# frozen_string_literal: true

require 'rom/constants'
require 'rom/relation_registry'
require 'rom/mapper_registry'

module ROM
  class Finalize
    class FinalizeRelations
      attr_reader :notifications

      class RegistryReaders < ::Module
        def initialize(relations)
          super()

          relations.each do |name|
            define_method(name) { __registry__[name] }
          end
        end
      end

      # Build relation registry of specified descendant classes
      #
      # This is used by the setup
      #
      # @param [Hash] gateways
      # @param [Array] relation_classes a list of relation descendants
      #
      # @api private
      def initialize(gateways, relation_classes, notifications:, mappers: nil, plugins: EMPTY_ARRAY)
        @gateways = gateways
        @relation_classes = relation_classes
        @mappers = mappers
        @plugins = plugins
        @notifications = notifications
      end

      # @return [Hash]
      #
      # @api private
      #
      # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      def run!
        relation_registry = RelationRegistry.new do |registry, relations|
          registry_readers = RegistryReaders.new(relation_names)
          @relation_classes.each do |klass|
            unless klass.adapter
              raise MissingAdapterIdentifierError,
                    "Relation class +#{klass}+ is missing the adapter identifier"
            end

            key = klass.relation_name.to_sym

            if registry.key?(key)
              raise RelationAlreadyDefinedError,
                    "Relation with name #{key.inspect} registered more than once"
            end

            klass.use(:registry_reader, readers: registry_readers)

            notifications.trigger(
              'configuration.relations.class.ready',
              relation: klass,
              adapter: klass.adapter
            )

            relations[key] = build_relation(klass, registry)
          end

          registry.each_value do |relation|
            notifications.trigger(
              'configuration.relations.object.registered',
              relation: relation, registry: registry
            )
          end
        end

        notifications.trigger(
          'configuration.relations.registry.created', registry: relation_registry
        )

        relation_registry
      end
      # rubocop:enable Metrics/AbcSize, Metrics/MethodLength

      # @return [ROM::Relation]
      #
      # @api private
      #
      # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
      def build_relation(klass, registry)
        # TODO: raise a meaningful error here and add spec covering the case
        #       where klass' gateway points to non-existant repo
        gateway = @gateways.fetch(klass.gateway)

        plugins = schema_plugins

        schema = klass.schema_proc.call do
          plugins.each { |plugin| app_plugin(plugin) }
        end

        klass.set_schema!(schema) if klass.schema.nil?

        notifications.trigger(
          'configuration.relations.schema.allocated',
          schema: schema, gateway: gateway, registry: registry
        )

        relation_plugins.each do |plugin|
          plugin.apply_to(klass)
        end

        notifications.trigger(
          'configuration.relations.schema.set',
          schema: schema, relation: klass, registry: registry, adapter: klass.adapter
        )

        rel_key = schema.name.to_sym
        dataset = gateway.dataset(schema.name.dataset).instance_exec(klass, &klass.dataset)

        notifications.trigger(
          'configuration.relations.dataset.allocated',
          dataset: dataset, relation: klass, adapter: klass.adapter
        )

        options = {
          __registry__: registry,
          mappers: mapper_registry(rel_key, klass),
          schema: schema,
          **plugin_options
        }

        klass.new(dataset, **options)
      end
      # rubocop:enable Metrics/MethodLength, Metrics/AbcSize

      # @api private
      def mapper_registry(rel_key, rel_class)
        registry = rel_class.mapper_registry(cache: @mappers.cache)

        if @mappers.key?(rel_key)
          registry.merge(@mappers[rel_key])
        else
          registry
        end
      end

      # @api private
      def plugin_options
        relation_plugins.map(&:config).map(&:to_hash).reduce(:merge) || EMPTY_HASH
      end

      # @api private
      def relation_plugins
        @plugins.select { |p| p.type == :relation }
      end

      # @api private
      def schema_plugins
        @plugins.select { |p| p.type == :schema }
      end

      # @api private
      def relation_names
        @relation_classes.map(&:relation_name).map(&:relation).uniq
      end
    end
  end
end


def build_relation(klass, registry)
        # TODO: raise a meaningful error here and add spec covering the case
        #       where klass' gateway points to non-existant repo
        gateway = @gateways.fetch(klass.gateway)

        plugins = schema_plugins

        schema = klass.schema_proc.call do
          plugins.each { |plugin| app_plugin(plugin) }
        end

        klass.set_schema!(schema) if klass.schema.nil?

        notifications.trigger(
          'configuration.relations.schema.allocated',
          schema: schema, gateway: gateway, registry: registry
        )

        relation_plugins.each do |plugin|
          plugin.apply_to(klass)
        end

        notifications.trigger(
          'configuration.relations.schema.set',
          schema: schema, relation: klass, registry: registry, adapter: klass.adapter
        )

        rel_key = schema.name.to_sym
        dataset = gateway.dataset(schema.name.dataset).instance_exec(klass, &klass.dataset)

        notifications.trigger(
          'configuration.relations.dataset.allocated',
          dataset: dataset, relation: klass, adapter: klass.adapter
        )

        options = {
          __registry__: registry,
          mappers: mapper_registry(rel_key, klass),
          schema: schema,
          **plugin_options
        }

        klass.new(dataset, **options)
      end

dataset

      def mapper_registry(rel_key, rel_class)
        registry = rel_class.mapper_registry(cache: @mappers.cache)

        if @mappers.key?(rel_key)
          registry.merge(@mappers[rel_key])
        else
          registry
        end
      end


# frozen_string_literal: true

require 'rom/registry'
require 'rom/command_compiler'
require 'rom/command_registry'

module ROM
  class Finalize
    class FinalizeCommands
      attr_reader :notifications

      # Build command registry hash for provided relations
      #
      # @param [RelationRegistry] relations registry
      # @param [Hash] gateways
      # @param [Array] command_classes a list of command subclasses
      #
      # @api private
      def initialize(relations, gateways, command_classes, notifications)
        @relations = relations
        @gateways = gateways
        @command_classes = command_classes
        @notifications = notifications
      end

      # @return [Hash]
      #
      # @api private
      #
      # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      def run!
        commands = @command_classes.map do |klass|
          relation = @relations[klass.relation]
          gateway = @gateways[relation.gateway]

          notifications.trigger(
            'configuration.commands.class.before_build',
            command: klass, gateway: gateway, dataset: relation.dataset, adapter: relation.adapter
          )

          klass.extend_for_relation(relation) if klass.restrictable

          klass.build(relation)
        end

        registry = Registry.new
        compiler = CommandCompiler.new(@gateways, @relations, registry, notifications)

        @relations.each do |(name, relation)|
          rel_commands = commands.select { |c| c.relation.name == relation.name }

          rel_commands.each do |command|
            identifier = command.class.register_as || command.class.default_name
            relation.commands.elements[identifier] = command
          end

          relation.commands.set_compiler(compiler)
          relation.commands.set_mappers(relation.mappers)

          registry.elements[name] = relation.commands
        end

        registry
      end
      # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
    end
  end
end


# frozen_string_literal: true

require 'rom/registry'

module ROM
  class Finalize
    class FinalizeMappers
      attr_reader :mapper_classes
      attr_reader :mapper_objects
      attr_reader :registry_hash

      # @api private
      def initialize(mapper_classes, mapper_objects)
        @mapper_classes = mapper_classes
        @mapper_objects = mapper_objects

        check_duplicate_registered_mappers

        @registry_hash = [@mapper_classes.map(&:base_relation) + @mapper_objects.keys]
          .flatten
          .uniq
          .to_h { [_1, {}] }
      end

      # @api private
      def run!
        cache = Cache.new

        mappers = registry_hash.to_h do |relation_name, relation_mappers|
          relation_mappers.update(build_mappers(relation_name))

          if mapper_objects.key?(relation_name)
            relation_mappers.update(mapper_objects[relation_name])
          end

          [relation_name, MapperRegistry.new(relation_mappers, cache: cache)]
        end

        Registry.new(mappers, cache: cache)
      end

      private

      def check_duplicate_registered_mappers
        duplicates = mapper_classes.map { [_1.relation, _1.register_as] }.tally.select { _2 > 1 }

        case duplicates.first
        in [rel, as], _
          raise MapperAlreadyDefinedError,
                "Mapper with `register_as #{as.inspect}` registered more " \
                "than once for relation #{rel.inspect}"
        else
          nil
        end
      end

      def build_mappers(relation_name)
        mapper_classes
          .select { |klass| klass.base_relation == relation_name }
          .to_h { |k| [k.register_as || k.relation, k.build] }
      end
    end
  end
end


def check_duplicate_registered_mappers
        duplicates = mapper_classes.map { [_1.relation, _1.register_as] }.tally.select { _2 > 1 }

        case duplicates.first
        in [rel, as], _
          raise MapperAlreadyDefinedError,
                "Mapper with `register_as #{as.inspect}` registered more " \
                "than once for relation #{rel.inspect}"
        else
          nil
        end
      end


# frozen_string_literal: true

module ROM
  module Commands
    class Lazy
      # Lazy command wrapper for delete commands
      #
      # @api public
      class Delete < Lazy
        # Execute a lazy delete command
        #
        # @see Commands::Delete#call
        #
        # @return [Hash, Array<Hash>]
        #
        # @api public
        def call(*args)
          first = args.first
          last = args.last
          size = args.size

          if size > 1 && last.is_a?(Array)
            raise NotImplementedError
          else
            input = evaluator.call(first)

            if input.is_a?(Array)
              input.map do |item|
                command_proc[command, *(size > 1 ? [last, item] : [input])].call
              end
            else
              command_proc[command, input].call
            end
          end
        end
      end
    end
  end
end

call

last

size > 1 && last.is_a?(Array)
!(size > 1 && last.is_a?(Array))


# frozen_string_literal: true

module ROM
  module Commands
    class Lazy
      # Lazy command wrapper for create commands
      #
      # @api public
      class Create < Lazy
        # Execute a command
        #
        # @see Command::Create#call
        #
        # @return [Hash,Array<Hash>]
        #
        # @api public
        # rubocop:disable Metrics/AbcSize
        def call(*args)
          first = args.first
          last = args.last
          size = args.size

          if size > 1 && last.is_a?(Array)
            last.map.with_index do |parent, index|
              children = evaluator.call(first, index)
              command_proc[command, parent, children].call(children, parent)
            end.reduce(:concat)
          else
            input = evaluator.call(first)
            command.call(input, *args[1..size - 1])
          end
        end
        # rubocop:enable Metrics/AbcSize
      end
    end
  end
end

call

size > 1 && last.is_a?(Array)
!(size > 1 && last.is_a?(Array))


# frozen_string_literal: true

module ROM
  module Commands
    class Lazy
      # Lazy command wrapper for update commands
      #
      # @api public
      class Update < Lazy
        # Execute a lazy update command
        #
        # @see Commands::Update#call
        #
        # @return [Hash, Array<Hash>]
        #
        # @api public
        # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
        def call(*args)
          first = args.first
          last = args.last
          size = args.size

          if size > 1 && last.is_a?(Array)
            last.map.with_index do |parent, index|
              children = evaluator.call(first, index)

              children.map do |child|
                command_proc[command, parent, child].call(child, parent)
              end
            end.reduce(:concat)
          else
            input = evaluator.call(first)

            if input.is_a?(Array)
              input.map.with_index do |item, _index|
                command_proc[command, last, item].call(item, *args[1..size - 1])
              end
            else
              command_proc[command, *(size > 1 ? [last, input] : [input])]
                .call(input, *args[1..size - 1])
            end
          end
        end
        # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
      end
    end
  end
end


# frozen_string_literal: true

require 'rom/commands/graph/input_evaluator'

module ROM
  module Commands
    class Graph
      # Class methods for command Graph
      #
      # @api private
      module ClassInterface
        # Build a command graph recursively
        #
        # This is used by `Container#command` when array with options is passed in
        #
        # @param [Registry] registry The command registry from container
        # @param [Array] options The options array
        # @param [Array] path The path for input evaluator proc
        #
        # @return [Graph]
        #
        # @api private
        def build(registry, options, path = EMPTY_ARRAY)
          options.reduce { |spec, other| build_command(registry, spec, other, path) }
        end

        # @api private
        # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
        def build_command(registry, spec, other, path)
          cmd_opts, nodes = other

          key, relation =
            if spec.is_a?(Hash)
              spec.to_a.first
            else
              [spec, spec]
            end

          name, opts =
            if cmd_opts.is_a?(Hash)
              cmd_opts.to_a.first
            else
              [cmd_opts]
            end

          command = registry[relation][name]
          tuple_path = [*path] << key
          input_proc = InputEvaluator.build(tuple_path, nodes)

          command = command.curry(input_proc, opts)

          if nodes
            if nodes.all? { |node| node.is_a?(Array) }
              command.combine(*nodes.map { |node| build(registry, node, tuple_path) })
            else
              command.combine(build(registry, nodes, tuple_path))
            end
          else
            command
          end
        end
        # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
      end
    end
  end
end

build

        def self.build(tuple_path, nodes)
          new(tuple_path, extract_excluded_keys(nodes))
        end

build

!(nodes.all? { |node| node.is_a?(Array) })


# frozen_string_literal: true

module ROM
  module Commands
    class Graph
      # Evaluator for lazy commands which extracts values for commands from nested hashes
      #
      # @api private
      class InputEvaluator
        include Dry::Equalizer(:tuple_path, :excluded_keys)

        # @!attribute [r] tuple_path
        #   @return [Array<Symbol>] A list of keys pointing to a value inside a hash
        attr_reader :tuple_path

        # @!attribute [r] excluded_keys
        #   @return [Array<Symbol>] A list of keys that should be excluded
        attr_reader :excluded_keys

        # @!attribute [r] exclude_proc
        #   @return [Array<Symbol>] A function that should determine which keys should be excluded
        attr_reader :exclude_proc

        # Build an input evaluator
        #
        # @param [Array<Symbol>] tuple_path The tuple path
        # @param [Array] nodes
        #
        # @return [InputEvaluator]
        #
        # @api private
        def self.build(tuple_path, nodes)
          new(tuple_path, extract_excluded_keys(nodes))
        end

        # @api private
        def self.extract_excluded_keys(nodes)
          return unless nodes

          nodes
            .map { |item| item.is_a?(Array) && item.size > 1 ? item.first : item }
            .compact
            .map { |item| item.is_a?(Hash) ? item.keys.first : item }
            .reject { |item| item.is_a?(Array) }
        end

        # Return default exclude_proc
        #
        # @api private
        def self.exclude_proc(excluded_keys)
          -> input { input.except(*excluded_keys) }
        end

        # Initialize a new input evaluator
        #
        # @return [InputEvaluator]
        #
        # @api private
        def initialize(tuple_path, excluded_keys)
          @tuple_path = tuple_path
          @excluded_keys = excluded_keys
          @exclude_proc = self.class.exclude_proc(excluded_keys)
        end

        # Evaluate input hash
        #
        # @param [Hash] input The input hash
        # @param [Integer] index Optional index
        #
        # @return [Hash]
        # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
        def call(input, index = nil)
          value =
            begin
              if index
                tuple_path[0..tuple_path.size - 2]
                  .reduce(input) { |a, e| a.fetch(e) }
                  .at(index)[tuple_path.last]
              else
                tuple_path.reduce(input) { |a, e| a.fetch(e) }
              end
            rescue KeyError => e
              raise KeyMissing, e.message
            end

          if excluded_keys
            value.is_a?(Array) ? value.map(&exclude_proc) : exclude_proc[value]
          else
            value
          end
        end
        # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
      end
    end
  end
end


# frozen_string_literal: true

require 'set'

module ROM
  module Plugins
    module Command
      # A plugin for automatically adding timestamp values
      # when executing a command
      #
      # Set up attributes to timestamp when the command is called
      #
      # @example
      #   class CreateTask < ROM::Commands::Create[:sql]
      #     result :one
      #     use :timestamps, timestamps: %i(created_at, updated_at), datestamps: %i(:written)
      #   end
      #
      #   create_user = rom.command(:user).create.curry(name: 'Jane')
      #
      #   result = create_user.call
      #   result[:created_at]  #=> Time.now.utc
      #
      # @api public
      class Timestamps < ::Module
        attr_reader :timestamps

        attr_reader :datestamps

        def initialize(timestamps: [], datestamps: [])
          super()
          @timestamps = store_attributes(timestamps)
          @datestamps = store_attributes(datestamps)
        end

        # @api private
        def store_attributes(attr)
          attr.is_a?(Array) ? attr : [attr]
        end

        # @api private
        def included(klass)
          initialize_timestamp_attributes(klass)
          klass.include(InstanceMethods)
          klass.extend(ClassInterface)
          super
        end

        def initialize_timestamp_attributes(klass)
          klass.defines :timestamp_columns, :datestamp_columns
          klass.timestamp_columns Set.new
          klass.datestamp_columns Set.new
          klass.before :set_timestamps
          klass.timestamp_columns klass.timestamp_columns.merge(timestamps) if timestamps.any?
          klass.datestamp_columns klass.datestamp_columns.merge(datestamps) if datestamps.any?
        end

        module InstanceMethods
          # @api private
          def timestamp_columns
            self.class.timestamp_columns
          end

          # @api private
          def datestamp_columns
            self.class.datestamp_columns
          end

          # Set the timestamp attributes on the given tuples
          #
          # @param [Array<Hash>, Hash] tuples the input tuple(s)
          #
          # @return [Array<Hash>, Hash]
          #
          # @api private
          def set_timestamps(tuples, *)
            timestamps = build_timestamps

            map_input_tuples(tuples) { |t| timestamps.merge(t) }
          end

          private

          # @api private
          def build_timestamps
            time        = Time.now.utc
            date        = Date.today
            timestamps  = {}

            timestamp_columns.each do |column|
              timestamps[column.to_sym] = time
            end

            datestamp_columns.each do |column|
              timestamps[column.to_sym] = date
            end

            timestamps
          end
        end

        module ClassInterface
          # @api private
          # Set up attributes to timestamp when the command is called
          #
          # @example
          #   class CreateTask < ROM::Commands::Create[:sql]
          #     result :one
          #     use :timestamps
          #     timestamps :created_at, :updated_at
          #   end
          #
          #   create_user = rom.command(:user).create.curry(name: 'Jane')
          #
          #   result = create_user.call
          #   result[:created_at]  #=> Time.now.utc
          #
          # @param [Array<Symbol>] names A list of attribute names
          #
          # @api public
          def timestamps(*names)
            timestamp_columns timestamp_columns.merge(names)
          end
          alias_method :timestamp, :timestamps

          # Set up attributes to datestamp when the command is called
          #
          # @example
          #   class CreateTask < ROM::Commands::Create[:sql]
          #     result :one
          #     use :timestamps
          #     datestamps :created_on, :updated_on
          #   end
          #
          #   create_user = rom.command(:user).create.curry(name: 'Jane')
          #
          #   result = create_user.call
          #   result[:created_at]  #=> Date.today
          #
          # @param [Array<Symbol>] names A list of attribute names
          #
          # @api public
          def datestamps(*names)
            datestamp_columns datestamp_columns.merge(names)
          end
          alias_method :datestamp, :datestamps
        end
      end
    end
  end
end


# frozen_string_literal: true

module ROM
  module Plugins
    module Command
      # Command plugin which sets input processing function via relation schema
      #
      # @api private
      module Schema
        def self.included(klass)
          super
          klass.extend(ClassInterface)
        end

        # @api private
        module ClassInterface
          # Build a command and set it input to relation's input_schema
          #
          # @see Command.build
          #
          # @return [Command]
          #
          # @api public
          def build(relation, **options)
            if relation.schema? && !options.key?(:input)
              relation_input = relation.input_schema
              command_input = input

              composed_input =
                if command_input.equal?(ROM::Command.input)
                  relation_input
                else
                  -> tuple { relation_input[command_input[tuple]] }
                end

              super(relation, **options, input: composed_input)
            else
              super
            end
          end
        end
      end
    end
  end
end

build

input

    # @!method self.input
    #   Get or set input processing function. This is typically set during setup
    #   to relation's input_schema
    #
    #   @overload input
    #     Get input processing function
    #
    #     @example
    #       class CreateUser < ROM::Commands::Create[:memory]
    #         input -> tuple { .. }
    #       end
    #
    #       CreateUser.input
    #       # Your custom function
    #
    #     @return [Proc,#call]
    #
    #   @overload input(identifier)
    #     Set input processing function
    #
    #     @example
    #       class CreateUser < ROM::Commands::Create[:memory]
    #         input -> tuple { .. }
    #       end
    #
    # @api public
    defines :input

    # @!method self.register_as
    #   Get or set identifier that should be used to register a command in a container
    #
    #   @overload register_as
    #     Get registration identifier
    #
    #     @example
    #       class CreateUser < ROM::Commands::Create[:memory]
    #         register_as :create_user
    #       end
    #
    #       CreateUser.register_as
    #       # => :create_user
    #
    #     @return [Symbol]
    #
    #   @overload register_as(identifier)
    #     Set registration identifier
    #
    #     @example
    #       class CreateUser < ROM::Commands::Create[:memory]
    #         register_as :create_user
    #       end
    #
    # @api public
    defines :register_as

    # @!method self.restrictable
    #   @overload restrictable
    #     Check if a command class is restrictable
    #
    #     @example
    #       class UpdateUser < ROM::Commands::Update[:memory]
    #         restrictable true
    #       end
    #
    #       CreateUser.restrictable
    #       # => true
    #
    #     @return [FalseClass, TrueClass]
    #
    #   @overload restrictable(value)
    #     Set if a command is restrictable
    #
    #     @example
    #       class UpdateUser < ROM::Commands::Update[:memory]
    #         restrictable true
    #       end
    #
    # @api public
    defines :restrictable

    # @!attribute [r] relation
    #   @return [Relation] Command's relation
    param :relation

    CommandType = Types::Strict::Symbol.enum(:create, :update, :delete)
    Result = Types::Strict::Symbol.enum(:one, :many)

    # @!attribute [r] type
    #   @return [Symbol] The command type, one of :create, :update or :delete
    option :type, type: CommandType, optional: true

    # @!attribute [r] source
    #   @return [Relation] The source relation
    option :source, default: -> { relation }

    # @!attribute [r] result
    #   @return [Symbol] Result type, either :one or :many
    option :result, type: Result

    # @!attribute [r] input
    #   @return [Proc, #call] Tuple processing function, typically uses Relation#input_schema
    option :input

    # @!attribute [r] curry_args
    #   @return [Array] Curried args
    option :curry_args, default: -> { EMPTY_ARRAY }

    # @!attribute [r] before
    #   @return [Array<Hash>] An array with before hooks configuration
    option :before, Types::Coercible::Array, reader: false, default: -> { self.class.before }

    # @!attribute [r] after
    #   @return [Array<Hash>] An array with after hooks configuration
    option :after, Types::Coercible::Array, reader: false, default: -> { self.class.after }

    input Hash
    result :many

    # Return name of this command's relation
    #
    # @return [ROM::Relation::Name]
    #
    # @api public
    def name
      relation.name
    end


# frozen_string_literal: true

require 'transproc/transformer'

require 'rom/changeset/pipe_registry'

module ROM
  class Changeset
    # Composable data transformation pipe used by default in changesets
    #
    # @api private
    class Pipe < Transproc::Transformer[PipeRegistry]
      extend Initializer

      define!(&:identity)

      param :processor, optional: true

      option :use_for_diff, optional: true, default: -> { true }
      option :diff_processor, default: -> { self[processor] }

      def self.new(*args, **opts)
        if args.empty?
          initialize(**opts)
        else
          super
        end
      end

      def self.initialize(**opts)
        transformer = allocate
        transformer.__send__(:initialize, dsl.(transformer), **opts)
        transformer
      end

      def self.[](name_or_proc)
        container[name_or_proc]
      end

      def [](*args)
        self.class[*args]
      end

      def bind(context)
        if processor.is_a?(Proc)
          bound_processor = self[-> *args { context.instance_exec(*args, &processor) }]
          bound_diff_processor = self[-> *args { context.instance_exec(*args, &diff_processor) }]

          new(bound_processor, diff_processor: bound_diff_processor)
        else
          self
        end
      end

      def compose(other, for_diff: other.is_a?(self.class) ? other.use_for_diff : false)
        new_proc = processor >> other

        if for_diff
          diff_proc = diff_processor >> (
            other.is_a?(self.class) ? other.diff_processor : other
          )

          new(new_proc, use_for_diff: true, diff_processor: diff_proc)
        else
          new(new_proc)
        end
      end
      alias_method :>>, :compose

      def call(data)
        processor.call(data)
      end

      def for_diff(data)
        use_for_diff ? diff_processor.call(data) : data
      end

      def new(processor, **opts)
        self.class.new(processor, **options, **opts)
      end
    end
  end
end


# frozen_string_literal: true

require 'rom/changeset/pipe'

module ROM
  class Changeset
    # Stateful changesets carry data and can transform it into
    # a different structure compatible with a persistence backend
    #
    # @abstract
    class Stateful < Changeset
      # Default no-op pipe
      EMPTY_PIPE = Pipe.new(use_for_diff: false).freeze

      # @!attribute [r] __data__
      #   @return [Hash] The relation data
      #   @api private
      option :__data__, optional: true

      # @!attribute [r] pipe
      #   @return [Changeset::Pipe] data transformation pipe
      #   @api private
      option :pipe, reader: false, optional: true

      # Define a changeset mapping
      #
      # Subsequent mapping definitions will be composed together
      # and applied in the order they way defined
      #
      # @example Transformation DSL
      #   class NewUser < ROM::Changeset::Create
      #     map do
      #       unwrap :address, prefix: true
      #     end
      #   end
      #
      # @example Using custom block
      #   class NewUser < ROM::Changeset::Create
      #     map do |tuple|
      #       tuple.merge(created_at: Time.now)
      #     end
      #   end
      #
      # @example Multiple mappings (executed in the order of definition)
      #   class NewUser < ROM::Changeset::Create
      #     map do
      #       unwrap :address, prefix: true
      #     end
      #
      #     map do |tuple|
      #       tuple.merge(created_at: Time.now)
      #     end
      #   end
      #
      # @return [Array<Pipe>, Transproc::Function>]
      #
      # @see https://github.com/solnic/transproc Transproc
      #
      # @api public
      def self.map(**options, &block)
        if block.parameters.empty?
          pipes << Class.new(Pipe).define!(&block).new(**options)
        else
          pipes << Pipe.new(block, **options)
        end
      end

      # Define a changeset mapping excluded from diffs
      #
      # @see Changeset::Stateful.map
      # @see Changeset::Stateful#extend
      #
      # @return [Array<Pipe>, Transproc::Function>]
      #
      # @api public
      def self.extend(*, &)
        if block_given?
          map(use_for_diff: false, &)
        else
          super
        end
      end

      # Build default pipe object
      #
      # This can be overridden in a custom changeset subclass
      #
      # @return [Pipe]
      def self.default_pipe(context)
        pipes.empty? ? EMPTY_PIPE : pipes.map { |p| p.bind(context) }.reduce(:>>)
      end

      # @api private
      def self.inherited(klass)
        return if klass == ROM::Changeset

        super
        klass.instance_variable_set(:@__pipes__, pipes.dup)
      end

      # @api private
      def self.pipes
        @__pipes__
      end
      @__pipes__ = EMPTY_ARRAY

      # Pipe changeset's data using custom steps define on the pipe
      #
      # @overload map(*steps)
      #   Apply mapping using built-in transformations
      #
      #   @example
      #     changeset.map(:add_timestamps)
      #
      #   @param [Array<Symbol>] steps A list of mapping steps
      #
      # @overload map(&block)
      #   Apply mapping using a custom block
      #
      #   @example
      #     changeset.map { |tuple| tuple.merge(created_at: Time.now) }
      #
      # @overload map(*steps, &block)
      #   Apply mapping using built-in transformations and a custom block
      #
      #   @example
      #     changeset.map(:add_timestamps) { |tuple| tuple.merge(status: 'published') }
      #
      #   @param [Array<Symbol>] steps A list of mapping steps
      #
      # @return [Changeset]
      #
      # @api public
      def map(*steps, &)
        extend(*steps, for_diff: true, &)
      end

      # Pipe changeset's data using custom steps define on the pipe.
      # You should use #map instead except updating timestamp fields.
      # Calling changeset.extend builds a pipe that excludes certain
      # steps for generating the diff. Currently the only place where
      # it is used is update changesets with the `:touch` step, i.e.
      # `changeset.extend(:touch).diff` will exclude `:updated_at`
      # from the diff.
      #
      # @see Changeset::Stateful#map
      #
      # @return [Changeset]
      #
      # @api public
      def extend(*steps, **options, &)
        if block_given?
          if steps.empty?
            with(pipe: pipe.compose(Pipe.new(proc(&)).bind(self), **options))
          else
            extend(*steps, **options).extend(**options, &)
          end
        else
          with(pipe: steps.reduce(pipe.with(**options)) { |a, e| a.compose(pipe[e], **options) })
        end
      end

      # Return changeset with data
      #
      # @param [Hash] data
      #
      # @return [Changeset]
      #
      # @api public
      def data(data)
        with(__data__: data)
      end

      # Coerce changeset to a hash
      #
      # This will send the data through the pipe
      #
      # @return [Hash]
      #
      # @api public
      def to_h
        pipe.call(__data__)
      end
      alias_method :to_hash, :to_h

      # Coerce changeset to an array
      #
      # This will send the data through the pipe
      #
      # @return [Array]
      #
      # @api public
      def to_a
        result == :one ? [to_h] : __data__.map { |element| pipe.call(element) }
      end
      alias_method :to_ary, :to_a

      # Commit stateful changeset
      #
      # @see Changeset#commit
      #
      # @api public
      def commit
        command.call(self)
      end

      # Associate a changeset with another changeset or hash-like object
      #
      # @example with another changeset
      #   new_user = users.changeset(name: 'Jane')
      #   new_task = users.changeset(:tasks, title: 'A task')
      #
      #   new_task.associate(new_user, :users)
      #
      # @example with a hash-like object
      #   user = users.users.by_pk(1).one
      #   new_task = users.changeset(:tasks, title: 'A task')
      #
      #   new_task.associate(user, :users)
      #
      # @param [#to_hash, Changeset] other Other changeset or hash-like object
      # @param [Symbol] name The association identifier from schema
      #
      # @api public
      def associate(other, name = Associated.infer_assoc_name(other))
        Associated.new(self, associations: { name => other })
      end

      # Return command result type
      #
      # @return [Symbol]
      #
      # @api private
      def result
        __data__.is_a?(Array) ? :many : :one
      end

      # Return string representation of the changeset
      #
      # @return [String]
      #
      # @api public
      def inspect
        %(#<#{self.class} relation=#{relation.name.inspect} data=#{__data__}>)
      end

      # @api private
      def command_compiler_options
        super.merge(result: result)
      end

      # Data transformation pipe
      #
      # @return [Changeset::Pipe]
      #
      # @api private
      def pipe
        @pipe ||= self.class.default_pipe(self)
      end

      private

      # @api private
      def respond_to_missing?(meth, include_private = false)
        super || __data__.respond_to?(meth)
      end

      # @api private
      def method_missing(meth, ...)
        if __data__.respond_to?(meth)
          response = __data__.__send__(meth, ...)

          if response.is_a?(__data__.class)
            with(__data__: response)
          else
            response
          end
        else
          super
        end
      end
    end
  end
end


def method_missing(meth, ...)
        if __data__.respond_to?(meth)
          response = __data__.__send__(meth, ...)

          if response.is_a?(__data__.class)
            with(__data__: response)
          else
            response
          end
        else
          super
        end
      end


# frozen_string_literal: true

module ROM
  class Changeset
    # Changeset specialization for update commands
    #
    # Update changesets will only execute their commands when
    # the data is different from the original tuple. Original tuple
    # is fetched from changeset's relation using `one` method.
    #
    # @example
    #   users.by_pk(1).changeset(:update, name: "Jane Doe").commit
    #
    # @see Changeset::Stateful
    #
    # @api public
    class Update < Stateful
      command_type :update

      # Commit update changeset if there's a diff
      #
      # This returns original tuple if there's no diff
      #
      # @return [Hash]
      #
      # @see Changeset#commit
      #
      # @api public
      def commit
        diff? ? super : original
      end

      # Return original tuple that this changeset may update
      #
      # @return [Hash]
      #
      # @api public
      def original
        @original ||= relation.one
      end

      # Return true if there's a diff between original and changeset data
      #
      # @return [TrueClass, FalseClass]
      #
      # @api public
      def diff?
        !diff.empty?
      end

      # Return if there's no diff between the original and changeset data
      #
      # @return [TrueClass, FalseClass]
      #
      # @api public
      def clean?
        diff.empty?
      end

      # Calculate the diff between the original and changeset data
      #
      # @return [Hash]
      #
      # @api public
      #
      # rubocop:disable Metrics/AbcSize
      def diff
        @diff ||=
          begin
            source = Hash(original)
            data = pipe.for_diff(__data__)
            data_tuple = data.to_a
            data_keys = data.keys & source.keys

            new_tuple = data_tuple.to_a.select { |k, _| data_keys.include?(k) }
            ori_tuple = source.to_a.select { |k, _| data_keys.include?(k) }

            (new_tuple - (new_tuple & ori_tuple)).to_h
          end
      end
      # rubocop:enable Metrics/AbcSize
    end
  end
end

diff

      def for_diff(data)
        use_for_diff ? diff_processor.call(data) : data
      end


# frozen_string_literal: true

require 'rom/initializer'

module ROM
  class Changeset
    # Associated changesets automatically set up FKs
    #
    # @api public
    class Associated
      extend Initializer

      # @!attribute [r] left
      #   @return [Changeset::Create] Child changeset
      param :left

      # @!attribute [r] associations
      #   @return [Array] List of association identifiers from relation schema
      option :associations

      # Infer association name from an object with a schema
      #
      # This expects other to be an object with a schema that includes a primary key
      # attribute with :source meta information. This makes it work with both structs
      # and relations
      #
      # @see Stateful#associate
      #
      # @api private
      def self.infer_assoc_name(other)
        schema = other.class.schema
        attrs = schema.is_a?(Hash) ? schema.values : schema
        pk = attrs.detect { |attr| attr.meta[:primary_key] }

        if pk
          pk.meta[:source]
        else
          raise ArgumentError, "can't infer association name for #{other}"
        end
      end

      # Commit changeset's composite command
      #
      # @example
      #   task_changeset = tasks.
      #     changeset(title: 'Task One').
      #     associate(user, :user).
      #     commit
      #   # {:id => 1, :user_id => 1, title: 'Task One'}
      #
      # @return [Array<Hash>, Hash]
      #
      # @api public
      def commit
        command.call
      end

      # Associate with other changesets
      #
      # @see Changeset#associate
      #
      # @return [Associated]
      #
      # @api public
      def associate(other, name = Associated.infer_assoc_name(other))
        self.class.new(left, associations: associations.merge(name => other))
      end

      # Create a composed command
      #
      # @example using existing parent data
      #   user_changeset = users.changeset(name: 'Jane')
      #   task_changeset = tasks.changeset(title: 'Task One')
      #
      #   user = users.create(user_changeset)
      #   task = tasks.create(task_changeset.associate(user, :user))
      #
      # @example saving both parent and child in one go
      #   user_changeset = users.changeset(name: 'Jane')
      #   task_changeset = tasks.changeset(title: 'Task One')
      #
      #   task = tasks.create(task_changeset.associate(user, :user))
      #
      # This works *only* with parent => child(ren) changeset hierarchy
      #
      # @return [ROM::Command::Composite]
      #
      # @api public
      def command
        associations.reduce(left.command.curry(left)) do |a, (assoc, other)|
          case other
          when Changeset
            a >> other.command.with_association(assoc).curry(other)
          when Associated
            a >> other.command.with_association(assoc)
          when Array
            raise NotImplementedError, 'Changeset::Associate does not support arrays yet'
          else
            a.with_association(assoc, parent: other)
          end
        end
      end

      # @api private
      def relation
        left.relation
      end
    end
  end
end


def command
        associations.reduce(left.command.curry(left)) do |a, (assoc, other)|
          case other
          when Changeset
            a >> other.command.with_association(assoc).curry(other)
          when Associated
            a >> other.command.with_association(assoc)
          when Array
            raise NotImplementedError, 'Changeset::Associate does not support arrays yet'
          else
            a.with_association(assoc, parent: other)
          end
        end
      end

command

curry

    def curry(*args)
      if curry_args.empty? && args.first.is_a?(Graph::InputEvaluator)
        Lazy[self].new(self, *args)
      else
        self.class.build(relation, **options, curry_args: args)
      end
    end


# frozen_string_literal: true

require 'rom/support/notifications'

require 'rom/changeset/create'
require 'rom/changeset/update'
require 'rom/changeset/delete'

require 'rom/changeset/extensions/relation'

module ROM
  module Plugins
    module Relation
      # Relation plugin which adds `Relation#changeset` method
      #
      # @api public
      module Changeset
        TYPES = {
          create: ROM::Changeset::Create,
          update: ROM::Changeset::Update,
          delete: ROM::Changeset::Delete
        }.freeze

        extend Notifications::Listener

        subscribe('configuration.relations.class.ready') do |event|
          event[:relation].include(InstanceMethods)
        end

        # Relation instance methods provided by the Changeset plugin
        #
        # @api public
        module InstanceMethods
          # Create a changeset for a relation
          #
          # @overload changeset(type, data)
          #   Create a changeset of one of predefined types
          #
          #   @example creating a record
          #     users.
          #       changeset(:create, name: 'Jane').
          #       commit
          #     # => #<ROM::Struct::User id=1 name="Jane">
          #
          #   @example updating a record
          #     users.
          #       by_pk(1).
          #       changeset(:update, name: 'Jack').
          #       commit
          #     # => #<ROM::Struct::User id=1 name="Jane">
          #
          #   @example providing data as a separate step
          #     changeset = users.changeset(:create)
          #     jack = changeset.data(name: 'Jack').commit
          #     jane = changeset.data(name: 'Jane').commit
          #
          #   @example using a command graph
          #     users.
          #       changeset(
          #         :create,
          #         name: "Jane",
          #         posts: [{ title: "Something about aliens" }]
          #       )
          #
          #   @param [Symbol] type The changeset type
          #   @param [Hash] data
          #   @return [Changeset]
          #
          # @overload changeset(changeset_class, data)
          #   @example using a custom changeset class
          #     class NewUser < ROM::Changeset::Create
          #       map do |tuple|
          #         { **tuple, name: tuple.values_at(:first_name, :last_name).join(' ') }
          #       end
          #     end
          #
          #     users.changeset(NewUser, first_name: 'John', last_name: 'Doe').commit
          #
          #   @param [Class] changeset_class A custom changeset class
          #   @return [Changeset]
          #
          # @api public
          def changeset(type, data = EMPTY_HASH)
            klass = type.is_a?(Symbol) ? TYPES.fetch(type) : type

            unless klass < ROM::Changeset
              raise ArgumentError, "+#{type.name}+ must be a Changeset descendant"
            end

            if klass < ROM::Changeset::Stateful
              klass.new(self, __data__: data)
            else
              klass.new(self)
            end
          rescue KeyError
            raise ArgumentError,
                  "+#{type.inspect}+ is not a valid changeset type. " \
                  "Must be one of: #{TYPES.keys.inspect}"
          end
        end
      end
    end
  end
end

changeset

klass < ROM::Changeset
klass < ROM::Changeset::Stateful
!(klass < ROM::Changeset)
!(klass < ROM::Changeset::Stateful)


# frozen_string_literal: true

require 'rom/repository/relation_reader'

module ROM
  class Repository
    # Class-level APIs for repositories
    #
    # @api public
    module ClassInterface
      # Create a root-repository class and set its root relation
      #
      # @example
      #   # where :users is the relation name in your rom container
      #   class UserRepo < ROM::Repository[:users]
      #   end
      #
      # @param name [Symbol] The relation `register_as` value
      #
      # @return [Class] descendant of ROM::Repository::Root
      #
      # @api public
      def [](name)
        fetch_or_store(name) do
          base =
            if self < Repository::Root # rubocop:disable Style/MinMaxComparison
              self
            else
              Repository::Root
            end

          klass = ::Class.new(base)
          klass.root(name)
          klass
        end
      end

      # Initialize a new repository object, establishing configured relation proxies from
      # the passed container
      #
      # @overload new(container, **options)
      #   Initialize with container as leading parameter
      #
      #   @param [ROM::Container] container Finalized rom container
      #
      #   @param [Hash] options Repository options
      #   @option options [Module] :struct_namespace Custom struct namespace
      #   @option options [Boolean] :auto_struct Enable/Disable auto-struct mapping
      #
      # @overload new(**options)
      #   Inititalize with container as option
      #
      #   @param [Hash] options Repository options
      #   @option options [ROM::Container] :container Finalized rom container
      #   @option options [Module] :struct_namespace Custom struct namespace
      #   @option options [Boolean] :auto_struct Enable/Disable auto-struct mapping
      #
      # @api public
      def new(container = nil, root: Undefined, **options)
        container ||= options.fetch(:container)

        unless relation_reader.added_to?(self)
          include relation_reader.new(
            relations: container.relations.elements.keys,
            cache: container.cache,
            root: root
          )
        end

        super(**options, container: container)
      end

      # Inherits configured relations and commands
      #
      # @api private
      def inherited(klass)
        super

        return if self === Repository # rubocop:disable Style/CaseEquality

        klass.extend(::Dry::Core::Cache)
        klass.commands(*commands)
      end

      # Defines command methods on a root repository
      #
      # @example
      #   class UserRepo < ROM::Repository[:users]
      #     commands :create, update: :by_pk, delete: :by_pk
      #   end
      #
      #   # with custom command plugin
      #   class UserRepo < ROM::Repository[:users]
      #     commands :create, use: :my_command_plugin
      #   end
      #
      #   # with custom mapper
      #   class UserRepo < ROM::Repository[:users]
      #     commands :create, mapper: :my_custom_mapper
      #   end
      #
      # @param [Array<Symbol>] names A list of command names
      # @option :mapper [Symbol] An optional mapper identifier
      # @option :use [Symbol] An optional command plugin identifier
      #
      # @return [Array<Symbol>] A list of defined command names
      #
      # @api public
      #
      # rubocop:disable Metrics/MethodLength
      def commands(*names, mapper: nil, use: nil, plugins_options: EMPTY_HASH, **opts)
        if names.any? || opts.any?
          @commands = names + opts.to_a

          @commands.each do |spec|
            type, *view = Array(spec).flatten

            if view.empty?
              define_command_method(
                type,
                mapper: mapper,
                use: use,
                plugins_options: plugins_options
              )
            else
              define_restricted_command_method(
                type,
                view,
                mapper: mapper,
                use: use,
                plugins_options: plugins_options
              )
            end
          end
        else
          @commands ||= []
        end
      end
      # rubocop:enable Metrics/MethodLength

      # @api public
      def use(plugin, **options)
        ROM.plugin_registry[:repository].fetch(plugin).apply_to(self, **options)
      end

      private

      # @api private
      def define_command_method(type, **opts)
        define_method(type) do |*input|
          if input.size == 1 && input[0].respond_to?(:commit)
            input[0].commit
          else
            root.command(type, **opts).call(*input)
          end
        end
      end

      # @api private
      def define_restricted_command_method(type, views, **opts)
        views.each do |view_name|
          meth_name = views.size > 1 ? :"#{type}_#{view_name}" : type

          define_method(meth_name) do |*args|
            arity = root.__send__(view_name).arity

            view_args = args[0..arity - 1]
            input = args[arity..args.size - 1]

            changeset = input.first

            if changeset.respond_to?(:commit)
              changeset.commit
            else
              root.command(type, **opts).public_send(view_name, *view_args).call(*input)
            end
          end
        end
      end
    end
  end
end


def new(container = nil, root: Undefined, **options)
        container ||= options.fetch(:container)

        unless relation_reader.added_to?(self)
          include relation_reader.new(
            relations: container.relations.elements.keys,
            cache: container.cache,
            root: root
          )
        end

        super(**options, container: container)
      end


def commands(*names, mapper: nil, use: nil, plugins_options: EMPTY_HASH, **opts)
        if names.any? || opts.any?
          @commands = names + opts.to_a

          @commands.each do |spec|
            type, *view = Array(spec).flatten

            if view.empty?
              define_command_method(
                type,
                mapper: mapper,
                use: use,
                plugins_options: plugins_options
              )
            else
              define_restricted_command_method(
                type,
                view,
                mapper: mapper,
                use: use,
                plugins_options: plugins_options
              )
            end
          end
        else
          @commands ||= []
        end
      end

commands

      def define_command_method(type, **opts)
        define_method(type) do |*input|
          if input.size == 1 && input[0].respond_to?(:commit)
            input[0].commit
          else
            root.command(type, **opts).call(*input)
          end
        end
      end

      def define_restricted_command_method(type, views, **opts)
        views.each do |view_name|
          meth_name = views.size > 1 ? :"#{type}_#{view_name}" : type

          define_method(meth_name) do |*args|
            arity = root.__send__(view_name).arity

            view_args = args[0..arity - 1]
            input = args[arity..args.size - 1]

            changeset = input.first

            if changeset.respond_to?(:commit)
              changeset.commit
            else
              root.command(type, **opts).public_send(view_name, *view_args).call(*input)
            end
          end
        end
      end

names.any? || opts.any?
!(names.any? || opts.any?)


def define_restricted_command_method(type, views, **opts)
        views.each do |view_name|
          meth_name = views.size > 1 ? :"#{type}_#{view_name}" : type

          define_method(meth_name) do |*args|
            arity = root.__send__(view_name).arity

            view_args = args[0..arity - 1]
            input = args[arity..args.size - 1]

            changeset = input.first

            if changeset.respond_to?(:commit)
              changeset.commit
            else
              root.command(type, **opts).public_send(view_name, *view_args).call(*input)
            end
          end
        end
      end


# frozen_string_literal: true

module ROM
  # TODO: finish this in 1.1.0
  #
  # @api private
  class Session
    include Dry::Equalizer(:queue, :status)

    attr_reader :repo

    attr_reader :queue

    attr_reader :status

    def initialize(repo)
      @repo = repo
      @status = :pending
      initialize_queue!
    end

    def add(changeset)
      queue << changeset
      self
    end

    def commit!
      queue.each(&:commit)

      @status = :success

      self
    rescue StandardError => e
      @status = :failure
      raise e
    ensure
      initialize_queue!
    end

    def pending?
      status.equal?(:pending)
    end

    def success?
      status.equal?(:success)
    end

    def failure?
      status.equal?(:failure)
    end

    private

    def initialize_queue!
      @queue = []
    end
  end
end


# frozen_string_literal: true

module Grape
  # An Endpoint is the proxy scope in which all routing
  # blocks are executed. In other words, any methods
  # on the instance level of this class may be called
  # from inside a `get`, `post`, etc.
  class Endpoint
    extend Forwardable
    include Grape::DSL::Settings
    include Grape::DSL::Headers
    include Grape::DSL::InsideRoute

    attr_reader :env, :request, :source, :options, :endpoints

    def_delegators :request, :params, :headers, :cookies
    def_delegator :cookies, :response_cookies

    class << self
      def block_to_unbound_method(block)
        return unless block

        define_method :temp_unbound_method, block
        method = instance_method(:temp_unbound_method)
        remove_method :temp_unbound_method
        method
      end
    end

    # Create a new endpoint.
    # @param new_settings [InheritableSetting] settings to determine the params,
    #   validations, and other properties from.
    # @param options [Hash] attributes of this endpoint
    # @option options path [String or Array] the path to this endpoint, within
    #   the current scope.
    # @option options method [String or Array] which HTTP method(s) can be used
    #   to reach this endpoint.
    # @option options route_options [Hash]
    # @note This happens at the time of API definition, so in this context the
    # endpoint does not know if it will be mounted under a different endpoint.
    # @yield a block defining what your API should do when this endpoint is hit
    def initialize(new_settings, **options, &block)
      self.inheritable_setting = new_settings.point_in_time_copy

      # now +namespace_stackable(:declared_params)+ contains all params defined for
      # this endpoint and its parents, but later it will be cleaned up,
      # see +reset_validations!+ in lib/grape/dsl/validations.rb
      inheritable_setting.route[:declared_params] = inheritable_setting.namespace_stackable[:declared_params].flatten
      inheritable_setting.route[:saved_validations] = inheritable_setting.namespace_stackable[:validations].dup

      inheritable_setting.namespace_stackable[:representations] ||= []
      inheritable_setting.namespace_inheritable[:default_error_status] ||= 500

      @options = options

      @options[:path] = Array(@options[:path])
      @options[:path] << '/' if @options[:path].empty?
      @options[:method] = Array(@options[:method])

      @status = nil
      @stream = nil
      @body = nil
      @source = self.class.block_to_unbound_method(block)
      @before_filter_passed = false
      @endpoints = @options[:app].endpoints if @options[:app].respond_to?(:endpoints)
    end

    # Update our settings from a given set of stackable parameters. Used when
    # the endpoint's API is mounted under another one.
    def inherit_settings(namespace_stackable)
      parent_validations = namespace_stackable[:validations]
      inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?
      parent_declared_params = namespace_stackable[:declared_params]
      inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?

      endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
    end

    def routes
      @routes ||= endpoints&.flat_map(&:routes) || to_routes
    end

    def reset_routes!
      endpoints&.each(&:reset_routes!)
      @namespace = nil
      @routes = nil
    end

    def mount_in(router)
      if endpoints
        compile!
        return endpoints.each { |e| e.mount_in(router) }
      end

      reset_routes!
      compile!
      routes.each do |route|
        router.append(route.apply(self))
        next if inheritable_setting.namespace_inheritable[:do_not_route_head] || route.request_method != Rack::GET

        route.dup.then do |head_route|
          head_route.convert_to_head_request!
          router.append(head_route.apply(self))
        end
      end
    end

    def namespace
      @namespace ||= Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace])
    end

    def call(env)
      dup.call!(env)
    end

    def call!(env)
      env[Grape::Env::API_ENDPOINT] = self
      @env = env
      # this adds the helpers only to the instance
      singleton_class.include(@helpers) if @helpers
      @app.call(env)
    end

    def ==(other)
      other.is_a?(self.class) &&
        options == other.options &&
        inheritable_setting.to_hash == other.inheritable_setting.to_hash
    end
    alias eql? ==

    # The purpose of this override is solely for stripping internals when an error occurs while calling
    # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
    # Otherwise, it calls super.
    def inspect
      return super unless env

      "#{self.class} in '#{route.origin}' endpoint"
    end

    protected

    def run
      ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env:) do
        @request = Grape::Request.new(env, build_params_with: inheritable_setting.namespace_inheritable[:build_params_with])
        begin
          run_filters befores, :before
          @before_filter_passed = true

          if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)
            header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
            raise Grape::Exceptions::MethodNotAllowed.new(header) unless options?

            header 'Allow', header['Allow']
            response_object = ''
            status 204
          else
            run_filters before_validations, :before_validation
            run_validators(request:)
            run_filters after_validations, :after_validation
            response_object = execute
          end

          run_filters afters, :after
          build_response_cookies

          # status verifies body presence when DELETE
          @body ||= response_object

          # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
          response_object = stream || [body]

          [status, header, response_object]
        ensure
          run_filters finallies, :finally
        end
      end
    end

    def execute
      return unless source

      ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self) do
        source.bind_call(self)
      end
    end

    def run_validators(request:)
      validators = inheritable_setting.route[:saved_validations]
      return if validators.empty?

      validation_errors = []

      Grape::Validations::ParamScopeTracker.track do
        ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators:, request:) do
          validators.each do |validator|
            validator.validate(request)
          rescue Grape::Exceptions::Validation => e
            validation_errors << e
            break if validator.fail_fast?
          rescue Grape::Exceptions::ValidationArrayErrors => e
            validation_errors.concat e.errors
            break if validator.fail_fast?
          end
        end
      end

      raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header)) if validation_errors.any?
    end

    def run_filters(filters, type = :other)
      return if filters.blank?

      ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters:, type:) do
        filters.each { |filter| instance_eval(&filter) }
      end
    end

    %i[befores before_validations after_validations afters finallies].each do |method|
      define_method method do
        inheritable_setting.namespace_stackable[method]
      end
    end

    def options?
      options[:options_route_enabled] &&
        env[Rack::REQUEST_METHOD] == Rack::OPTIONS
    end

    private

    attr_reader :before_filter_passed

    def compile!
      @app = options[:app] || build_stack
      @helpers = build_helpers
    end

    def to_routes
      route_options = options[:route_options]
      default_route_options = prepare_default_route_attributes(route_options)
      complete_route_options = route_options.merge(default_route_options)
      path_settings = prepare_default_path_settings

      options[:method].flat_map do |method|
        options[:path].map do |path|
          prepared_path = Path.new(path, default_route_options[:namespace], path_settings)
          pattern = Grape::Router::Pattern.new(
            origin: prepared_path.origin,
            suffix: prepared_path.suffix,
            anchor: default_route_options[:anchor],
            params: route_options[:params],
            format: options[:format],
            version: default_route_options[:version],
            requirements: default_route_options[:requirements]
          )
          Grape::Router::Route.new(self, method, pattern, complete_route_options)
        end
      end
    end

    def prepare_default_route_attributes(route_options)
      {
        namespace:,
        version: prepare_version(inheritable_setting.namespace_inheritable[:version]),
        requirements: prepare_routes_requirements(route_options[:requirements]),
        prefix: inheritable_setting.namespace_inheritable[:root_prefix],
        anchor: route_options.fetch(:anchor, true),
        settings: inheritable_setting.route.except(:declared_params, :saved_validations),
        forward_match: options[:forward_match]
      }
    end

    def prepare_default_path_settings
      namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
      namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
      namespace_stackable_hash.merge!(namespace_inheritable_hash)
    end

    def prepare_routes_requirements(route_options_requirements)
      namespace_requirements = inheritable_setting.namespace_stackable[:namespace].filter_map(&:requirements)
      namespace_requirements << route_options_requirements if route_options_requirements.present?
      namespace_requirements.reduce({}, :merge)
    end

    def prepare_version(namespace_inheritable_version)
      return if namespace_inheritable_version.blank?

      namespace_inheritable_version.length == 1 ? namespace_inheritable_version.first : namespace_inheritable_version
    end

    def build_stack
      stack = Grape::Middleware::Stack.new

      content_types = inheritable_setting.namespace_stackable_with_hash(:content_types)
      format = inheritable_setting.namespace_inheritable[:format]

      stack.use Rack::Head
      stack.use Rack::Lint if lint?
      stack.use Grape::Middleware::Error,
                format:,
                content_types:,
                default_status: inheritable_setting.namespace_inheritable[:default_error_status],
                rescue_all: inheritable_setting.namespace_inheritable[:rescue_all],
                rescue_grape_exceptions: inheritable_setting.namespace_inheritable[:rescue_grape_exceptions],
                default_error_formatter: inheritable_setting.namespace_inheritable[:default_error_formatter],
                error_formatters: inheritable_setting.namespace_stackable_with_hash(:error_formatters),
                rescue_options: inheritable_setting.namespace_stackable_with_hash(:rescue_options),
                rescue_handlers:,
                base_only_rescue_handlers: inheritable_setting.namespace_stackable_with_hash(:base_only_rescue_handlers),
                all_rescue_handler: inheritable_setting.namespace_inheritable[:all_rescue_handler],
                grape_exceptions_rescue_handler: inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler]

      stack.concat inheritable_setting.namespace_stackable[:middleware]

      if inheritable_setting.namespace_inheritable[:version].present?
        stack.use Grape::Middleware::Versioner.using(inheritable_setting.namespace_inheritable[:version_options][:using]),
                  versions: inheritable_setting.namespace_inheritable[:version].flatten,
                  version_options: inheritable_setting.namespace_inheritable[:version_options],
                  prefix: inheritable_setting.namespace_inheritable[:root_prefix],
                  mount_path: inheritable_setting.namespace_stackable[:mount_path].first
      end

      stack.use Grape::Middleware::Formatter,
                format:,
                default_format: inheritable_setting.namespace_inheritable[:default_format] || :txt,
                content_types:,
                formatters: inheritable_setting.namespace_stackable_with_hash(:formatters),
                parsers: inheritable_setting.namespace_stackable_with_hash(:parsers)

      builder = stack.build
      builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
      builder.to_app
    end

    def build_helpers
      helpers = inheritable_setting.namespace_stackable[:helpers]
      return if helpers.empty?

      Module.new { helpers.each { |mod_to_include| include mod_to_include } }
    end

    def build_response_cookies
      response_cookies do |name, value|
        cookie_value = value.is_a?(Hash) ? value : { value: }
        Rack::Utils.set_cookie_header! header, name, cookie_value
      end
    end

    def lint?
      inheritable_setting.namespace_inheritable[:lint] || Grape.config.lint
    end

    def rescue_handlers
      rescue_handlers = inheritable_setting.namespace_reverse_stackable[:rescue_handlers]
      return if rescue_handlers.blank?

      rescue_handlers.each_with_object({}) do |rescue_handler, result|
        result.merge!(rescue_handler) { |_k, s1, _s2| s1 }
      end
    end
  end
end


def mount_in(router)
      if endpoints
        compile!
        return endpoints.each { |e| e.mount_in(router) }
      end

      reset_routes!
      compile!
      routes.each do |route|
        router.append(route.apply(self))
        next if inheritable_setting.namespace_inheritable[:do_not_route_head] || route.request_method != Rack::GET

        route.dup.then do |head_route|
          head_route.convert_to_head_request!
          router.append(head_route.apply(self))
        end
      end
    end


def run_validators(request:)
      validators = inheritable_setting.route[:saved_validations]
      return if validators.empty?

      validation_errors = []

      Grape::Validations::ParamScopeTracker.track do
        ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators:, request:) do
          validators.each do |validator|
            validator.validate(request)
          rescue Grape::Exceptions::Validation => e
            validation_errors << e
            break if validator.fail_fast?
          rescue Grape::Exceptions::ValidationArrayErrors => e
            validation_errors.concat e.errors
            break if validator.fail_fast?
          end
        end
      end

      raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header)) if validation_errors.any?
    end


def to_routes
      route_options = options[:route_options]
      default_route_options = prepare_default_route_attributes(route_options)
      complete_route_options = route_options.merge(default_route_options)
      path_settings = prepare_default_path_settings

      options[:method].flat_map do |method|
        options[:path].map do |path|
          prepared_path = Path.new(path, default_route_options[:namespace], path_settings)
          pattern = Grape::Router::Pattern.new(
            origin: prepared_path.origin,
            suffix: prepared_path.suffix,
            anchor: default_route_options[:anchor],
            params: route_options[:params],
            format: options[:format],
            version: default_route_options[:version],
            requirements: default_route_options[:requirements]
          )
          Grape::Router::Route.new(self, method, pattern, complete_route_options)
        end
      end
    end

    def prepare_default_route_attributes(route_options)
      {
        namespace:,
        version: prepare_version(inheritable_setting.namespace_inheritable[:version]),
        requirements: prepare_routes_requirements(route_options[:requirements]),
        prefix: inheritable_setting.namespace_inheritable[:root_prefix],
        anchor: route_options.fetch(:anchor, true),
        settings: inheritable_setting.route.except(:declared_params, :saved_validations),
        forward_match: options[:forward_match]
      }
    end


def prepare_default_route_attributes(route_options)
      {
        namespace:,
        version: prepare_version(inheritable_setting.namespace_inheritable[:version]),
        requirements: prepare_routes_requirements(route_options[:requirements]),
        prefix: inheritable_setting.namespace_inheritable[:root_prefix],
        anchor: route_options.fetch(:anchor, true),
        settings: inheritable_setting.route.except(:declared_params, :saved_validations),
        forward_match: options[:forward_match]
      }
    end

    def prepare_version(namespace_inheritable_version)
      return if namespace_inheritable_version.blank?

      namespace_inheritable_version.length == 1 ? namespace_inheritable_version.first : namespace_inheritable_version
    end

    def prepare_routes_requirements(route_options_requirements)
      namespace_requirements = inheritable_setting.namespace_stackable[:namespace].filter_map(&:requirements)
      namespace_requirements << route_options_requirements if route_options_requirements.present?
      namespace_requirements.reduce({}, :merge)
    end


def build_stack
      stack = Grape::Middleware::Stack.new

      content_types = inheritable_setting.namespace_stackable_with_hash(:content_types)
      format = inheritable_setting.namespace_inheritable[:format]

      stack.use Rack::Head
      stack.use Rack::Lint if lint?
      stack.use Grape::Middleware::Error,
                format:,
                content_types:,
                default_status: inheritable_setting.namespace_inheritable[:default_error_status],
                rescue_all: inheritable_setting.namespace_inheritable[:rescue_all],
                rescue_grape_exceptions: inheritable_setting.namespace_inheritable[:rescue_grape_exceptions],
                default_error_formatter: inheritable_setting.namespace_inheritable[:default_error_formatter],
                error_formatters: inheritable_setting.namespace_stackable_with_hash(:error_formatters),
                rescue_options: inheritable_setting.namespace_stackable_with_hash(:rescue_options),
                rescue_handlers:,
                base_only_rescue_handlers: inheritable_setting.namespace_stackable_with_hash(:base_only_rescue_handlers),
                all_rescue_handler: inheritable_setting.namespace_inheritable[:all_rescue_handler],
                grape_exceptions_rescue_handler: inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler]

      stack.concat inheritable_setting.namespace_stackable[:middleware]

      if inheritable_setting.namespace_inheritable[:version].present?
        stack.use Grape::Middleware::Versioner.using(inheritable_setting.namespace_inheritable[:version_options][:using]),
                  versions: inheritable_setting.namespace_inheritable[:version].flatten,
                  version_options: inheritable_setting.namespace_inheritable[:version_options],
                  prefix: inheritable_setting.namespace_inheritable[:root_prefix],
                  mount_path: inheritable_setting.namespace_stackable[:mount_path].first
      end

      stack.use Grape::Middleware::Formatter,
                format:,
                default_format: inheritable_setting.namespace_inheritable[:default_format] || :txt,
                content_types:,
                formatters: inheritable_setting.namespace_stackable_with_hash(:formatters),
                parsers: inheritable_setting.namespace_stackable_with_hash(:parsers)

      builder = stack.build
      builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
      builder.to_app
    end

concat

      def concat(other_specs)
        use, not_use = other_specs.partition { |o| o.first == :use }
        others << not_use
        merge_with(use)
      end

using

      def using(strategy)
        raise Grape::Exceptions::InvalidVersionerOption, strategy unless registry.key?(strategy)

        registry[strategy]
      end


# frozen_string_literal: true

module Grape
  # The API class is the primary entry point for creating Grape APIs. Users
  # should subclass this class in order to build an API.
  class API
    # Class methods that we want to call on the API rather than on the API object
    NON_OVERRIDABLE = %i[base= base_instance? call change! configuration compile! inherit_settings recognize_path reset! routes top_level_setting= top_level_setting].freeze

    Helpers = Grape::DSL::Helpers::BaseHelper

    class Boolean
      def self.build(val)
        return nil if val != true && val != false

        new
      end
    end

    class << self
      extend Forwardable

      attr_accessor :base_instance, :instances

      delegate_missing_to :base_instance

      # This is the interface point between Rack and Grape; it accepts a request
      # from Rack and ultimately returns an array of three values: the status,
      # the headers, and the body. See [the rack specification]
      # (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.
      # NOTE: This will only be called on an API directly mounted on RACK
      def_delegators :base_instance, :new, :configuration, :call, :change!, :compile!, :recognize_path, :routes

      # Initialize the instance variables on the remountable class, and the base_instance
      # an instance that will be used to create the set up but will not be mounted
      def initial_setup(base_instance_parent)
        @instances = []
        @setup = []
        @base_parent = base_instance_parent
        @base_instance = mount_instance
      end

      # Redefines all methods so that are forwarded to add_setup and be recorded
      def override_all_methods!
        (base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
          define_singleton_method(method_override) do |*args, **kwargs, &block|
            add_setup(method: method_override, args:, kwargs:, block:)
          end
        end
      end

      # Configure an API from the outside. If a block is given, it'll pass a
      # configuration hash to the block which you can use to configure your
      # API. If no block is given, returns the configuration hash.
      # The configuration set here is accessible from inside an API with
      # `configuration` as normal.
      def configure
        config = @base_instance.configuration
        if block_given?
          yield config
          self
        else
          config
        end
      end

      # The remountable class can have a configuration hash to provide some dynamic class-level variables.
      # For instance, a description could be done using: `desc configuration[:description]` if it may vary
      # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
      # too much, you may actually want to provide a new API rather than remount it.
      def mount_instance(configuration: nil)
        Class.new(@base_parent).tap do |instance|
          instance.configuration = Grape::Util::EndpointConfiguration.new(configuration || {})
          instance.base = self
          replay_setup_on(instance)
        end
      end

      private

      # When inherited, will create a list of all instances (times the API was mounted)
      # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
      def inherited(api)
        super

        api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
        api.override_all_methods!
      end

      # Replays the set up to produce an API as defined in this class, can be called
      # on classes that inherit from Grape::API
      def replay_setup_on(instance)
        @setup.each do |setup_step|
          replay_step_on(instance, **setup_step)
        end
      end

      # Adds a new stage to the set up require to get a Grape::API up and running
      def add_setup(**step)
        @setup << step
        last_response = nil
        @instances.each do |instance|
          last_response = replay_step_on(instance, **step)
        end

        refresh_mount_step if step[:method] != :mount
        last_response
      end

      # Updating all previously mounted classes in the case that new methods have been executed.
      def refresh_mount_step
        @setup.each do |setup_step|
          next if setup_step[:method] != :mount

          refresh_mount_step = setup_step.merge(method: :refresh_mounted_api)
          @setup << refresh_mount_step
          @instances.each do |instance|
            replay_step_on(instance, **refresh_mount_step)
          end
        end
      end

      def replay_step_on(instance, method:, args:, kwargs:, block:)
        return if skip_immediate_run?(instance, args, kwargs)

        eval_args = evaluate_arguments(instance.configuration, *args)
        eval_kwargs = kwargs.deep_transform_values { |v| evaluate_arguments(instance.configuration, v).first }
        response = instance.__send__(method, *eval_args, **eval_kwargs, &block)

        return response if skip_immediate_run?(instance, [response], kwargs)

        evaluate_arguments(instance.configuration, response).first
      end

      # Skips steps that contain arguments to be lazily executed (on re-mount time)
      def skip_immediate_run?(instance, args, kwargs)
        instance.base_instance? &&
          (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) } || any_lazy?(kwargs.values))
      end

      def any_lazy?(args)
        args.any?(Grape::Util::Lazy::Base)
      end

      def evaluate_arguments(configuration, *args)
        args.map do |argument|
          case argument
          when Grape::Util::Lazy::Base
            argument.evaluate_from(configuration)
          when Hash
            argument.transform_values { |value| evaluate_arguments(configuration, value).first }
          when Array
            evaluate_arguments(configuration, *argument)
          else
            argument
          end
        end
      end
    end
  end
end


def evaluate_arguments(configuration, *args)
        args.map do |argument|
          case argument
          when Grape::Util::Lazy::Base
            argument.evaluate_from(configuration)
          when Hash
            argument.transform_values { |value| evaluate_arguments(configuration, value).first }
          when Array
            evaluate_arguments(configuration, *argument)
          else
            argument
          end
        end
      end


# frozen_string_literal: true

module Grape
  class Router
    # Taken from Rails
    #     normalize_path("/foo")  # => "/foo"
    #     normalize_path("/foo/") # => "/foo"
    #     normalize_path("foo")   # => "/foo"
    #     normalize_path("")      # => "/"
    #     normalize_path("/%ab")  # => "/%AB"
    # https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
    def self.normalize_path(path)
      return '/' unless path
      return path if path == '/'

      # Fast path for the overwhelming majority of paths that don't need to be normalized
      return path if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))

      # Slow path
      encoding = path.encoding
      path = "/#{path}"
      path.squeeze!('/')

      unless path == '/'
        path.delete_suffix!('/')
        path.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }
      end

      path.force_encoding(encoding)
    end

    def initialize
      @neutral_map = []
      @neutral_regexes = []
      @map = Hash.new { |hash, key| hash[key] = [] }
      @optimized_map = Hash.new { |hash, key| hash[key] = // }
    end

    def compile!
      return if @compiled

      @union = Regexp.union(@neutral_regexes)
      @neutral_regexes = nil
      (Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
        next unless @map.key?(method)

        routes = @map[method]
        optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
        @optimized_map[method] = Regexp.union(optimized_map)
      end
      @compiled = true
    end

    def append(route)
      @map[route.request_method] << route
    end

    def associate_routes(greedy_route)
      @neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
      @neutral_map << greedy_route
    end

    def call(env)
      with_optimization do
        input = Router.normalize_path(env[Rack::PATH_INFO])
        method = env[Rack::REQUEST_METHOD]
        response, route = identity(input, method, env)
        response || rotation(input, method, env, route)
      end
    end

    def recognize_path(input)
      any = with_optimization { greedy_match?(input) }
      return if any == default_response

      any.endpoint
    end

    DEFAULT_RESPONSE_HEADERS = Grape::Util::Header.new.merge('X-Cascade' => 'pass').freeze
    DEFAULT_RESPONSE_BODY = ['404 Not Found'].freeze

    private

    def identity(input, method, env)
      route = nil
      response = transaction(input, method, env) do
        route = match?(input, method)
        process_route(route, input, env) if route
      end
      [response, route]
    end

    def rotation(input, method, env, exact_route)
      response = nil
      @map[method].each do |route|
        next if exact_route == route
        next unless route.match?(input)

        response = process_route(route, input, env)
        break unless cascade?(response)
      end
      response
    end

    def transaction(input, method, env)
      response = yield
      return response if halt?(response)

      last_response_cascade = !response.nil?
      last_neighbor_route = greedy_match?(input)

      # If last_neighbor_route exists and request method is OPTIONS,
      # return response by using #include_allow_header.
      return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && method == Rack::OPTIONS && last_neighbor_route

      route = match?(input, '*')

      return last_neighbor_route.call(env) if last_neighbor_route && last_response_cascade && route

      if route
        route_response = process_route(route, input, env)
        return route_response if halt?(route_response)

        last_response_cascade = !route_response.nil?
      end

      return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && last_neighbor_route

      nil
    end

    # Returns true if `response` should be returned as-is from the enclosing
    # transaction. Closes the body as a side effect when the response is
    # cascading so callers can safely try the next match.
    def halt?(response)
      return false unless response

      cascade = cascade?(response)
      response[2].close if cascade && response[2].respond_to?(:close)
      !cascade
    end

    def process_route(route, input, env, include_allow_header: false)
      args = env[Grape::Env::GRAPE_ROUTING_ARGS] || { route_info: route }
      route_params = route.params(input)
      env[Grape::Env::GRAPE_ROUTING_ARGS] = route_params.blank? ? args : args.merge(route_params)
      env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header if include_allow_header
      route.call(env)
    end

    def with_optimization
      compile!
      yield || default_response
    end

    def default_response
      [404, DEFAULT_RESPONSE_HEADERS.dup, DEFAULT_RESPONSE_BODY.dup]
    end

    def match?(input, method)
      @optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
    end

    def greedy_match?(input)
      @union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
    end

    def cascade?(response)
      response && response[1]['X-Cascade'] == 'pass'
    end
  end
end


def compile!
      return if @compiled

      @union = Regexp.union(@neutral_regexes)
      @neutral_regexes = nil
      (Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
        next unless @map.key?(method)

        routes = @map[method]
        optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
        @optimized_map[method] = Regexp.union(optimized_map)
      end
      @compiled = true
    end


def rotation(input, method, env, exact_route)
      response = nil
      @map[method].each do |route|
        next if exact_route == route
        next unless route.match?(input)

        response = process_route(route, input, env)
        break unless cascade?(response)
      end
      response
    end

rotation

    def process_route(route, input, env, include_allow_header: false)
      args = env[Grape::Env::GRAPE_ROUTING_ARGS] || { route_info: route }
      route_params = route.params(input)
      env[Grape::Env::GRAPE_ROUTING_ARGS] = route_params.blank? ? args : args.merge(route_params)
      env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header if include_allow_header
      route.call(env)
    end

cascade?

    def cascade?(response)
      response && response[1]['X-Cascade'] == 'pass'
    end


def transaction(input, method, env)
      response = yield
      return response if halt?(response)

      last_response_cascade = !response.nil?
      last_neighbor_route = greedy_match?(input)

      # If last_neighbor_route exists and request method is OPTIONS,
      # return response by using #include_allow_header.
      return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && method == Rack::OPTIONS && last_neighbor_route

      route = match?(input, '*')

      return last_neighbor_route.call(env) if last_neighbor_route && last_response_cascade && route

      if route
        route_response = process_route(route, input, env)
        return route_response if halt?(route_response)

        last_response_cascade = !route_response.nil?
      end

      return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && last_neighbor_route

      nil
    end

transaction

halt?

    def halt?(response)
      return false unless response

      cascade = cascade?(response)
      response[2].close if cascade && response[2].respond_to?(:close)
      !cascade
    end

    def greedy_match?(input)
      @union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
    end

match?

    def match?(input, method)
      @optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
    end


# frozen_string_literal: true

module Grape
  class DeclaredParamsHandler
    def initialize(include_missing: true, evaluate_given: false, stringify: false, contract_key_map: nil)
      @include_missing = include_missing
      @evaluate_given = evaluate_given
      @stringify = stringify
      @contract_key_map = contract_key_map
    end

    def call(passed_params, declared_params, route_params, renamed_params)
      recursive_declared(
        passed_params,
        declared_params:,
        route_params:,
        renamed_params:
      )
    end

    private

    def recursive_declared(passed_params, declared_params:, route_params:, renamed_params:, params_nested_path: [])
      res = if passed_params.is_a?(Array)
              passed_params.map do |passed_param|
                recursive_declared(passed_param, declared_params:, params_nested_path:, renamed_params:, route_params:)
              end
            else
              declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
            end

      @contract_key_map&.each { |key_map| key_map.write(passed_params, res) }

      res
    end

    def declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
      declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
        next if @evaluate_given && !declared_param_attr.scope.attr_meets_dependency?(passed_params)

        declared_hash_attr(
          passed_params,
          declared_param: declared_param_attr.key,
          params_nested_path:,
          memo:,
          renamed_params:,
          route_params:
        )
      end
    end

    def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
      if declared_param.is_a?(Hash)
        declared_param.each_pair do |declared_parent_param, declared_children_params|
          next unless @include_missing || passed_params.key?(declared_parent_param)

          memo_key = build_memo_key(params_nested_path, declared_parent_param, renamed_params)
          passed_children_params = passed_params[declared_parent_param] || passed_params.class.new

          params_nested_path_dup = params_nested_path.dup
          params_nested_path_dup << declared_parent_param.to_s

          memo[memo_key] = handle_passed_param(params_nested_path_dup, route_params:, has_passed_children: passed_children_params.any?) do
            recursive_declared(
              passed_children_params,
              declared_params: declared_children_params,
              params_nested_path: params_nested_path_dup,
              renamed_params:,
              route_params:
            )
          end
        end
      else
        # If it is not a Hash then it does not have children.
        # Find its value or set it to nil.
        return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))

        memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)
        passed_param = passed_params[declared_param]

        params_nested_path_dup = params_nested_path.dup
        params_nested_path_dup << declared_param.to_s

        memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup, route_params:) do
          passed_param
        end
      end
    end

    def build_memo_key(params_nested_path, declared_param, renamed_params)
      rename_path = params_nested_path + [declared_param.to_s]
      renamed_param_name = renamed_params[rename_path]

      param = renamed_param_name || declared_param
      @stringify ? param.to_s : param.to_sym
    end

    def handle_passed_param(params_nested_path, route_params:, has_passed_children: false, &_block)
      return yield if has_passed_children

      key = params_nested_path[0]
      key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1

      type = route_params.dig(key, :type)
      has_children = route_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }

      if type == 'Hash' && !has_children
        {}
      elsif type == 'Array' || (type&.start_with?('[') && !type.include?(','))
        []
      elsif type == 'Set' || type&.start_with?('#<Set', 'Set')
        Set.new
      else
        yield
      end
    end
  end
end


def declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
      declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
        next if @evaluate_given && !declared_param_attr.scope.attr_meets_dependency?(passed_params)

        declared_hash_attr(
          passed_params,
          declared_param: declared_param_attr.key,
          params_nested_path:,
          memo:,
          renamed_params:,
          route_params:
        )
      end
    end

    def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
      if declared_param.is_a?(Hash)
        declared_param.each_pair do |declared_parent_param, declared_children_params|
          next unless @include_missing || passed_params.key?(declared_parent_param)

          memo_key = build_memo_key(params_nested_path, declared_parent_param, renamed_params)
          passed_children_params = passed_params[declared_parent_param] || passed_params.class.new

          params_nested_path_dup = params_nested_path.dup
          params_nested_path_dup << declared_parent_param.to_s

          memo[memo_key] = handle_passed_param(params_nested_path_dup, route_params:, has_passed_children: passed_children_params.any?) do
            recursive_declared(
              passed_children_params,
              declared_params: declared_children_params,
              params_nested_path: params_nested_path_dup,
              renamed_params:,
              route_params:
            )
          end
        end
      else
        # If it is not a Hash then it does not have children.
        # Find its value or set it to nil.
        return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))

        memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)
        passed_param = passed_params[declared_param]

        params_nested_path_dup = params_nested_path.dup
        params_nested_path_dup << declared_param.to_s

        memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup, route_params:) do
          passed_param
        end
      end
    end


def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
      if declared_param.is_a?(Hash)
        declared_param.each_pair do |declared_parent_param, declared_children_params|
          next unless @include_missing || passed_params.key?(declared_parent_param)

          memo_key = build_memo_key(params_nested_path, declared_parent_param, renamed_params)
          passed_children_params = passed_params[declared_parent_param] || passed_params.class.new

          params_nested_path_dup = params_nested_path.dup
          params_nested_path_dup << declared_parent_param.to_s

          memo[memo_key] = handle_passed_param(params_nested_path_dup, route_params:, has_passed_children: passed_children_params.any?) do
            recursive_declared(
              passed_children_params,
              declared_params: declared_children_params,
              params_nested_path: params_nested_path_dup,
              renamed_params:,
              route_params:
            )
          end
        end
      else
        # If it is not a Hash then it does not have children.
        # Find its value or set it to nil.
        return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))

        memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)
        passed_param = passed_params[declared_param]

        params_nested_path_dup = params_nested_path.dup
        params_nested_path_dup << declared_param.to_s

        memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup, route_params:) do
          passed_param
        end
      end
    end