# frozen_string_literal: true require_relative "errors" module Hanami module Middleware # HTTP request body parser class BodyParser # @api private # @since 1.3.0 module ClassInterface # Instantiate a new body parser instance and load its parsers # # @example # Hanami::Middleware::BodyParser.new(->(env) { [200, {}, "app"] }, :json) # # Hanami::Middleware::BodyParser.new( # ->(env) { [200, {}, "app"] }, [json: "application/json+scim"] # ) # # Hanami::Middleware::BodyParser.new( # ->(env) { [200, {}, "app"] }, [json: ["application/json+scim", "application/ld+json"]] # ) # # @param app [#call] # @param parser_specs [Symbol, Array] parser name or name with mime-type(s) # # @api private # @since 2.0.0 # @return BodyParser def new(app, parser_specs) super(app, build_parsers(parser_specs)) end # @api private # @since 1.3.0 def build(parser, **config) parser = case parser when String, Symbol build(parser_class(parser), **config) when Class parser.new(**config) else parser end ensure_parser parser parser end # @api private # @since 2.0.0 def build_parsers(parser_specs, registry = {}) return DEFAULT_BODY_PARSERS if parser_specs.empty? parsers = Array(parser_specs).flatten(0) parsers.each_with_object(registry) do |spec, memo| if spec.is_a?(Hash) && spec.size > 1 spec.each do |key, value| build_parsers([key => [value]], memo) end else name, *mime_types = Array(*spec).flatten(0) parser = build(name, mime_types: mime_types.flatten) parser.mime_types.each do |mime| memo[mime] = parser end end end end private # @api private # @since 1.3.0 PARSER_METHODS = %i[mime_types parse].freeze # @api private # @since 2.0.0 DEFAULT_BODY_PARSERS = {}.freeze # @api private # @since 1.3.0 def ensure_parser(parser) unless PARSER_METHODS.all? { |method| parser.respond_to?(method) } raise InvalidParserError.new(parser) end end # @api private # @since 1.3.0 # rubocop:disable Lint/SuppressedException def parser_class(parser_name) parser = nil begin require "hanami/middleware/body_parser/#{parser_name}_parser" rescue LoadError; end begin parser = load_parser!("#{classify(parser_name)}Parser") rescue NameError; end parser ensure raise UnknownParserError, parser_name unless parser end # rubocop:enable Lint/SuppressedException # @api private # @since 1.3.0 def classify(parser) parser.to_s.split("_").map(&:capitalize).join end # @api private # @since 1.3.0 def load_parser!(class_name) Hanami::Middleware::BodyParser.const_get(class_name, false) end end end end end