# frozen_string_literal: true

module Grape
  module Util
    # A branchable, inheritable settings object which can store both stackable
    # and inheritable values (see InheritableValues and StackableValues).
    class InheritableSetting
      attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent, :point_in_time_copies

      # Retrieve global settings.
      def self.global
        @global ||= {}
      end

      # Clear all global settings.
      # @api private
      # @note only for testing
      def self.reset_global!
        @global = {}
      end

      # Instantiate a new settings instance, with blank values. The fresh
      # instance can then be set to inherit from an existing instance (see
      # #inherit_from).
      def initialize
        self.route = {}
        self.api_class = {}
        self.namespace = InheritableValues.new # only inheritable from a parent when
        # used with a mount, or should every API::Class be a separate namespace by default?
        self.namespace_inheritable = InheritableValues.new
        self.namespace_stackable = StackableValues.new
        self.namespace_reverse_stackable = ReverseStackableValues.new

        self.point_in_time_copies = []

        self.parent = nil
      end

      # Return the class-level global properties.
      def global
        self.class.global
      end

      # Set our inherited values to the given parent's current values. Also,
      # update the inherited values on any settings instances which were forked
      # from us.
      # @param parent [InheritableSetting]
      def inherit_from(parent)
        return if parent.nil?

        self.parent = parent

        namespace_inheritable.inherited_values = parent.namespace_inheritable
        namespace_stackable.inherited_values = parent.namespace_stackable
        namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable
        self.route = parent.route.merge(route)

        point_in_time_copies.each { |cloned_one| cloned_one.inherit_from parent }
      end

      # Create a point-in-time copy of this settings instance, with clones of
      # all our values. Note that, should this instance's parent be set or
      # changed via #inherit_from, it will copy that inheritence to any copies
      # which were made.
      def point_in_time_copy
        self.class.new.tap do |new_setting|
          point_in_time_copies << new_setting
          new_setting.point_in_time_copies = []

          new_setting.namespace = namespace.clone
          new_setting.namespace_inheritable = namespace_inheritable.clone
          new_setting.namespace_stackable = namespace_stackable.clone
          new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone
          new_setting.route = route.clone
          new_setting.api_class = api_class

          new_setting.inherit_from(parent)
        end
      end

      # Resets the instance store of per-route settings.
      # @api private
      def route_end
        @route = {}
      end

      # Return a serializable hash of our values.
      def to_hash
        {
          global: global.clone,
          route: route.clone,
          namespace: namespace.to_hash,
          namespace_inheritable: namespace_inheritable.to_hash,
          namespace_stackable: namespace_stackable.to_hash,
          namespace_reverse_stackable: namespace_reverse_stackable.to_hash
        }
      end

      def namespace_stackable_with_hash(key)
        data = namespace_stackable[key]
        return if data.blank?

        data.each_with_object({}) { |value, result| result.deep_merge!(value) }
      end
    end
  end
end


def point_in_time_copy
        self.class.new.tap do |new_setting|
          point_in_time_copies << new_setting
          new_setting.point_in_time_copies = []

          new_setting.namespace = namespace.clone
          new_setting.namespace_inheritable = namespace_inheritable.clone
          new_setting.namespace_stackable = namespace_stackable.clone
          new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone
          new_setting.route = route.clone
          new_setting.api_class = api_class

          new_setting.inherit_from(parent)
        end
      end


def to_hash
        {
          global: global.clone,
          route: route.clone,
          namespace: namespace.to_hash,
          namespace_inheritable: namespace_inheritable.to_hash,
          namespace_stackable: namespace_stackable.to_hash,
          namespace_reverse_stackable: namespace_reverse_stackable.to_hash
        }
      end


# frozen_string_literal: true

module Grape
  module Util
    module DeepFreeze
      module_function

      # Recursively freezes Hash (keys and values), Array (elements), and String
      # objects. All other types are returned as-is.
      #
      # Intentionally left unfrozen:
      #   - Procs / lambdas — may be deferred DB-backed callables
      #   - Coercers (e.g. ArrayCoercer) — use lazy ivar memoization at request time
      #   - Classes / Modules — shared constants that must remain open
      #   - ParamsScope — self-freezes at the end of its own initialize
      def deep_freeze(obj)
        case obj
        when Hash
          obj.each do |k, v|
            deep_freeze(k)
            deep_freeze(v)
          end
          obj.freeze
        when Array
          obj.each { |v| deep_freeze(v) }
          obj.freeze
        when String
          obj.freeze
        else
          obj
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Util
    # Base for classes which need to operate with own values kept
    # in the hash and inherited values kept in a Hash-like object.
    class BaseInheritable
      attr_accessor :inherited_values, :new_values

      # @param inherited_values [Object] An object implementing an interface
      #   of the Hash class.
      def initialize(inherited_values = nil)
        @inherited_values = inherited_values || {}
        @new_values = {}
      end

      def delete(*keys)
        keys.map do |key|
          # since delete returns the deleted value, seems natural to `map` the result
          new_values.delete key
        end
      end

      def initialize_copy(other)
        super
        self.inherited_values = other.inherited_values
        self.new_values = other.new_values.dup
      end

      def keys
        if new_values.any?
          inherited_values.keys.tap do |combined|
            combined.concat(new_values.keys)
            combined.uniq!
          end
        else
          inherited_values.keys
        end
      end

      def key?(name)
        inherited_values.key?(name) || new_values.key?(name)
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  class Router
    class Pattern
      extend Forwardable

      DEFAULT_CAPTURES = %w[format version].freeze

      attr_reader :origin, :path, :pattern, :to_regexp

      def_delegators :pattern, :params
      def_delegators :to_regexp, :===
      alias match? ===

      def initialize(origin:, suffix:, anchor:, params:, format:, version:, requirements:)
        @origin = origin
        @path = PatternCache[[build_path_from_pattern(@origin, anchor), suffix]]
        @pattern = Mustermann::Grape.new(@path, uri_decode: true, params:, capture: extract_capture(format, version, requirements))
        @to_regexp = @pattern.to_regexp
      end

      def captures_default
        to_regexp.names
                 .delete_if { |n| DEFAULT_CAPTURES.include?(n) }
                 .to_h { |k| [k, ''] }
      end

      private

      def extract_capture(format, version, requirements)
        capture = {}
        capture[:format] = map_str(format) if format.present?
        capture[:version] = map_str(version) if version.present?

        return capture if requirements.blank?

        requirements.merge(capture)
      end

      def build_path_from_pattern(pattern, anchor)
        if pattern.end_with?('*path')
          pattern.dup.insert(pattern.rindex('/') + 1, '?')
        elsif anchor
          pattern
        elsif pattern.end_with?('/')
          "#{pattern}?*path"
        else
          "#{pattern}/?*path"
        end
      end

      def map_str(value)
        Array.wrap(value).map(&:to_s)
      end

      class PatternCache < Grape::Util::Cache
        def initialize
          super
          @cache = Hash.new do |h, (pattern, suffix)|
            h[[pattern, suffix]] = -"#{pattern}#{suffix}"
          end
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Validators
      class ValuesValidator < Base
        default_message_key :values

        def initialize(attrs, options, required, scope, opts)
          super
          values = option_value

          # Zero-arity procs return a collection per-request (e.g. DB-backed lists).
          # Non-zero-arity procs are per-element predicates, called directly at validation time.
          # Non-Proc values are wrapped in a zero-arity lambda for a uniform call interface.
          if values.is_a?(Proc)
            @values_call = values
            @values_is_predicate = !values.arity.zero?
          else
            @values_call = -> { values }
            @values_is_predicate = false
          end
        end

        def validate_param!(attr_name, params)
          return unless hash_like?(params)

          val = scrub(params[attr_name])

          return if val.nil? && !required_for_root_scope?
          return if val != false && val.blank? && @allow_blank
          return if check_values?(val, attr_name)

          validation_error!(attr_name)
        end

        private

        def check_values?(val, attr_name)
          param_array = val.nil? ? [nil] : Array.wrap(val)

          if @values_is_predicate
            begin
              param_array.all? { |param| @values_call.call(param) }
            rescue StandardError => e
              warn "Error '#{e}' raised while validating attribute '#{attr_name}'"
              false
            end
          else
            values = @values_call.call
            return true if values.nil?

            param_array.all? { |param| values.include?(param) }
          end
        end

        def required_for_root_scope?
          return false unless @required

          scope = @scope
          scope = scope.parent while scope.lateral?

          scope.root?
        end
      end
    end
  end
end


def check_values?(val, attr_name)
          param_array = val.nil? ? [nil] : Array.wrap(val)

          if @values_is_predicate
            begin
              param_array.all? { |param| @values_call.call(param) }
            rescue StandardError => e
              warn "Error '#{e}' raised while validating attribute '#{attr_name}'"
              false
            end
          else
            values = @values_call.call
            return true if values.nil?

            param_array.all? { |param| values.include?(param) }
          end
        end


# frozen_string_literal: true

module Grape
  module Validations
    module Validators
      class MultipleParamsBase < Base
        def validate!(params)
          attributes = MultipleAttributesIterator.new(@attrs, @scope, params)
          array_errors = []

          attributes.each do |resource_params|
            validate_params!(resource_params)
          rescue Grape::Exceptions::Validation => e
            array_errors << e
          end

          raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?
        end

        private

        def keys_in_common(resource_params, known_keys = all_keys)
          return [] unless hash_like?(resource_params)

          known_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) }
        end

        def all_keys
          @attrs.map { |attr| @scope.full_name(attr) }
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Validators
      class CoerceValidator < Base
        default_message_key :coerce

        def initialize(attrs, options, required, scope, opts)
          super

          raw_type = @options[:type]
          type = hash_like?(raw_type) ? raw_type[:value] : raw_type
          @converter =
            if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
              type
            else
              Types.build_coercer(type, method: @options[:method])
            end
        end

        def validate_param!(attr_name, params)
          validation_error!(attr_name) unless hash_like?(params)

          new_value = coerce_value(params[attr_name])

          validation_error!(attr_name, new_value.message || @exception_message) if new_value.is_a?(Types::InvalidValue)

          # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash
          # which looses wrappers for hashes and arrays after reassigning values
          #
          #     h = Hashie::Mash.new(list: [1, 2, 3, 4])
          #     => #<Hashie::Mash list=#<Hashie::Array [1, 2, 3, 4]>>
          #     list = h.list
          #     h[:list] = list
          #     h
          #     => #<Hashie::Mash list=[1, 2, 3, 4]>
          return if params[attr_name].instance_of?(new_value.class) && params[attr_name] == new_value

          params[attr_name] = new_value
        end

        private

        # Calls the converter built at definition time.
        # Custom coercers may raise; any StandardError is treated as an invalid value.
        def coerce_value(val)
          @converter.call(val)
        rescue StandardError
          Types::InvalidValue.new
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Validators
      class LengthValidator < Base
        def initialize(attrs, options, required, scope, opts)
          super

          @min, @max, @is = @options.values_at(:min, :max, :is)
          validate_boundary!(:min, @min)
          validate_boundary!(:max, @max)
          raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if @min && @max && @min > @max

          return if @is.nil?
          raise ArgumentError, 'is must be an integer greater than zero' unless @is.is_a?(Integer) && @is.positive?
          raise ArgumentError, 'is cannot be combined with min or max' unless @min.nil? && @max.nil?
        end

        def validate_param!(attr_name, params)
          param = params[attr_name]

          return unless param.respond_to?(:length)

          return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)

          validation_error!(attr_name, message do
            if @min && @max
              translate(:length, min: @min, max: @max)
            elsif @min
              translate(:length_min, min: @min)
            elsif @max
              translate(:length_max, max: @max)
            else
              translate(:length_is, is: @is)
            end
          end)
        end

        private

        def validate_boundary!(name, val)
          raise ArgumentError, "#{name} must be an integer greater than or equal to zero" if !val.nil? && (!val.is_a?(Integer) || val.negative?)
        end
      end
    end
  end
end


def validate_param!(attr_name, params)
          param = params[attr_name]

          return unless param.respond_to?(:length)

          return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)

          validation_error!(attr_name, message do
            if @min && @max
              translate(:length, min: @min, max: @max)
            elsif @min
              translate(:length_min, min: @min)
            elsif @max
              translate(:length_max, max: @max)
            else
              translate(:length_is, is: @is)
            end
          end)
        end


# frozen_string_literal: true

module Grape
  module Validations
    module Validators
      # Base class for all parameter validators.
      #
      # == Freeze contract
      # Validator instances are shared across requests and are frozen after
      # initialization (via +.new+). All inputs (+options+, +opts+, +attrs+)
      # arrive pre-frozen from the DSL boundary, so subclass ivars derived
      # from them are frozen by construction. Lazy ivar assignment
      # (e.g. +memoize+, <tt>||=</tt>) will raise +FrozenError+ at request time.
      class Base
        include Grape::Util::Translation

        attr_reader :attrs

        class << self
          # Declares the default I18n message key used by +validation_error!+.
          # Subclasses that only need a single fixed error message can declare it
          # at the class level instead of overriding +initialize+:
          #
          #   class MyValidator < Grape::Validations::Validators::Base
          #     default_message_key :my_error
          #   end
          #
          # The key is resolved through +message+, so a per-option +:message+
          # override still takes precedence.
          def default_message_key(key = nil)
            if key
              @default_message_key = key
            else
              @default_message_key || (superclass.respond_to?(:default_message_key) ? superclass.default_message_key : nil)
            end
          end

          def new(...)
            super.freeze
          end

          def inherited(klass)
            super
            Validations.register(klass)
          end
        end

        # Creates a new Validator from options specified
        # by a +requires+ or +optional+ directive during
        # parameter definition.
        # @param attrs [Array] names of attributes to which the Validator applies
        # @param options [Object] implementation-dependent Validator options; deep-frozen on assignment
        # @param required [Boolean] attribute(s) are required or optional
        # @param scope [ParamsScope] parent scope for this Validator
        # @param opts [Hash] additional validation options
        def initialize(attrs, options, required, scope, opts)
          @attrs = Array(attrs).freeze
          @options = Grape::Util::DeepFreeze.deep_freeze(options)
          @option = @options # TODO: remove in next major release
          @required = required
          @scope = scope
          @fail_fast, @allow_blank = opts.values_at(:fail_fast, :allow_blank)
          @exception_message = message(self.class.default_message_key) if self.class.default_message_key
        end

        # Validates a given request.
        # @note Override #validate! unless you need to access the entire request.
        # @param request [Grape::Request] the request currently being handled
        # @raise [Grape::Exceptions::Validation] if validation failed
        # @return [void]
        def validate(request)
          return unless @scope.should_validate?(request.params)

          validate!(request.params)
        end

        def fail_fast?
          @fail_fast
        end

        # Validates a given parameter hash.
        # @note Override #validate_param! for per-parameter validation,
        #   or #validate if you need access to the entire request.
        # @param params [Hash] parameters to validate
        # @raise [Grape::Exceptions::Validation] if validation failed
        # @return [void]
        def validate!(params)
          attributes = SingleAttributeIterator.new(@attrs, @scope, params)
          # we collect errors inside array because
          # there may be more than one error per field
          array_errors = []

          attributes.each do |val, attr_name, empty_val|
            next if !@scope.required? && empty_val
            next unless @scope.meets_dependency?(val, params)

            validate_param!(attr_name, val) if @required || (hash_like?(val) && val.key?(attr_name))
          rescue Grape::Exceptions::Validation => e
            array_errors << e
          end

          raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?
        end

        protected

        # Validates a single attribute. Override in subclasses.
        # @param attr_name [Symbol, String] the attribute name
        # @param params [Hash] the parameter hash containing the attribute
        # @raise [Grape::Exceptions::Validation] if validation failed
        # @return [void]
        def validate_param!(attr_name, params)
          raise NotImplementedError
        end

        private

        def validation_error!(attr_name_or_params, message = @exception_message)
          params = attr_name_or_params.is_a?(Array) ? attr_name_or_params : @scope.full_name(attr_name_or_params)
          raise Grape::Exceptions::Validation.new(params:, message:)
        end

        def hash_like?(obj)
          obj.respond_to?(:key?)
        end

        def options_key?(key, options = nil)
          current_options = options || @options
          hash_like?(current_options) && current_options.key?(key) && !current_options[key].nil?
        end

        # Returns the effective message for a validation error.
        # Prefers an explicit +:message+ option, then +default_key+.
        # If both are nil, the block (if given) is called to compute a fallback —
        # useful for validators that build a message Hash for deferred i18n interpolation.
        # @example
        #   @exception_message = message(:presence)             # symbol key or custom message
        #   @exception_message = message { build_hash_message } # computed fallback
        def message(default_key = nil)
          key = options_key?(:message) ? @options[:message] : default_key
          return key unless key.nil?

          yield if block_given?
        end

        def option_value
          options_key?(:value) ? @options[:value] : @options
        end

        def scrub(value)
          return value unless value.respond_to?(:valid_encoding?) && !value.valid_encoding?

          value.scrub
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Validators
      class DefaultValidator < Base
        def initialize(attrs, options, required, scope, opts)
          super
          # !important, lazy call at runtime
          @default_call =
            if @options.is_a?(Proc)
              @options.arity.zero? ? proc { @options.call } : @options
            elsif @options.duplicable?
              proc { @options.dup }
            else
              proc { @options }
            end
        end

        def validate!(params)
          attrs = SingleAttributeIterator.new(@attrs, @scope, params)
          attrs.each do |resource_params, attr_name|
            next unless @scope.meets_dependency?(resource_params, params)

            resource_params[attr_name] = @default_call.call(resource_params) if hash_like?(resource_params) && resource_params[attr_name].nil?
          end
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Types
      # Coerces the given value to a type defined via a +type+ argument during
      # initialization. When +strict+ is true, it doesn't coerce a value but check
      # that it has the proper type.
      class PrimitiveCoercer < DryTypeCoercer
        def initialize(type, strict: false)
          super

          @coercer = cache_coercer[type]
        end

        def call(val)
          return InvalidValue.new if reject?(val)
          return nil if val.nil? || treat_as_nil?(val)

          super
        end

        protected

        attr_reader :type

        # This method maintains logic which was defined by Virtus. For example,
        # dry-types is ok to convert an array or a hash to a string, it is supported,
        # but Virtus wouldn't accept it. So, this method only exists to not introduce
        # breaking changes.
        def reject?(val)
          case val
          when Array, Hash
            type == String
          when String
            type == Hash
          else
            false
          end
        end

        # Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
        # absence of a value and coerces it into nil. See a discussion there
        # https://github.com/ruby-grape/grape/pull/2045
        def treat_as_nil?(val)
          val == '' && type != String
        end
      end
    end
  end
end

class PrimitiveCoercer < < DryTypeCoercer


# frozen_string_literal: true

module Grape
  module Validations
    module Types
      # This class is intended for use with Grape endpoint parameters that
      # have been declared to be of variant-type using the +:types+ option.
      # +MultipleTypeCoercer+ will build a coercer for each type declared
      # in the array passed to +:types+ using {Types.build_coercer}. It will
      # apply these coercers to parameter values in the order given to
      # +:types+, and will return the value returned by the first coercer
      # to successfully coerce the parameter value. Therefore if +String+ is
      # an allowed type it should be declared last, since it will always
      # successfully "coerce" the value.
      class MultipleTypeCoercer
        # Construct a new coercer that will attempt to coerce
        # values to the given list of types in the given order.
        #
        # @param types [Array<Class>] list of allowed types
        # @param method [#call,#parse] method by which values should be
        #   coerced. See class docs for default behaviour.
        def initialize(types, method = nil)
          @method = method.respond_to?(:parse) ? method.method(:parse) : method

          @type_coercers = types.map do |type|
            if Types.multiple? type
              VariantCollectionCoercer.new type, @method
            else
              Types.build_coercer type, strict: !@method.nil?
            end
          end
        end

        # Coerces the given value.
        #
        # @param val [String] value to be coerced, in grape
        #   this should always be a string.
        # @return [Object,InvalidValue] the coerced result, or an instance
        #   of {InvalidValue} if the value could not be coerced.
        def call(val)
          # once the value is coerced by the custom method, its type should be checked
          val = @method.call(val) if @method

          coerced_val = InvalidValue.new

          @type_coercers.each do |coercer|
            coerced_val = coercer.call(val)

            return coerced_val unless coerced_val.is_a?(InvalidValue)
          end

          coerced_val
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Types
      # A base class for classes which must identify a coercer to be used.
      # If the +strict+ argument is true, it won't coerce the given value
      # but check its type. More information there
      # https://dry-rb.org/gems/dry-types/main/built-in-types/
      class DryTypeCoercer
        class << self
          # Returns a collection coercer which corresponds to a given type.
          # Example:
          #
          #    collection_coercer_for(Array)
          #    #=> Grape::Validations::Types::ArrayCoercer
          def collection_coercer_for(type)
            case type
            when Array
              ArrayCoercer
            when Set
              SetCoercer
            else
              raise ArgumentError, "Unknown type: #{type}"
            end
          end

          # Returns an instance of a coercer for a given type
          def coercer_instance_for(type, strict: false)
            klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)
            klass.new(type, strict:)
          end
        end

        def initialize(type, strict: false)
          @type = type
          @strict = strict
          @cache_coercer = strict ? DryTypes::StrictCache : DryTypes::ParamsCache
        end

        # Coerces the given value to a type which was specified during
        # initialization as a type argument.
        #
        # @param val [Object]
        def call(val)
          return if val.nil?

          @coercer[val]
        rescue Dry::Types::CoercionError
          InvalidValue.new
        end

        protected

        attr_reader :type, :strict, :cache_coercer
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Types
      # This class will detect type classes that implement
      # a class-level +parse+ method. The method should accept one
      # +String+ argument and should return the value coerced to
      # the appropriate type. The method may raise an exception if
      # there are any problems parsing the string.
      #
      # Alternately an optional +method+ may be supplied (see the
      # +coerce_with+ option of {Grape::Dsl::Parameters#requires}).
      # This may be any class or object implementing +parse+ or +call+,
      # with the same contract as described above.
      #
      # Type Checking
      # -------------
      #
      # Calls to +coerced?+ will consult this class to check
      # that the coerced value produced above is in fact of the
      # expected type. By default this class performs a basic check
      # against the type supplied, but this behaviour will be
      # overridden if the class implements a class-level
      # +coerced?+ or +parsed?+ method. This method
      # will receive a single parameter that is the coerced value
      # and should return +true+ if the value meets type expectations.
      # Arbitrary assertions may be made here but the grape validation
      # system should be preferred.
      #
      # Alternately a proc or other object responding to +call+ may be
      # supplied in place of a type. This should implement the same
      # contract as +coerced?+, and must be supplied with a coercion
      # +method+.
      class CustomTypeCoercer
        # A new coercer for the given type specification
        # and coercion method.
        #
        # @param type [Class,#coerced?,#parsed?,#call?]
        #   specifier for the target type. See class docs.
        # @param method [#parse,#call]
        #   optional coercion method. See class docs.
        def initialize(type, method = nil)
          coercion_method = infer_coercion_method type, method
          @method = enforce_symbolized_keys type, coercion_method
          @type_check = infer_type_check(type)
        end

        # Coerces the given value.
        #
        # @param value [String] value to be coerced, in grape
        #   this should always be a string.
        # @return [Object] the coerced result
        def call(val)
          coerced_val = @method.call(val)

          return coerced_val if coerced_val.is_a?(InvalidValue)
          return InvalidValue.new unless coerced?(coerced_val)

          coerced_val
        end

        def coerced?(val)
          val.nil? || @type_check.call(val)
        end

        private

        # Determine the coercion method we're expected to use
        # based on the parameters given.
        #
        # @param type see #new
        # @param method see #new
        # @return [#call] coercion method
        def infer_coercion_method(type, method)
          if method
            if method.respond_to? :parse
              method.method :parse
            else
              method
            end
          else
            # Try to use parse() declared on the target type.
            # This may raise an exception, but we are out of ideas anyway.
            type.method :parse
          end
        end

        # Determine how the type validity of a coerced
        # value should be decided.
        #
        # @param type see #new
        # @return [#call] a procedure which accepts a single parameter
        #   and returns +true+ if the passed object is of the correct type.
        def infer_type_check(type)
          # First check for special class methods
          if type.respond_to? :coerced?
            type.method :coerced?
          elsif type.respond_to? :parsed?
            type.method :parsed?
          elsif type.respond_to? :call
            # Arbitrary proc passed for type validation.
            # Note that this will fail unless a method is also
            # passed, or if the type also implements a parse() method.
            type
          elsif type.is_a?(Enumerable)
            lambda do |value|
              value.is_a?(Enumerable) && value.all? do |val|
                recursive_type_check(type.first, val)
              end
            end
          else
            # By default, do a simple type check
            ->(value) { value.is_a? type }
          end
        end

        def recursive_type_check(type, value)
          if type.is_a?(Enumerable) && value.is_a?(Enumerable)
            value.all? { |val| recursive_type_check(type.first, val) }
          else
            !type.is_a?(Enumerable) && value.is_a?(type)
          end
        end

        # Enforce symbolized keys for complex types
        # by wrapping the coercion method such that
        # any Hash objects in the immediate heirarchy
        # have their keys recursively symbolized.
        # This helps common libs such as JSON to work easily.
        #
        # @param type see #new
        # @param method see #infer_coercion_method
        # @return [#call] +method+ wrapped in an additional
        #   key-conversion step, or just returns +method+
        #   itself if no conversion is deemed to be
        #   necessary.
        def enforce_symbolized_keys(type, method)
          # Collections have all values processed individually
          if [Array, Set].include?(type)
            lambda do |val|
              method.call(val).tap do |new_val|
                new_val.map do |item|
                  item.is_a?(Hash) ? item.deep_symbolize_keys : item
                end
              end
            end

          # Hash objects are processed directly
          elsif type == Hash
            lambda do |val|
              method.call(val).deep_symbolize_keys
            end

          # Simple types are not processed.
          # This includes Array<primitive> types.
          else
            method
          end
        end
      end
    end
  end
end


def infer_type_check(type)
          # First check for special class methods
          if type.respond_to? :coerced?
            type.method :coerced?
          elsif type.respond_to? :parsed?
            type.method :parsed?
          elsif type.respond_to? :call
            # Arbitrary proc passed for type validation.
            # Note that this will fail unless a method is also
            # passed, or if the type also implements a parse() method.
            type
          elsif type.is_a?(Enumerable)
            lambda do |value|
              value.is_a?(Enumerable) && value.all? do |val|
                recursive_type_check(type.first, val)
              end
            end
          else
            # By default, do a simple type check
            ->(value) { value.is_a? type }
          end
        end


def enforce_symbolized_keys(type, method)
          # Collections have all values processed individually
          if [Array, Set].include?(type)
            lambda do |val|
              method.call(val).tap do |new_val|
                new_val.map do |item|
                  item.is_a?(Hash) ? item.deep_symbolize_keys : item
                end
              end
            end

          # Hash objects are processed directly
          elsif type == Hash
            lambda do |val|
              method.call(val).deep_symbolize_keys
            end

          # Simple types are not processed.
          # This includes Array<primitive> types.
          else
            method
          end
        end


# frozen_string_literal: true

module Grape
  module Validations
    module Types
      # This class wraps {MultipleTypeCoercer}, for use with collections
      # that allow members of more than one type.
      class VariantCollectionCoercer
        # Construct a new coercer that will attempt to coerce
        # a list of values such that all members are of one of
        # the given types. The container may also optionally be
        # coerced to a +Set+. An arbitrary coercion +method+ may
        # be supplied, which will be passed the entire collection
        # as a parameter and should return a new collection, or
        # may return the same one if no coercion was required.
        #
        # @param types [Array<Class>,Set<Class>] list of allowed types,
        #   also specifying the container type
        # @param method [#call,#parse] method by which values should be coerced
        def initialize(types, method = nil)
          @types = types
          @method = method.respond_to?(:parse) ? method.method(:parse) : method

          # If we have a coercion method, pass it in here to save
          # building another one, even though we call it directly.
          @member_coercer = MultipleTypeCoercer.new types, method
        end

        # Coerce the given value.
        #
        # @param value [Array<String>] collection of values to be coerced
        # @return [Array<Object>,Set<Object>,InvalidValue]
        #   the coerced result, or an instance
        #   of {InvalidValue} if the value could not be coerced.
        def call(value)
          return unless value.is_a? Array

          value =
            if @method
              @method.call(value)
            else
              value.map { |v| @member_coercer.call(v) }
            end
          return Set.new value if @types.is_a? Set

          value
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Validations
    module Types
      # Coerces elements in an array. It might be an array of strings or integers or
      # an array of arrays of integers.
      #
      # It could've been possible to use an +of+
      # method (https://dry-rb.org/gems/dry-types/main/array-with-member/)
      # provided by dry-types. Unfortunately, it doesn't work for Grape because of
      # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
      # maintains Virtus behavior in coercing.
      class ArrayCoercer < DryTypeCoercer
        def initialize(type, strict: false)
          super
          @coercer = strict ? DryTypes::Strict::Array : DryTypes::Params::Array
          @subtype = type.first
        end

        def call(_val)
          collection = super
          return collection if collection.is_a?(InvalidValue)

          coerce_elements collection
        end

        protected

        attr_reader :subtype

        def coerce_elements(collection)
          return if collection.nil?

          collection.each_with_index do |elem, index|
            return InvalidValue.new if reject?(elem)

            coerced_elem = elem_coercer.call(elem)

            return coerced_elem if coerced_elem.is_a?(InvalidValue)

            collection[index] = coerced_elem
          end

          collection
        end

        # This method maintains logic which was defined by Virtus for arrays.
        # Virtus doesn't allow nil in arrays.
        def reject?(val)
          val.nil?
        end

        def elem_coercer
          @elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict:)
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Middleware
    module Versioner
      class Base < Grape::Middleware::Base
        include Grape::Middleware::PrecomputedContentTypes

        DEFAULT_OPTIONS = {
          pattern: /.*/i,
          prefix: nil,
          mount_path: nil,
          version_options: {
            strict: false,
            cascade: true,
            parameter: 'apiver',
            vendor: nil
          }.freeze
        }.freeze

        CASCADE_PASS_HEADER = { 'X-Cascade' => 'pass' }.freeze

        DEFAULT_OPTIONS.each_key do |key|
          define_method key do
            options[key]
          end
        end

        DEFAULT_OPTIONS[:version_options].each_key do |key|
          define_method key do
            options[:version_options][key]
          end
        end

        def self.inherited(klass)
          super
          Versioner.register(klass)
        end

        attr_reader :error_headers, :versions, :available_media_types

        def initialize(app, **options)
          super
          @error_headers = cascade ? CASCADE_PASS_HEADER : {}
          @versions = options[:versions]&.map(&:to_s) # making sure versions are strings to ease potential match
          @available_media_types = build_available_media_types
        end

        def potential_version_match?(potential_version)
          versions.blank? || versions.include?(potential_version)
        end

        def version_not_found!
          throw :error, status: 404, message: '404 API Version Not Found', headers: CASCADE_PASS_HEADER
        end

        private

        def build_available_media_types
          media_types = []
          base_media_type = "application/vnd.#{vendor}"
          content_types.each_key do |extension|
            versions&.reverse_each do |version|
              media_types << "#{base_media_type}-#{version}+#{extension}"
              media_types << "#{base_media_type}-#{version}"
            end
            media_types << "#{base_media_type}+#{extension}"
          end

          media_types << base_media_type
          media_types.concat(content_types.values.flatten)
          media_types
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Middleware
    module Versioner
      # This middleware sets various version related rack environment variables
      # based on the uri path and removes the version substring from the uri
      # path. If the version substring does not match any potential initialized
      # versions, a 404 error is thrown.
      #
      # Example: For a uri path
      #   /v1/resource
      #
      # The following rack env variables are set and path is rewritten to
      # '/resource':
      #
      #   env['api.version'] => 'v1'
      #
      class Path < Base
        def initialize(app, **options)
          super
          @prefixes = [mount_path, Grape::Router.normalize_path(prefix)].select { |p| p.present? && p != '/' }.freeze
        end

        def before
          path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
          return if path_info == '/'

          path_info = @prefixes.reduce(path_info) do |pi, path|
            pi.start_with?(path) ? pi.delete_prefix(path) : pi
          end

          slash_position = path_info.index('/', 1) # omit the first one
          return unless slash_position

          potential_version = path_info[1..(slash_position - 1)]
          return unless potential_version.match?(pattern)

          version_not_found! unless potential_version_match?(potential_version)
          env[Grape::Env::API_VERSION] = potential_version
        end
      end
    end
  end
end


# frozen_string_literal: true

module Grape
  module Middleware
    module Versioner
      # This middleware sets various version related rack environment variables
      # based on the HTTP Accept header with the pattern:
      # application/vnd.:vendor-:version+:format
      #
      # Example: For request header
      #    Accept: application/vnd.mycompany.a-cool-resource-v1+json
      #
      # The following rack env variables are set:
      #
      #    env['api.type']    => 'application'
      #    env['api.subtype'] => 'vnd.mycompany.a-cool-resource-v1+json'
      #    env['api.vendor]   => 'mycompany.a-cool-resource'
      #    env['api.version]  => 'v1'
      #    env['api.format]   => 'json'
      #
      # If version does not match this route, then a 406 is raised with
      # X-Cascade header to alert Grape::Router to attempt the next matched
      # route.
      class Header < Base
        def before
          match_best_quality_media_type! do |media_type|
            env.update(
              Grape::Env::API_TYPE => media_type.type,
              Grape::Env::API_SUBTYPE => media_type.subtype,
              Grape::Env::API_VENDOR => media_type.vendor,
              Grape::Env::API_VERSION => media_type.version,
              Grape::Env::API_FORMAT => media_type.format
            )
          end
        end

        private

        def match_best_quality_media_type!
          return unless vendor

          strict_header_checks!
          media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
          if media_type
            yield media_type
          else
            fail!
          end
        end

        def accept_header
          env['HTTP_ACCEPT']
        end

        def strict_header_checks!
          return unless strict

          accept_header_check!
          version_and_vendor_check!
        end

        def accept_header_check!
          return if accept_header.present?

          invalid_accept_header!('Accept header must be set.')
        end

        def version_and_vendor_check!
          return if versions.blank? || version_and_vendor?

          invalid_accept_header!('API vendor or version not found.')
        end

        def q_values_mime_types
          @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
        end

        def version_and_vendor?
          q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
        end

        def invalid_accept_header!(message)
          raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
        end

        def invalid_version_header!(message)
          raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
        end

        def fail!
          return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?

          media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
          vendor_not_found!(media_types) || version_not_found!(media_types)
        end

        def vendor_not_found!(media_types)
          return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }

          invalid_accept_header!('API vendor not found.')
        end

        def version_not_found!(media_types)
          return unless media_types.all? { |media_type| media_type&.version && versions && !versions.include?(media_type.version) }

          invalid_version_header!('API version not found.')
        end
      end
    end
  end
end


def match_best_quality_media_type!
          return unless vendor

          strict_header_checks!
          media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
          if media_type
            yield media_type
          else
            fail!
          end
        end


# frozen_string_literal: true

module Grape
  module Util
    module Lazy
      class ValueEnumerable < Value
        def [](key)
          if @value_hash[key].nil?
            Value.new(nil).reached_by(access_keys, key)
          else
            @value_hash[key].reached_by(access_keys, key)
          end
        end

        def fetch(access_keys)
          fetched_keys = access_keys.dup
          value = self[fetched_keys.shift]
          fetched_keys.any? ? value.fetch(fetched_keys) : value
        end

        def []=(key, value)
          @value_hash[key] = case value
                             when Hash
                               ValueHash.new(value)
                             when Array
                               ValueArray.new(value)
                             else
                               Value.new(value)
                             end
        end
      end
    end
  end
end


# frozen_string_literal: true

require "pathname"
require "zeitwerk"
require_relative "hanami/constants"

# A complete web framework for Ruby
#
# @since 0.1.0
#
# @see http://hanamirb.org
module Hanami
  @_mutex = Mutex.new
  @_bundled = {}

  # @api private
  # @since 2.0.0
  def self.loader
    @loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
      loader.inflector.inflect "db" => "DB"
      loader.inflector.inflect "db_logging" => "DBLogging"
      loader.inflector.inflect "slice_configured_db_operation" => "SliceConfiguredDBOperation"
      loader.inflector.inflect "sql_adapter" => "SQLAdapter"
      loader.inflector.inflect "sql_logger" => "SQLLogger"
      loader.inflector.inflect "sql_formatter" => "SQLFormatter"

      gem_lib = loader.dirs.first
      loader.ignore(
        "#{gem_lib}/hanami/{constants,boot,errors,extensions/router/errors,prepare,rake_tasks,setup}.rb",
        # Ignore conditionally-loaded classes dependent on gems that may not be included in the
        # user's Gemfile
        "#{gem_lib}/hanami/config/{assets,router,views}.rb",
        "#{gem_lib}/hanami/slice/router.rb",
        "#{gem_lib}/hanami/slice/routing/resolver.rb",
        "#{gem_lib}/hanami/slice/routing/middleware/stack.rb",
        "#{gem_lib}/hanami/extensions/**/*"
      )

      unless Hanami.bundled?("hanami-router")
        loader.ignore("#{gem_lib}/hanami/routes.rb")
      end
    end
  end

  # Finds and loads the Hanami app file (`config/app.rb`).
  #
  # Raises an exception if the app file cannot be found.
  #
  # @return [app] the loaded app class
  #
  # @api public
  # @since 2.0.0
  def self.setup(raise_exception: true)
    return app if app?

    app_path = self.app_path

    if app_path
      prepare_load_path
      require(app_path.to_s)
      app
    elsif raise_exception
      raise(
        AppLoadError,
        "Could not locate your Hanami app file.\n\n" \
        "Your app file should be at `config/app.rb` in your project's root directory."
      )
    end
  end

  # Prepare the load path as early as possible (based on the default root inferred from the location
  # of `config/app.rb`), so `require` can work at the top of `config/app.rb`. This may be useful
  # when external classes are needed for configuring certain aspects of the app.
  #
  # @api private
  # @since 2.0.0
  private_class_method def self.prepare_load_path
    lib_path = app_path&.join("..", "..", LIB_DIR)

    if lib_path&.directory?
      path = lib_path.realpath.to_s
      $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
    end

    lib_path
  end

  # Returns the Hamami app class.
  #
  # To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
  #
  # @return [Hanami::App] the app class
  #
  # @raise [AppLoadError] if the app has not been loaded
  #
  # @see .setup
  #
  # @api public
  # @since 2.0.0
  def self.app
    @_mutex.synchronize do
      unless defined?(@_app)
        raise AppLoadError,
              "Hanami.app is not yet configured. " \
              "You may need to `require \"hanami/setup\"` to load your config/app.rb file."
      end

      @_app
    end
  end

  # Returns true if the Hanami app class has been loaded.
  #
  # @return [Boolean]
  #
  # @api public
  # @since 2.0.0
  def self.app?
    instance_variable_defined?(:@_app)
  end

  # @api private
  # @since 2.0.0
  def self.app=(klass)
    @_mutex.synchronize do
      if instance_variable_defined?(:@_app)
        raise AppLoadError, "Hanami.app is already configured."
      end

      @_app = klass unless klass.name.nil?
    end
  end

  # Finds and returns the absolute path for the Hanami app file (`config/app.rb`).
  #
  # Searches within the given directory, then searches upwards through parent directories until the
  # app file can be found.
  #
  # @param dir [String, Pathname] The directory from which to start searching. Defaults to the
  #   current directory.
  #
  # @return [Pathname, nil] the app file path, or nil if not found.
  #
  # @api public
  # @since 2.0.0
  def self.app_path(dir = Dir.pwd)
    dir = Pathname(dir).expand_path
    path = dir.join(APP_PATH)

    if path.file?
      path
    elsif !dir.root?
      app_path(dir.parent)
    end
  end

  # Returns the Hanami app environment as determined from the environment.
  #
  # Checks the following environment variables in order:
  #
  # - `HANAMI_ENV`
  # - `APP_ENV`
  # - `RACK_ENV`
  #
  # Defaults to `:development` if no environment variable is set.
  #
  # @example
  #   Hanami.env # => :development
  #
  # @return [Symbol] the environment name
  #
  # @api public
  # @since 2.0.0
  def self.env(e: ENV)
    (e["HANAMI_ENV"] || e["APP_ENV"] || e["RACK_ENV"] || :development).to_sym
  end

  # Returns true if {.env} matches any of the given names
  #
  # @example
  #   Hanami.env # => :development
  #   Hanami.env?(:development, :test) # => true
  #
  # @param names [Array<Symbol>] the environment names to check
  #
  # @return [Boolean]
  #
  # @api public
  # @since 2.0.0
  def self.env?(*names)
    names.map(&:to_sym).include?(env)
  end

  # Returns the app's logger.
  #
  # Direct global access to the logger via this method is not recommended. Instead, consider
  # accessing the logger via the app or slice container, in most cases as an dependency using the
  # `Deps` mixin.
  #
  # @example
  #   # app/my_component.rb
  #
  #   module MyApp
  #     class MyComponent
  #       include Deps["logger"]
  #
  #       def some_method
  #         logger.info("hello")
  #       end
  #     end
  #   end
  #
  # @return [Dry::Logger::Dispatcher]
  #
  # @api public
  # @since 1.0.0
  def self.logger
    app[:logger]
  end

  # Prepares the Hanami app.
  #
  # @see App::ClassMethods#prepare
  #
  # @api public
  # @since 2.0.0
  def self.prepare
    app.prepare
  end

  # Boots the Hanami app.
  #
  # @see App::ClassMethods#boot
  #
  # @api public
  # @since 2.0.0
  def self.boot
    app.boot
  end

  # Shuts down the Hanami app.
  #
  # @see App::ClassMethods#shutdown
  #
  # @api public
  # @since 2.0.0
  def self.shutdown
    app.shutdown
  end

  # @api private
  # @since 2.0.0
  def self.bundled?(gem_name)
    @_mutex.synchronize do
      @_bundled[gem_name] ||= begin
        gem(gem_name)
      rescue Gem::LoadError
        false
      end
    end
  end

  # Returns an array of bundler group names to be eagerly loaded by hanami-cli and other CLI
  # extensions.
  #
  # @api private
  # @since 2.0.0
  def self.bundler_groups
    [:plugins]
  end

  loader.setup

  require_relative "hanami/errors"
  require_relative "hanami/extensions"
end


def self.setup(raise_exception: true)
    return app if app?

    app_path = self.app_path

    if app_path
      prepare_load_path
      require(app_path.to_s)
      app
    elsif raise_exception
      raise(
        AppLoadError,
        "Could not locate your Hanami app file.\n\n" \
        "Your app file should be at `config/app.rb` in your project's root directory."
      )
    end
  end


def self.app
    @_mutex.synchronize do
      unless defined?(@_app)
        raise AppLoadError,
              "Hanami.app is not yet configured. " \
              "You may need to `require \"hanami/setup\"` to load your config/app.rb file."
      end

      @_app
    end
  end


# frozen_string_literal: true

require_relative "constants"

module Hanami
  # @api private
  class SliceRegistrar
    VALID_SLICE_NAME_RE = /^[a-z][a-z0-9_]*$/
    SLICE_DELIMITER = CONTAINER_KEY_DELIMITER

    attr_reader :parent, :slices
    private :parent, :slices

    def initialize(parent)
      @parent = parent
      @slices = {}
    end

    def register(name, slice_class = nil, &block)
      unless name.to_s =~ VALID_SLICE_NAME_RE
        raise ArgumentError, "slice name #{name.inspect} must be lowercase alphanumeric text and underscores only"
      end

      return unless filter_slice_names([name]).any?

      if slices.key?(name.to_sym)
        raise SliceLoadError, "Slice '#{name}' is already registered"
      end

      slice = slice_class || build_slice(name, &block)

      configure_slice(name, slice)

      slices[name.to_sym] = slice
    end

    def [](name)
      slices.fetch(name) do
        raise SliceLoadError, "Slice '#{name}' not found"
      end
    end

    def freeze
      slices.freeze
      super
    end

    def load_slices
      slice_configs = root.join(CONFIG_DIR, SLICES_DIR).glob("*#{RB_EXT}")
        .map { _1.basename(RB_EXT) }

      slice_dirs = root.join(SLICES_DIR).glob("*")
        .select(&:directory?)
        .map { _1.basename }

      (slice_dirs + slice_configs).uniq.sort
        .then { filter_slice_names(_1) }
        .each(&method(:load_slice))

      self
    end

    def each(&block)
      slices.each_value(&block)
    end

    def keys
      slices.keys
    end

    def to_a
      slices.values
    end

    def with_nested
      to_a.flat_map { |slice|
        # Return nested slices first so that their more specific namespaces may be picked up first
        # by SliceConfigurable#slice_for
        slice.slices.with_nested + [slice]
      }
    end

    private

    def root
      parent.root
    end

    def inflector
      parent.inflector
    end

    def parent_slice_namespace
      parent.eql?(parent.app) ? Object : parent.namespace
    end

    # Runs when a slice file has been found inside the app at `config/slices/[slice_name].rb`,
    # or when a slice directory exists at `slices/[slice_name]`.
    #
    # If a slice definition file is found by `find_slice_require_path`, then `load_slice` will
    # require the file before registering the slice class.
    #
    # If a slice class is not found, registering the slice will generate the slice class.
    def load_slice(slice_name)
      slice_require_path = find_slice_require_path(slice_name)
      require slice_require_path if slice_require_path

      slice_class =
        begin
          inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
        rescue NameError => exception
          raise exception unless exception.name.to_s == inflector.camelize(slice_name) || exception.name.to_s == :Slice
        end

      register(slice_name, slice_class)
    end

    # Finds the path to the slice's definition file, if it exists, in the following order:
    #
    # 1. `config/slices/[slice_name].rb`
    # 2. `slices/[parent_slice_name]/config/[slice_name].rb` (unless parent is the app)
    # 3. `slices/[slice_name]/config/slice.rb`
    #
    # If the slice is nested under another slice then it will look in the following order:
    #
    # 1. `config/slices/[parent_slice_name]/[slice_name].rb`
    # 2. `slices/[parent_slice_name]/config/[slice_name].rb`
    # 3. `slices/[parent_slice_name]/[slice_name]/config/slice.rb`
    def find_slice_require_path(slice_name)
      app_slice_file_path = [slice_name]
      app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app)
      ancestors = [
        parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)),
        parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name),
        root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice")
      ]

      ancestors
        .uniq
        .find { _1.sub_ext(RB_EXT).file? }
        &.to_s
    end

    def build_slice(slice_name, &block)
      slice_module =
        begin
          inflector.constantize(slice_module_name(slice_name))
        rescue NameError
          parent_slice_namespace.const_set(inflector.camelize(slice_name), Module.new)
        end

      slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
    end

    def slice_module_name(slice_name)
      inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
    end

    def configure_slice(slice_name, slice)
      slice.instance_variable_set(:@parent, parent)

      # Slices require a root, so provide a sensible default based on the slice's parent
      slice.config.root ||= root.join(SLICES_DIR, slice_name.to_s)

      slice.config.slices = child_slice_names(slice_name, parent.config.slices)
    end

    # Returns a filtered array of slice names based on the parent's `config.slices`
    #
    # This works with both singular slice names (e.g. `"admin"`) as well as dot-delimited nested
    # slice names (e.g. `"admin.shop"`).
    #
    # It will consider only the base names of the slices (since in this case, a parent slice must be
    # loaded in order for its children to be loaded).
    #
    # @example
    #   parent.config.slices # => ["admin.shop"]
    #   filter_slice_names(["admin", "main"]) # => ["admin"]
    #
    #   parent.config.slices # => ["admin"]
    #   filter_slice_names(["admin", "main"]) # => ["admin"]
    def filter_slice_names(slice_names)
      slice_names = slice_names.map(&:to_s)

      if parent.config.slices
        slice_names & parent.config.slices.map { base_slice_name(_1) }
      else
        slice_names
      end
    end

    # Returns the base slice name from an (optionally) dot-delimited nested slice name.
    #
    # @example
    #   base_slice_name("admin") # => "admin"
    #   base_slice_name("admin.users") # => "admin"
    def base_slice_name(name)
      name.to_s.split(SLICE_DELIMITER).first
    end

    # Returns an array of slice names specific to the given child slice.
    #
    # @example
    #   child_local_slice_names("admin", ["main", "admin.users"]) # => ["users"]
    def child_slice_names(parent_slice_name, slice_names)
      slice_names
        &.select { |name|
          name.include?(SLICE_DELIMITER) && name.split(SLICE_DELIMITER)[0] == parent_slice_name.to_s
        }
        &.map { |name|
          name.split(SLICE_DELIMITER)[1..].join(SLICE_DELIMITER) # which version of Ruby supports this?
        }
    end
  end
end


def load_slices
      slice_configs = root.join(CONFIG_DIR, SLICES_DIR).glob("*#{RB_EXT}")
        .map { _1.basename(RB_EXT) }

      slice_dirs = root.join(SLICES_DIR).glob("*")
        .select(&:directory?)
        .map { _1.basename }

      (slice_dirs + slice_configs).uniq.sort
        .then { filter_slice_names(_1) }
        .each(&method(:load_slice))

      self
    end


def load_slice(slice_name)
      slice_require_path = find_slice_require_path(slice_name)
      require slice_require_path if slice_require_path

      slice_class =
        begin
          inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
        rescue NameError => exception
          raise exception unless exception.name.to_s == inflector.camelize(slice_name) || exception.name.to_s == :Slice
        end

      register(slice_name, slice_class)
    end


def find_slice_require_path(slice_name)
      app_slice_file_path = [slice_name]
      app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app)
      ancestors = [
        parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)),
        parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name),
        root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice")
      ]

      ancestors
        .uniq
        .find { _1.sub_ext(RB_EXT).file? }
        &.to_s
    end


# frozen_string_literal: true

require_relative "constants"
require_relative "errors"
require_relative "slice/router"

module Hanami
  # App routes
  #
  # Users are expected to inherit from this class to define their app
  # routes.
  #
  # @example
  #   # config/routes.rb
  #   # frozen_string_literal: true
  #
  #   require "hanami/routes"
  #
  #   module MyApp
  #     class Routes < Hanami::Routes
  #       root to: "home.show"
  #     end
  #   end
  #
  #   See {Hanami::Slice::Router} for the syntax allowed within the `define` block.
  #
  # @see Hanami::Slice::Router
  # @since 2.0.0
  class Routes
    # Error raised when no action could be found in an app or slice container for the key given in a
    # routes file.
    #
    # @api public
    # @since 2.0.0
    class MissingActionError < Hanami::Error
      # @api private
      def initialize(action_key, slice)
        action_path = action_key.gsub(CONTAINER_KEY_DELIMITER, PATH_DELIMITER)
        action_constant = slice.inflector.camelize(
          slice.inflector.underscore(slice.namespace.to_s) + PATH_DELIMITER + action_path
        )
        action_file_path = slice.relative_source_path.join(action_path).to_s.concat(RB_EXT)

        super(<<~MSG)
          Could not find action with key #{action_key.inspect} in #{slice}

          To fix this, define the action class #{action_constant} in #{action_file_path}
        MSG
      end
    end

    # Error raised when a given routes endpoint does not implement the `#call` interface required
    # for Rack.
    #
    # @api public
    # @since 2.0.0
    class NotCallableEndpointError < Hanami::Error
      # @api private
      def initialize(endpoint)
        super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
      end
    end

    # Wrapper class for the (otherwise opaque) proc returned from {.routes}, adding an `#empty?`
    # method that returns true if no routes were defined.
    #
    # This is useful when needing to determine behaviour based on the presence of user-defined
    # routes, such as determining whether to show the Hanami welcome page in {Slice#load_router}.
    #
    # @api private
    # @since 2.1.0
    class RoutesProc < DelegateClass(Proc)
      # @api private
      # @since 2.1.0
      def self.empty
        new(proc {}, empty: true)
      end

      # @api private
      # @since 2.1.0
      def initialize(proc, empty: false)
        @empty = empty
        super(proc)
      end

      # @api private
      # @since 2.1.0
      def empty?
        !!@empty
      end
    end

    # @api private
    def self.routes
      @routes ||= build_routes
    end

    class << self
      # @api private
      def build_routes(definitions = self.definitions)
        return RoutesProc.empty if definitions.empty?

        routes_proc = proc do
          definitions.each do |(name, args, kwargs, block)|
            if block
              public_send(name, *args, **kwargs, &block)
            else
              public_send(name, *args, **kwargs)
            end
          end
        end

        RoutesProc.new(routes_proc)
      end

      # @api private
      def definitions
        @definitions ||= []
      end

      private

      # @api private
      def supported_methods
        @supported_methods ||= Slice::Router.public_instance_methods
      end

      # @api private
      def respond_to_missing?(name, include_private = false)
        supported_methods.include?(name) || super
      end

      # Capture all method calls that are supported by the router DSL
      # so that it can be evaluated lazily during configuration/boot
      # process
      #
      # @api private
      def method_missing(name, *args, **kwargs, &block)
        return super unless respond_to?(name)

        definitions << [name, args, kwargs, block]
        self
      end
    end
  end
end


def build_routes(definitions = self.definitions)
        return RoutesProc.empty if definitions.empty?

        routes_proc = proc do
          definitions.each do |(name, args, kwargs, block)|
            if block
              public_send(name, *args, **kwargs, &block)
            else
              public_send(name, *args, **kwargs)
            end
          end
        end

        RoutesProc.new(routes_proc)
      end


# frozen_string_literal: true

require "dry/core/constants"
require "dry/configurable"
require_relative "errors"

module Hanami
  # Provides user-defined settings for an Hanami app or slice.
  #
  # Define your own settings by inheriting from this class in `config/settings.rb` within an app or
  # slice. Your settings will be loaded from matching ENV vars (with upper-cased names) and be
  # registered as a component as part of the Hanami app {Hanami::Slice::ClassMethods#prepare
  # prepare} step.
  #
  # The settings instance is registered in your app and slice containers as a `"settings"`
  # component. You can use the `Deps` mixin to inject this dependency and make settings available to
  # your other components as required.
  #
  # @example
  #   # config/settings.rb
  #   # frozen_string_literal: true
  #
  #   module MyApp
  #     class Settings < Hanami::Settings
  #       Secret = Types::String.constrained(min_size: 20)
  #
  #       setting :database_url, constructor: Types::String
  #       setting :session_secret, constructor: Secret
  #       setting :some_flag, default: false, constructor: Types::Params::Bool
  #     end
  #   end
  #
  # Settings are defined with [dry-configurable][dry-c]'s `setting` method. You may likely want to
  # provide `default:` and `constructor:` options for your settings.
  #
  # If you have [dry-types][dry-t] bundled, then a nested `Types` module will be available for type
  # checking your setting values. Pass type objects to the setting `constructor:` options to ensure
  # their values meet your type expectations. You can use dry-types' default type objects or define
  # your own.
  #
  # When the settings are initialized, all type errors will be collected and presented together for
  # correction. Settings are loaded early, as part of the Hanami app's
  # {Hanami::Slice::ClassMethods#prepare prepare} step, to ensure that the app boots only when valid
  # settings are present.
  #
  # Setting values are loaded from a configurable store, which defaults to
  # {Hanami::Settings::EnvStore}, which fetches the values from equivalent upper-cased keys in
  # `ENV`. You can configure an alternative store via {Hanami::Config#settings_store}. Setting stores
  # must implement a `#fetch` method with the same signature as `Hash#fetch`.
  #
  # [dry-c]: https://dry-rb.org/gems/dry-configurable/
  # [dry-t]: https://dry-rb.org/gems/dry-types/
  #
  # @see Hanami::Settings::DotenvStore
  #
  # @api public
  # @since 2.0.0
  class Settings
    # Error raised when setting values do not meet their type expectations.
    #
    # Its message collects all the individual errors that can be raised for each setting.
    #
    # @api public
    # @since 2.0.0
    class InvalidSettingsError < Hanami::Error
      # @api private
      def initialize(errors)
        super()
        @errors = errors
      end

      # Returns the exception's message.
      #
      # @return [String]
      #
      # @api public
      # @since 2.0.0
      def to_s
        <<~STR.strip
          Could not initialize settings. The following settings were invalid:

          #{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
        STR
      end
    end

    class << self
      # Defines a nested `Types` constant in `Settings` subclasses if dry-types is bundled.
      #
      # @see https://dry-rb.org/gems/dry-types
      #
      # @api private
      def inherited(subclass)
        super

        if Hanami.bundled?("dry-types")
          require "dry/types"
          subclass.const_set(:Types, Dry.Types())
        end
      end

      # Loads the settings for a slice.
      #
      # Returns nil if no settings class is defined.
      #
      # @return [Settings, nil]
      #
      # @api private
      def load_for_slice(slice)
        return unless settings_defined?(slice)

        require_slice_settings(slice) unless slice_settings_class?(slice)

        slice_settings_class(slice).new(slice.config.settings_store)
      end

      private

      # Returns true if settings are defined for the slice.
      #
      # Settings are considered defined if a `Settings` class is already defined in the slice
      # namespace, or a `config/settings.rb` exists under the slice root.
      def settings_defined?(slice)
        slice.namespace.const_defined?(SETTINGS_CLASS_NAME) ||
          slice.root.join("#{SETTINGS_PATH}#{RB_EXT}").file?
      end

      def slice_settings_class?(slice)
        slice.namespace.const_defined?(SETTINGS_CLASS_NAME)
      end

      def slice_settings_class(slice)
        slice.namespace.const_get(SETTINGS_CLASS_NAME)
      end

      def require_slice_settings(slice)
        require "hanami/settings"

        slice_settings_require_path = File.join(slice.root, SETTINGS_PATH)

        begin
          require slice_settings_require_path
        rescue LoadError => exception
          raise exception unless exception.path == slice_settings_require_path
        end
      end
    end

    # @api private
    Undefined = Dry::Core::Constants::Undefined

    # @api private
    EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH

    include Dry::Configurable

    # @api private
    def initialize(store = EMPTY_STORE)
      errors = config._settings.map(&:name).each_with_object({}) do |name, errs|
        value = store.fetch(name, Undefined)

        if value.eql?(Undefined)
          # When a key is missing entirely from the store, _read_ its value from the config instead.
          # This ensures its setting constructor runs (with a `nil` argument given) and raises any
          # necessary errors.
          public_send(name)
        else
          public_send("#{name}=", value)
        end
      rescue => exception # rubocop:disable Style/RescueStandardError
        errs[name] = exception
      end

      raise InvalidSettingsError, errors if errors.any?

      config.finalize!
    end

    # Returns a string containing a human-readable representation of the settings.
    #
    # This includes setting names only, not any values, to ensure that sensitive values do not
    # inadvertently leak.
    #
    # Use {#inspect_values} to inspect settings with their values.
    #
    # @example
    #   settings.inspect
    #   # => #<MyApp::Settings [database_url, session_secret, some_flag]>
    #
    # @return [String]
    #
    # @see #inspect_values
    #
    # @api public
    # @since 2.0.0
    def inspect
      "#<#{self.class} [#{config._settings.map(&:name).join(", ")}]>"
    end

    # rubocop:disable Layout/LineLength

    # Returns a string containing a human-readable representation of the settings and their values.
    #
    # @example
    #   settings.inspect_values
    #   # => #<MyApp::Settings database_url="postgres://localhost/my_db", session_secret="xxx", some_flag=true]>
    #
    # @return [String]
    #
    # @see #inspect
    #
    # @api public
    # @since 2.0.0
    def inspect_values
      "#<#{self.class} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
    end

    # rubocop:enable Layout/LineLength

    private

    def method_missing(name, *args, &block)
      if config.respond_to?(name)
        config.send(name, *args, &block)
      else
        super
      end
    end

    def respond_to_missing?(name, _include_all = false)
      config.respond_to?(name) || super
    end
  end
end


# frozen_string_literal: true

require "uri"
require "pathname"
require "dry/configurable"
require "dry/inflector"

require_relative "constants"
require_relative "universal_logger"

module Hanami
  # Hanami app config
  #
  # @since 2.0.0
  class Config
    include Dry::Configurable

    # @!attribute [rw] root
    #   Sets the root for the app or slice.
    #
    #   For the app, this defaults to `Dir.pwd`. For slices detected in `slices/` `config/slices/`,
    #   this defaults to `slices/[slice_name]/`.
    #
    #   Accepts a string path and will return a `Pathname`.
    #
    #   @return [Pathname]
    #
    #   @api public
    #   @since 2.0.0
    setting :root, constructor: ->(path) { Pathname(path) if path }

    # @!attribute [rw] inflector
    #   Sets the app's inflector.
    #
    #   This expects a `Dry::Inflector` (or compatible) inflector instance.
    #
    #   To configure custom inflection rules without having to assign a whole inflector, see
    #   {#inflections}.
    #
    #   @return [Dry::Inflector]
    #
    #   @see #inflections
    #
    #   @api public
    #   @since 2.0.0
    setting :inflector, default: Dry::Inflector.new

    # @!attribute [rw] settings_store
    #   Sets the store used to retrieve {Hanami::Settings} values.
    #
    #   Defaults to an instance of {Hanami::Settings::EnvStore}.
    #
    #   @return [#fetch]
    #
    #   @see Hanami::Settings
    #   @see Hanami::Settings::EnvStore#fetch
    #
    #   @api public
    #   @since 2.0.0
    setting :settings_store, default: Hanami::Settings::EnvStore.new

    # @!attribute [rw] slices
    #   Sets the slices to load when the app is preared or booted.
    #
    #   Defaults to `nil`, which will load all slices. Set this to an array of slice names to load
    #   only those slices.
    #
    #   This attribute is also populated from the `HANAMI_SLICES` environment variable.
    #
    #   @example
    #     config.slices = ["admin", "search"]
    #
    #   @example
    #     ENV["HANAMI_SLICES"] # => "admin,search"
    #     config.slices # => ["admin", "search"]
    #
    #   @return [Array<String>, nil]
    #
    #   @api public
    #   @since 2.0.0
    setting :slices

    # @!attribute [rw] shared_app_component_keys
    #   Sets the keys for the components to be imported from the app into all other slices.
    #
    #   You should append items to this array, since the default shared components are essential for
    #   slices to operate within the app.
    #
    #   @example
    #     config.shared_app_component_keys += ["shared_component_a", "shared_component_b"]
    #
    #   @return [Array<String>]
    #
    #   @api public
    #   @since 2.0.0
    setting :shared_app_component_keys, default: %w[
      inflector
      logger
      notifications
      rack.monitor
      routes
      settings
    ]

    # @!attribute [rw] no_auto_register_paths
    #   Sets the paths to skip from container auto-registration.
    #
    #   Paths are relative to `/app` or a slice root.
    #
    #   Defaults to `["db", "entities", "relations", "structs"]`.
    #
    #   @return [Array<String>] array of relative paths
    #
    #   @api public
    #   @since 2.0.0
    setting :no_auto_register_paths, default: [
      "db",
      "entities",
      "relations",
      "structs"
    ]

    # @!attribute [rw] base_url
    #   Sets the base URL for app's web server.
    #
    #   This is passed to the {Slice::ClassMethods#router router} and used for generating links.
    #
    #   Defaults to `"http://0.0.0.0:2300"`. String values passed are turned into `URI` instances.
    #
    #   @return [URI]
    #
    #   @see Slice::ClassMethods#router
    #
    #   @api public
    #   @since 2.0.0
    setting :base_url, default: "http://0.0.0.0:2300", constructor: ->(url) { URI(url) }

    # @!attribute [rw] render_errors
    #   Sets whether to catch exceptions and render error pages.
    #
    #   For HTML responses, these error pages are in `public/{404,500}.html`.
    #
    #   Defaults to `true` in production mode, `false` in all others.
    #
    #   @return [Boolean]
    #
    #   @api public
    #   @since 2.1.0
    setting :render_errors, default: false

    # @!attribute [rw] render_detailed_errors
    #   Sets whether to catch exceptions and render detailed, interactive error pages.
    #
    #   Requires the hanami-webconsole gem to be available.
    #
    #   Defaults to `false` in production mode, `true` in all others.
    #
    #   @return [Boolean]
    #
    #   @api public
    #   @since 2.1.0
    setting :render_detailed_errors, default: false

    # @!attribute [rw] render_error_responses
    #   Sets a mapping of exception class names (as strings) to symbolic response status codes used
    #   for rendering error responses.
    #
    #   The response status codes will be passed to `Rack::Utils.status_code`.
    #
    #   In ordinary usage, you should not replace this hash. Instead, add keys and values for the
    #   errors you want handled.
    #
    #   @example
    #     config.render_error_responses
    #     # => {"Hanami::Router::NotFoundError" => :not_found}
    #
    #     config.render_error_responses["MyNotFoundError"] = :not_found
    #
    #   @return [Hash{String => Symbol}]
    #
    #   @see #render_errors
    #
    #   @api public
    #   @since 2.1.0
    setting :render_error_responses, default: Hash.new(:internal_server_error).merge!(
      "Hanami::Router::NotAllowedError" => :not_found,
      "Hanami::Router::NotFoundError" => :not_found
    )

    # @!attribute [rw] console
    #   Returns the app's console config
    #
    #   @example
    #     config.console.engine # => :irb
    #
    #   @return [Hanami::Config::Console]
    #
    #   @api public
    #   @since 2.3.0
    setting :console, default: Hanami::Config::Console.new

    # Returns the app or slice's {Hanami::SliceName slice_name}.
    #
    # This is useful for default config values that depend on this name.
    #
    # @return [Hanami::SliceName]
    #
    # @api private
    # @since 2.0.0
    attr_reader :app_name

    # Returns the app's environment.
    #
    # @example
    #   config.env # => :development
    #
    # @return [Symbol]
    #
    # @see #environment
    #
    # @api private
    # @since 2.0.0
    attr_reader :env

    # Returns the app's actions config, or a null config if hanami-action is not bundled.
    #
    # @example When hanami-action is bundled
    #   config.actions.default_request_format # => :html
    #
    # @example When hanami-action is not bundled
    #   config.actions.default_request_format # => NoMethodError
    #
    # @return [Hanami::Config::Actions, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.0.0
    attr_reader :actions

    # Returns the app's db config, or a null config if hanami-db is not bundled.
    #
    # @example When hanami-db is bundled
    #   config.db.import_from_parent # => false
    #
    # @example When hanami-db is not bundle
    #   config.db.import_from_parent # => NoMethodError
    #
    # @return [Hanami::Config::DB, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.2.0
    attr_reader :db

    # Returns the app's i18n config, or a null config if i18n is not bundled.
    #
    # @example When i18n is bundled
    #   config.i18n.default_locale # => :en
    #
    # @example When i18n is not bundled
    #   config.i18n.default_locale # => NoMethodError
    #
    # @return [Hanami::Config::I18n, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.2.0
    attr_reader :i18n

    # Returns the app's middleware stack, or nil if hanami-router is not bundled.
    #
    # Use this to configure middleware that should apply to all routes.
    #
    # @example
    #   config.middleware.use :body_parser, :json
    #   config.middleware.use MyCustomMiddleware
    #
    # @return [Hanami::Slice::Routing::Middleware::Stack, nil]
    #
    # @api public
    # @since 2.0.0
    attr_reader :middleware

    # @api private
    # @since 2.0.0
    alias_method :middleware_stack, :middleware

    # Returns the app's router config, or a null config if hanami-router is not bundled.
    #
    # @example When hanami-router is bundled
    #   config.router.resolver # => Hanami::Slice::Routing::Resolver
    #
    # @example When hanami-router is not bundled
    #   config.router.resolver # => NoMethodError
    #
    # @return [Hanami::Config::Router, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.0.0
    attr_reader :router

    # Returns the app's views config, or a null config if hanami-view is not bundled.
    #
    # @example When hanami-view is bundled
    #   config.views.paths # => [...]
    #
    # @example When hanami-view is not bundled
    #   config.views.paths # => NoMethodError
    #
    # @return [Hanami::Config::Views, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.1.0
    attr_reader :views

    # Returns the app's views config, or a null config if hanami-view is not bundled.
    #
    # @example When hanami-view is bundled
    #   config.views.paths # => [...]
    #
    # @example When hanami-view is not bundled
    #   config.views.paths # => NoMethodError
    #
    # @return [Hanami::Config::Assets, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.1.0
    attr_reader :assets

    # @api private
    # rubocop:disable Metrics/AbcSize
    def initialize(app_name:, env:)
      @app_name = app_name
      @env = env

      # Apply default values that are only knowable at initialize-time (vs require-time)
      self.root = Dir.pwd
      self.render_errors = (env == :production)
      self.render_detailed_errors = (env == :development)
      load_from_env

      @actions = load_dependent_config("hanami-action") {
        require_relative "config/actions"
        Actions.new
      }

      @assets = load_dependent_config("hanami-assets") {
        require_relative "config/assets"
        Hanami::Config::Assets.new
      }

      @db = load_dependent_config("hanami-db") { DB.new }

      @i18n = load_dependent_config("i18n") {
        require_relative "config/i18n"
        I18n.new
      }

      @logger = Config::Logger.new(env: env, app_name: app_name)

      @middleware = load_dependent_config("hanami-router") {
        require_relative "slice/routing/middleware/stack"
        Slice::Routing::Middleware::Stack.new
      }

      @router = load_dependent_config("hanami-router") {
        require_relative "config/router"
        Router.new(self)
      }

      @views = load_dependent_config("hanami-view") {
        require_relative "config/views"
        Views.new
      }

      yield self if block_given?
    end
    # rubocop:enable Metrics/AbcSize

    # @api private
    def initialize_copy(source)
      super

      @app_name = app_name.dup

      @actions = source.actions.dup
      @assets = source.assets.dup
      @db = source.db.dup
      @i18n = source.i18n.dup
      @logger = source.logger.dup
      @middleware = source.middleware.dup
      @router = source.router.dup.tap do |router|
        router.instance_variable_set(:@base_config, self)
      end
      @views = source.views.dup
    end
    private :initialize_copy

    # Finalizes the config.
    #
    # This is called when the app or slice is prepared. After this, no further changes to config can
    # be made.
    #
    # @api private
    def finalize!
      # Finalize nested configs
      assets.finalize!
      actions.finalize!(self)
      views.finalize!
      logger.finalize!
      router.finalize!

      super
    end

    # Configures the app's custom inflections.
    #
    # You should call this one time only. Subsequent calls will override previously configured
    # inflections.
    #
    # @example
    #   config.inflections do |inflections|
    #     inflections.acronym "WNBA"
    #   end
    #
    # @see https://dry-rb.org/gems/dry-inflector
    #
    # @return [Dry::Inflector] the configured inflector
    #
    # @api public
    # @since 2.0.0
    def inflections(&block)
      self.inflector = Dry::Inflector.new(&block)
    end

    # Disabling this to permit distinct documentation for `#logger` vs `#logger=`
    #
    # rubocop:disable Style/TrivialAccessors

    # Returns the logger config.
    #
    # Use this to configure various options for the default `Dry::Logger::Dispatcher` logger instance.
    #
    # @example
    #   config.logger.level = :debug
    #
    # @return [Hanami::Config::Logger]
    #
    # @see Hanami::Config::Logger
    #
    # @api public
    # @since 2.0.0
    def logger
      @logger
    end

    # Sets the app's logger instance.
    #
    # This entirely replaces the default `Dry::Logger::Dispatcher` instance that would have been
    # configured via {#logger}.
    #
    # The provided logger will be wrapped with {Hanami::UniversalLogger} if it doesn't support both
    # keyword arguments and `#tagged` logging, ensuring compatibility with Hanami's logging
    # interface.
    #
    # @see #logger_instance
    # @see Hanami::UniversalLogger
    #
    # @api public
    # @since 2.0.0
    def logger=(logger_instance)
      @logger_instance = Hanami::UniversalLogger[logger_instance]
    end

    # rubocop:enable Style/TrivialAccessors

    # Returns the configured logger instance.
    #
    # Unless you've replaced the logger with {#logger=}, this returns a `Dry::Logger::Dispatcher` configured
    # with the options configured through {#logger}.
    #
    # This configured logger is registered in all app and slice containers as `"logger"`. For
    # typical usage, you should access the logger via this component, not directly from config.
    #
    # @example Accessing the logger component
    #   Hanami.app["logger"] # => #<Dry::Logger::Dispatcher>
    #
    # @example Injecting the logger as a dependency
    #   module MyApp
    #     class MyClass
    #       include Deps["logger"]
    #
    #       def my_method
    #         logger.info("hello")
    #       end
    #     end
    #   end
    #
    # @return [Dry::Logger::Dispatcher]
    #
    # @see #logger
    # @see Hanami::Config::Logger
    #
    # @api public
    # @since 2.0.0
    def logger_instance
      @logger_instance || logger.instance
    end

    private

    def load_from_env
      self.slices = ENV["HANAMI_SLICES"]&.split(",")&.map(&:strip)
    end

    def load_dependent_config(gem_name)
      if Hanami.bundled?(gem_name)
        yield
      else
        require_relative "config/null_config"
        NullConfig.new
      end
    end

    def method_missing(name, *args, &block)
      if config.respond_to?(name)
        config.public_send(name, *args, &block)
      else
        super
      end
    end

    def respond_to_missing?(name, _include_all = false)
      config.respond_to?(name) || super
    end
  end
end


def initialize_copy(source)
      super

      @app_name = app_name.dup

      @actions = source.actions.dup
      @assets = source.assets.dup
      @db = source.db.dup
      @i18n = source.i18n.dup
      @logger = source.logger.dup
      @middleware = source.middleware.dup
      @router = source.router.dup.tap do |router|
        router.instance_variable_set(:@base_config, self)
      end
      @views = source.views.dup
    end


# frozen_string_literal: true

require_relative "constants"
require_relative "env"

module Hanami
  # The Hanami app is a singular slice tasked with managing the core components of the app and
  # coordinating overall app boot.
  #
  # For smaller apps, the app may be the only slice present, whereas larger apps may consist of many
  # slices, with the app reserved for holding a small number of shared components only.
  #
  # @see Slice
  #
  # @api public
  # @since 2.0.0
  class App < Slice
    @_mutex = Mutex.new

    # @api private
    # @since 2.0.0
    def self.inherited(subclass)
      super

      Hanami.app = subclass

      subclass.extend(ClassMethods)

      @_mutex.synchronize do
        subclass.class_eval do
          @config = Hanami::Config.new(app_name: slice_name, env: Hanami.env)
          Hanami::Env.load
        end
      end
    end

    # App class interface
    module ClassMethods
      # Returns the app's config.
      #
      # @return [Hanami::Config]
      #
      # @api public
      # @since 2.0.0
      attr_reader :config

      # Returns the app's {SliceName}.
      #
      # @return [Hanami::SliceName]
      #
      # @see Slice::ClassMethods#slice_name
      #
      # @api public
      # @since 2.0.0
      def app_name
        slice_name
      end

      # Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/` directory
      # if it exists. If the lib directory is already added, this will do nothing.
      #
      # In ordinary circumstances, you should never have to call this method: this method is called
      # immediately upon subclassing {Hanami::App}, as a convenicence to put lib/ (under the default
      # root of `Dir.pwd`) on the load path automatically. This is helpful if you need to require
      # files inside the subclass body for performing certain app configuration steps.
      #
      # If you change your app's `config.root` and you need to require files from its `lib/`
      # directory within your {App} subclass body, you should call {.prepare_load_path} explicitly
      # after setting the new root.
      #
      # Otherwise, this method is called again as part of the app {.prepare} step, so if you've
      # changed your app's root and do _not_ need to require files within your {App} subclass body,
      # then you don't need to call this method.
      #
      # @example
      #   module MyApp
      #     class App < Hanami::App
      #       config.root = Pathname(__dir__).join("../src")
      #       prepare_load_path
      #
      #       # You can make requires for your files here
      #     end
      #   end
      #
      # @return [self]
      #
      # @api public
      # @since 2.0.0
      def prepare_load_path
        if (lib_path = root.join(LIB_DIR)).directory?
          path = lib_path.realpath.to_s
          $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
        end

        self
      end

      private

      def prepare_all
        prepare_load_path

        # Make app-wide notifications available as early as possible
        container.use(:notifications)

        # Ensure all basic slice preparation is complete before we make adjustments below (which
        # rely on the basic prepare steps having already run)
        super

        # Run specific prepare steps for the app slice. Note also that some standard steps have been
        # skipped via the empty method overrides below.
        prepare_app_component_dirs
        prepare_app_providers
      end

      # Skip standard slice prepare steps that do not apply to the app
      def prepare_container_component_dirs; end
      def prepare_container_imports; end

      # rubocop:disable Metrics/AbcSize

      def prepare_app_component_dirs
        # Component files in both `app/` and `app/lib/` define classes in the
        # app's namespace

        if root.join(APP_DIR, LIB_DIR).directory?
          container.config.component_dirs.add(File.join(APP_DIR, LIB_DIR)) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
          end
        end

        # When auto-registering components in `app/`, ignore files in `app/lib/` (these will be
        # auto-registered as above), as well as the configured no_auto_register_paths
        no_auto_register_paths = ([LIB_DIR] + config.no_auto_register_paths)
          .map { |path|
            path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
          }

        if root.join(APP_DIR).directory?
          container.config.component_dirs.add(APP_DIR) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
            dir.auto_register = -> component {
              relative_path = component.file_path.relative_path_from(root.join(APP_DIR)).to_s
              !relative_path.start_with?(*no_auto_register_paths)
            }
          end
        end
      end

      def prepare_app_providers
        require_relative "providers/inflector"
        register_provider(:inflector, source: Hanami::Providers::Inflector)

        # Allow logger to be replaced by users with a manual provider, for advanced cases
        unless container.providers[:logger]
          require_relative "providers/logger"
          register_provider(:logger, source: Hanami::Providers::Logger)
        end

        if Hanami.bundled?("rack")
          require_relative "providers/rack"
          register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
        end

        if Hanami.bundled?("hanami-db")
          register_provider(:db_logging, source: Hanami::Providers::DBLogging)
        end
      end

      def prepare_autoloader
        autoloader.tag = "hanami.app.#{slice_name.name}"

        # Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
        # This method adds other dirs that are not otherwise configured as component dirs.

        # Autoload classes from `lib/[app_namespace]/`
        if root.join(LIB_DIR, app_name.name).directory?
          autoloader.push_dir(root.join(LIB_DIR, app_name.name), namespace: namespace)
        end

        autoloader.setup
      end

      # rubocop:enable Metrics/AbcSize
    end
  end
end


def prepare_app_component_dirs
        # Component files in both `app/` and `app/lib/` define classes in the
        # app's namespace

        if root.join(APP_DIR, LIB_DIR).directory?
          container.config.component_dirs.add(File.join(APP_DIR, LIB_DIR)) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
          end
        end

        # When auto-registering components in `app/`, ignore files in `app/lib/` (these will be
        # auto-registered as above), as well as the configured no_auto_register_paths
        no_auto_register_paths = ([LIB_DIR] + config.no_auto_register_paths)
          .map { |path|
            path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
          }

        if root.join(APP_DIR).directory?
          container.config.component_dirs.add(APP_DIR) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
            dir.auto_register = -> component {
              relative_path = component.file_path.relative_path_from(root.join(APP_DIR)).to_s
              !relative_path.start_with?(*no_auto_register_paths)
            }
          end
        end
      end


def prepare_app_providers
        require_relative "providers/inflector"
        register_provider(:inflector, source: Hanami::Providers::Inflector)

        # Allow logger to be replaced by users with a manual provider, for advanced cases
        unless container.providers[:logger]
          require_relative "providers/logger"
          register_provider(:logger, source: Hanami::Providers::Logger)
        end

        if Hanami.bundled?("rack")
          require_relative "providers/rack"
          register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
        end

        if Hanami.bundled?("hanami-db")
          register_provider(:db_logging, source: Hanami::Providers::DBLogging)
        end
      end