diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3a11f910ba..e221cfd920 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 5000` -# on 2024-03-26 03:54:44 UTC using RuboCop version 1.59.0. +# on 2024-04-01 12:18:08 UTC using RuboCop version 1.59.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -40,7 +40,7 @@ Lint/EmptyClass: Exclude: - 'lib/grape/dsl/parameters.rb' -# Offense count: 6 +# Offense count: 5 # Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: @@ -49,7 +49,6 @@ Lint/MissingSuper: - 'lib/grape/namespace.rb' - 'lib/grape/path.rb' - 'lib/grape/router/pattern.rb' - - 'lib/grape/validations/validators/base.rb' # Offense count: 1 # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. @@ -92,7 +91,7 @@ RSpec/AnyInstance: - 'spec/grape/api_spec.rb' - 'spec/grape/middleware/base_spec.rb' -# Offense count: 2 +# Offense count: 1 # Configuration parameters: IgnoredMetadata. RSpec/DescribeClass: Exclude: @@ -102,7 +101,6 @@ RSpec/DescribeClass: - '**/spec/system/**/*' - '**/spec/views/**/*' - 'spec/grape/named_api_spec.rb' - - 'spec/grape/validations/instance_behaivour_spec.rb' # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). @@ -156,7 +154,7 @@ RSpec/MessageChain: Exclude: - 'spec/grape/middleware/formatter_spec.rb' -# Offense count: 148 +# Offense count: 144 # Configuration parameters: . # SupportedStyles: have_received, receive RSpec/MessageSpies: @@ -206,17 +204,15 @@ RSpec/RepeatedExampleGroupDescription: - 'spec/grape/util/inheritable_setting_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 6 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). RSpec/ScatteredSetup: Exclude: - 'spec/grape/util/inheritable_setting_spec.rb' - - 'spec/grape/validations_spec.rb' -# Offense count: 9 +# Offense count: 8 RSpec/StubbedMock: Exclude: - - 'spec/grape/api_spec.rb' - 'spec/grape/dsl/inside_route_spec.rb' - 'spec/grape/dsl/routing_spec.rb' - 'spec/grape/middleware/formatter_spec.rb' @@ -295,7 +291,7 @@ Style/OptionalBooleanParameter: - 'lib/grape/validations/types/primitive_coercer.rb' - 'lib/grape/validations/types/set_coercer.rb' -# Offense count: 28 +# Offense count: 29 # This cop supports safe autocorrection (--autocorrect). Style/RedundantConstantBase: Exclude: diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b5a8fe0a..96c236ec38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * [#2389](https://github.com/ruby-grape/grape/pull/2389): Remove rack-accept dependency - [@ericproulx](https://github.com/ericproulx). * [#2426](https://github.com/ruby-grape/grape/pull/2426): Drop support for rack 1.x series - [@ericproulx](https://github.com/ericproulx). * [#2427](https://github.com/ruby-grape/grape/pull/2427): Use `rack-contrib` jsonp instead of rack-jsonp - [@ericproulx](https://github.com/ericproulx). +* [#2363](https://github.com/ruby-grape/grape/pull/2363): Replace autoload by zeitwerk - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/UPGRADING.md b/UPGRADING.md index d7ef9248b0..3bc1fcf312 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,13 @@ Upgrading Grape ### Upgrading to >= 2.1.0 +#### Zeitwerk + +Grape's autoloader has been updated and it's now based on [Zeitwerk](https://github.com/fxn/zeitwerk). +If you MP (Monkey Patch) some files and you're not following the [file structure](https://github.com/fxn/zeitwerk?tab=readme-ov-file#file-structure), you might end up with a Zeitwerk error. + +See [#2363](https://github.com/ruby-grape/grape/pull/2363) for more information. + #### Changes in rescue_from The `rack_response` method has been deprecated and the `error_response` method has been removed. Use `error!` instead. diff --git a/benchmark/compile_many_routes.rb b/benchmark/compile_many_routes.rb index 9fa858cf37..1b273302ff 100644 --- a/benchmark/compile_many_routes.rb +++ b/benchmark/compile_many_routes.rb @@ -3,9 +3,6 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'grape' require 'benchmark/ips' -require 'grape/eager_load' - -Grape.eager_load! class API < Grape::API prefix :api diff --git a/grape.gemspec b/grape.gemspec index 1ad463c280..92c2d7fd0c 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -25,6 +25,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'dry-types', '>= 1.1' s.add_runtime_dependency 'mustermann-grape', '~> 1.1.0' s.add_runtime_dependency 'rack', '>= 2' + s.add_runtime_dependency 'zeitwerk' s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec'] s.require_paths = ['lib'] diff --git a/lib/grape.rb b/lib/grape.rb index 4bfecdcb01..2c99d377a8 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -1,12 +1,5 @@ # frozen_string_literal: true -require 'logger' -require 'rack' -require 'rack/builder' -require 'rack/auth/basic' -require 'set' -require 'bigdecimal' -require 'date' require 'active_support' require 'active_support/concern' require 'active_support/configurable' @@ -26,293 +19,54 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/deep_dup' require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/exclude' -require 'active_support/dependencies/autoload' require 'active_support/deprecation' require 'active_support/inflector' require 'active_support/notifications' -require 'i18n' + +require 'English' +require 'bigdecimal' +require 'date' +require 'dry-types' +require 'forwardable' +require 'json' +require 'logger' +require 'mustermann/grape' +require 'pathname' +require 'rack' +require 'rack/auth/basic' +require 'rack/builder' +require 'rack/head' +require 'set' +require 'singleton' +require 'zeitwerk' + +loader = Zeitwerk::Loader.for_gem +loader.inflector.inflect( + 'api' => 'API', + 'dsl' => 'DSL' +) +railtie = "#{__dir__}/grape/railtie.rb" +loader.do_not_eager_load(railtie) +loader.setup I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__) module Grape include ActiveSupport::Configurable - extend ::ActiveSupport::Autoload def self.deprecator @deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape') end - def self.lowercase_headers? - Rack::CONTENT_TYPE == 'content-type' - end - - eager_autoload do - autoload :API - autoload :Endpoint - - autoload :Namespace - - autoload :Path - autoload :Cookies - autoload :Validations - autoload :ErrorFormatter - autoload :Formatter - autoload :Parser - autoload :Request - autoload :Env, 'grape/util/env' - autoload :Json, 'grape/util/json' - autoload :Xml, 'grape/util/xml' - autoload :DryTypes - end - - module Http - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Headers - end - end - - module Exceptions - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Base - autoload :Validation - autoload :ValidationArrayErrors - autoload :ValidationErrors - autoload :MissingVendorOption - autoload :MissingMimeType - autoload :MissingOption - autoload :InvalidFormatter - autoload :InvalidVersionerOption - autoload :UnknownValidator - autoload :UnknownOptions - autoload :UnknownParameter - autoload :InvalidWithOptionForRepresent - autoload :IncompatibleOptionValues - autoload :MissingGroupType - autoload :UnsupportedGroupType - autoload :InvalidMessageBody - autoload :InvalidAcceptHeader - autoload :InvalidVersionHeader - autoload :MethodNotAllowed - autoload :InvalidResponse - autoload :EmptyMessageBody - autoload :TooManyMultipartFiles - autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type' - autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type' - end - end - - module Extensions - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Hash - end - module ActiveSupport - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :HashWithIndifferentAccess - end - end - - module Hashie - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Mash - end - end - end - - module Middleware - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Base - autoload :Versioner - autoload :Formatter - autoload :Error - autoload :Globals - autoload :Stack - autoload :Helpers - end - - module Auth - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Base - autoload :DSL - autoload :StrategyInfo - autoload :Strategies - end - end - - module Versioner - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Path - autoload :Header - autoload :Param - autoload :AcceptVersionHeader - end - end - end - - module Util - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :InheritableValues - autoload :StackableValues - autoload :ReverseStackableValues - autoload :InheritableSetting - autoload :StrictHashConfiguration - autoload :Registrable - end - end - - module ErrorFormatter - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Base - autoload :Json - autoload :Txt - autoload :Xml - end - end - - module Formatter - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Json - autoload :SerializableHash - autoload :Txt - autoload :Xml - end - end - - module Parser - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Json - autoload :Xml - end - end - - module DSL - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :API - autoload :Callbacks - autoload :Settings - autoload :Configuration - autoload :InsideRoute - autoload :Helpers - autoload :Middleware - autoload :Parameters - autoload :RequestResponse - autoload :Routing - autoload :Validations - autoload :Logger - autoload :Desc - end - end - - class API - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Helpers - end - end - - module Presenters - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :Presenter - end - end - - module ServeStream - extend ::ActiveSupport::Autoload - eager_autoload do - autoload :FileBody - autoload :SendfileResponse - autoload :StreamResponse - end - end - - module Validations - extend ::ActiveSupport::Autoload - - eager_autoload do - autoload :AttributesIterator - autoload :MultipleAttributesIterator - autoload :SingleAttributeIterator - autoload :Types - autoload :ParamsScope - autoload :ContractScope - autoload :ValidatorFactory - autoload :Base, 'grape/validations/validators/base' - end - - module Types - extend ::ActiveSupport::Autoload - - eager_autoload do - autoload :InvalidValue - autoload :DryTypeCoercer - autoload :ArrayCoercer - autoload :SetCoercer - autoload :PrimitiveCoercer - autoload :CustomTypeCoercer - autoload :CustomTypeCollectionCoercer - autoload :MultipleTypeCoercer - autoload :VariantCollectionCoercer - end - end - - module Validators - extend ::ActiveSupport::Autoload - - eager_autoload do - autoload :Base - autoload :MultipleParamsBase - autoload :AllOrNoneOfValidator - autoload :AllowBlankValidator - autoload :AsValidator - autoload :AtLeastOneOfValidator - autoload :CoerceValidator - autoload :DefaultValidator - autoload :ExactlyOneOfValidator - autoload :ExceptValuesValidator - autoload :MutualExclusionValidator - autoload :PresenceValidator - autoload :RegexpValidator - autoload :SameAsValidator - autoload :ValuesValidator - end - end - end - - module Types - extend ::ActiveSupport::Autoload - - eager_autoload do - autoload :InvalidValue - end - end - configure do |config| config.param_builder = Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder config.compile_methods! end end -require 'grape/content_types' - -require 'grape/util/lazy_value' -require 'grape/util/lazy_block' -require 'grape/util/endpoint_configuration' -require 'grape/version' - # https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html # adding Grape.deprecator to Rails App if any require 'grape/railtie' if defined?(Rails::Railtie) && ActiveSupport.gem_version >= Gem::Version.new('7.1') +loader.eager_load diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 536d997e9f..38f17dcc56 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'grape/router' -require 'grape/api/instance' - module Grape # The API class is the primary entry point for creating Grape APIs. Users # should subclass this class in order to build an API. @@ -128,7 +125,6 @@ def method_missing(method, *args, &block) end def compile! - require 'grape/eager_load' instance_for_rack.compile! # See API::Instance.compile! end diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index a326f44c6e..c0c6ba2fd5 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/router' - module Grape class API # The API Instance class, is the engine behind Grape::API. Each class that inherits @@ -112,7 +110,7 @@ def nest(*blocks, &block) end def evaluate_as_instance_with_configuration(block, lazy: false) - lazy_block = Grape::Util::LazyBlock.new do |configuration| + lazy_block = Grape::Util::Lazy::Block.new do |configuration| value_for_configuration = configuration self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy? response = instance_eval(&block) diff --git a/lib/grape/content_types.rb b/lib/grape/content_types.rb index c6f295154a..cc0cc2cab1 100644 --- a/lib/grape/content_types.rb +++ b/lib/grape/content_types.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/util/registrable' - module Grape module ContentTypes extend Util::Registrable diff --git a/lib/grape/dry_types.rb b/lib/grape/dry_types.rb index f0676c376d..5f1bc3cde9 100644 --- a/lib/grape/dry_types.rb +++ b/lib/grape/dry_types.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'dry-types' - module Grape module DryTypes # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index ef3bdec080..d93546061f 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/dsl/headers' - module Grape module DSL module InsideRoute diff --git a/lib/grape/eager_load.rb b/lib/grape/eager_load.rb deleted file mode 100644 index ef7bc3ec7c..0000000000 --- a/lib/grape/eager_load.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -Grape.eager_load! -Grape::Http.eager_load! -Grape::Exceptions.eager_load! -Grape::Extensions.eager_load! -Grape::Extensions::ActiveSupport.eager_load! -Grape::Extensions::Hashie.eager_load! -Grape::Middleware.eager_load! -Grape::Middleware::Auth.eager_load! -Grape::Middleware::Versioner.eager_load! -Grape::Util.eager_load! -Grape::ErrorFormatter.eager_load! -Grape::Formatter.eager_load! -Grape::Parser.eager_load! -Grape::DSL.eager_load! -Grape::API.eager_load! -Grape::Presenters.eager_load! -Grape::ServeStream.eager_load! -Rack::Head # AutoLoads the Rack::Head diff --git a/lib/grape/util/env.rb b/lib/grape/env.rb similarity index 100% rename from lib/grape/util/env.rb rename to lib/grape/env.rb diff --git a/lib/grape/exceptions/validation.rb b/lib/grape/exceptions/validation.rb index b66fc46c97..8d368d2776 100644 --- a/lib/grape/exceptions/validation.rb +++ b/lib/grape/exceptions/validation.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/exceptions/base' - module Grape module Exceptions class Validation < Grape::Exceptions::Base diff --git a/lib/grape/exceptions/validation_errors.rb b/lib/grape/exceptions/validation_errors.rb index 4b3d5b9e00..09e4a37b0a 100644 --- a/lib/grape/exceptions/validation_errors.rb +++ b/lib/grape/exceptions/validation_errors.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/exceptions/base' - module Grape module Exceptions class ValidationErrors < Grape::Exceptions::Base diff --git a/lib/grape/http/headers.rb b/lib/grape/http/headers.rb index a7e5984f75..ae0989a3b3 100644 --- a/lib/grape/http/headers.rb +++ b/lib/grape/http/headers.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/util/lazy_object' - module Grape module Http module Headers @@ -11,7 +9,11 @@ module Headers REQUEST_METHOD = 'REQUEST_METHOD' QUERY_STRING = 'QUERY_STRING' - if Grape.lowercase_headers? + def self.lowercase? + Rack::CONTENT_TYPE == 'content-type' + end + + if lowercase? ALLOW = 'allow' LOCATION = 'location' TRANSFER_ENCODING = 'transfer-encoding' @@ -32,7 +34,7 @@ module Headers OPTIONS = 'OPTIONS' SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze - SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze } + SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::Lazy::Object.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze } HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION' HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING' @@ -40,7 +42,7 @@ module Headers FORMAT = 'format' - HTTP_HEADERS = Grape::Util::LazyObject.new do + HTTP_HEADERS = Grape::Util::Lazy::Object.new do common_http_headers = %w[ Version Host diff --git a/lib/grape/util/json.rb b/lib/grape/json.rb similarity index 72% rename from lib/grape/util/json.rb rename to lib/grape/json.rb index 26695e92ac..a30014538f 100644 --- a/lib/grape/util/json.rb +++ b/lib/grape/json.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -require 'json' - module Grape - if Object.const_defined? :MultiJson + if defined?(::MultiJson) Json = ::MultiJson else Json = ::JSON diff --git a/lib/grape/middleware/auth/base.rb b/lib/grape/middleware/auth/base.rb index 67a1a53bbf..d7703cd783 100644 --- a/lib/grape/middleware/auth/base.rb +++ b/lib/grape/middleware/auth/base.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'rack/auth/basic' - module Grape module Middleware module Auth diff --git a/lib/grape/middleware/auth/dsl.rb b/lib/grape/middleware/auth/dsl.rb index eb27588b7d..598358d9d6 100644 --- a/lib/grape/middleware/auth/dsl.rb +++ b/lib/grape/middleware/auth/dsl.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'rack/auth/basic' - module Grape module Middleware module Auth diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index 5eb998fc08..87f2429fff 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/dsl/headers' - module Grape module Middleware class Base diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index 545519a1a1..dedb4cd586 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' - module Grape module Middleware class Error < Base diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index 0e87373d0b..2f0f7f0dfa 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' - module Grape module Middleware class Formatter < Base diff --git a/lib/grape/middleware/globals.rb b/lib/grape/middleware/globals.rb index 850f241967..10d5029dcc 100644 --- a/lib/grape/middleware/globals.rb +++ b/lib/grape/middleware/globals.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' - module Grape module Middleware class Globals < Base diff --git a/lib/grape/middleware/versioner/accept_version_header.rb b/lib/grape/middleware/versioner/accept_version_header.rb index 6198ae0cfa..98237e9476 100644 --- a/lib/grape/middleware/versioner/accept_version_header.rb +++ b/lib/grape/middleware/versioner/accept_version_header.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' - module Grape module Middleware module Versioner diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index f8c9f28230..33ea25660b 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -1,9 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' -require 'grape/util/media_type' -require 'grape/util/accept_header_handler' - module Grape module Middleware module Versioner @@ -37,11 +33,13 @@ def before content_types: content_types, allowed_methods: env[Grape::Env::GRAPE_ALLOWED_METHODS] ) do |media_type| - env[Grape::Env::API_TYPE] = media_type.type - env[Grape::Env::API_SUBTYPE] = media_type.subtype - env[Grape::Env::API_VENDOR] = media_type.vendor - env[Grape::Env::API_VERSION] = media_type.version - env[Grape::Env::API_FORMAT] = media_type.format + 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 end diff --git a/lib/grape/middleware/versioner/param.rb b/lib/grape/middleware/versioner/param.rb index 8e9fe36c0f..69b1d0f48f 100644 --- a/lib/grape/middleware/versioner/param.rb +++ b/lib/grape/middleware/versioner/param.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' - module Grape module Middleware module Versioner diff --git a/lib/grape/middleware/versioner/path.rb b/lib/grape/middleware/versioner/path.rb index ec15a6d7cf..d6c3e90dda 100644 --- a/lib/grape/middleware/versioner/path.rb +++ b/lib/grape/middleware/versioner/path.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' - module Grape module Middleware module Versioner diff --git a/lib/grape/namespace.rb b/lib/grape/namespace.rb index 3473d3efb1..8375e3a561 100644 --- a/lib/grape/namespace.rb +++ b/lib/grape/namespace.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/util/cache' - module Grape # A container for endpoints or other namespaces, which allows for both # logical grouping of endpoints as well as sharing common configuration. diff --git a/lib/grape/path.rb b/lib/grape/path.rb index e08725e52e..e6fa540e7e 100644 --- a/lib/grape/path.rb +++ b/lib/grape/path.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/util/cache' - module Grape # Represents a path to an endpoint. class Path diff --git a/lib/grape/request.rb b/lib/grape/request.rb index c907f0b4ab..8b75da98ea 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/util/lazy_object' - module Grape class Request < Rack::Request HTTP_PREFIX = 'HTTP_' @@ -36,7 +34,7 @@ def grape_routing_args end def build_headers - Grape::Util::LazyObject.new do + Grape::Util::Lazy::Object.new do env.each_pair.with_object({}) do |(k, v), headers| next unless k.to_s.start_with? HTTP_PREFIX @@ -46,7 +44,7 @@ def build_headers end end - if Grape.lowercase_headers? + if Grape::Http::Headers.lowercase? def transform_header(header) -header[5..].tr('_', '-').downcase end diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 51107efbab..7d9dd85641 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -1,9 +1,5 @@ # frozen_string_literal: true -require 'grape/router/route' -require 'grape/router/greedy_route' -require 'grape/util/cache' - module Grape class Router attr_reader :map, :compiled diff --git a/lib/grape/router/greedy_route.rb b/lib/grape/router/greedy_route.rb index 765aa83de9..787c1265b6 100644 --- a/lib/grape/router/greedy_route.rb +++ b/lib/grape/router/greedy_route.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'grape/router/attribute_translator' -require 'forwardable' - # Act like a Grape::Router::Route but for greedy_match # see @neutral_map diff --git a/lib/grape/router/pattern.rb b/lib/grape/router/pattern.rb index a1fce07a0b..0f646bea90 100644 --- a/lib/grape/router/pattern.rb +++ b/lib/grape/router/pattern.rb @@ -1,9 +1,5 @@ # frozen_string_literal: true -require 'forwardable' -require 'mustermann/grape' -require 'grape/util/cache' - module Grape class Router class Pattern diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index 7d4a912a3b..2ee3bb89fb 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -1,9 +1,5 @@ # frozen_string_literal: true -require 'grape/router/pattern' -require 'grape/router/attribute_translator' -require 'forwardable' - module Grape class Router class Route diff --git a/lib/grape/util/accept/header.rb b/lib/grape/util/accept/header.rb new file mode 100644 index 0000000000..3f6b3a67a9 --- /dev/null +++ b/lib/grape/util/accept/header.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Grape + module Util + module Accept + module Header + ALLOWED_CHARACTERS = %r{^([a-z*]+)/([a-z0-9*&\^\-_#{$ERROR_INFO}.+]+)(?:;([a-z0-9=;]+))?$}.freeze + class << self + # Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44 + def parse_media_type(media_type) + # see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names + m = media_type&.match(ALLOWED_CHARACTERS) + m ? [m[1], m[2], m[3] || ''] : [] + end + end + end + end + end +end diff --git a/lib/grape/util/accept_header_handler.rb b/lib/grape/util/accept_header_handler.rb index 25d7036319..c7fc7bee9a 100644 --- a/lib/grape/util/accept_header_handler.rb +++ b/lib/grape/util/accept_header_handler.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/util/media_type' - module Grape module Util class AcceptHeaderHandler diff --git a/lib/grape/util/cache.rb b/lib/grape/util/cache.rb index b58f432400..7514296c2b 100644 --- a/lib/grape/util/cache.rb +++ b/lib/grape/util/cache.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'singleton' -require 'forwardable' - module Grape module Util class Cache diff --git a/lib/grape/util/endpoint_configuration.rb b/lib/grape/util/endpoint_configuration.rb index 90ad256f88..4985562551 100644 --- a/lib/grape/util/endpoint_configuration.rb +++ b/lib/grape/util/endpoint_configuration.rb @@ -2,7 +2,7 @@ module Grape module Util - class EndpointConfiguration < LazyValueHash + class EndpointConfiguration < Lazy::ValueHash end end end diff --git a/lib/grape/util/inheritable_values.rb b/lib/grape/util/inheritable_values.rb index 6b43f8ca2b..48cebb23aa 100644 --- a/lib/grape/util/inheritable_values.rb +++ b/lib/grape/util/inheritable_values.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'base_inheritable' - module Grape module Util class InheritableValues < BaseInheritable diff --git a/lib/grape/util/lazy/block.rb b/lib/grape/util/lazy/block.rb new file mode 100644 index 0000000000..a47d44b094 --- /dev/null +++ b/lib/grape/util/lazy/block.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Grape + module Util + module Lazy + class Block + def initialize(&new_block) + @block = new_block + end + + def evaluate_from(configuration) + @block.call(configuration) + end + + def evaluate + @block.call({}) + end + + def lazy? + true + end + + def to_s + evaluate.to_s + end + end + end + end +end diff --git a/lib/grape/util/lazy/object.rb b/lib/grape/util/lazy/object.rb new file mode 100644 index 0000000000..6c10dadfcc --- /dev/null +++ b/lib/grape/util/lazy/object.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Based on https://github.com/HornsAndHooves/lazy_object + +module Grape + module Util + module Lazy + class Object < BasicObject + attr_reader :callable + + def initialize(&callable) + @callable = callable + end + + def __target_object__ + @__target_object__ ||= callable.call + end + + def ==(other) + __target_object__ == other + end + + def !=(other) + __target_object__ != other + end + + def ! + !__target_object__ + end + + def method_missing(method_name, *args, &block) + if __target_object__.respond_to?(method_name) + __target_object__.send(method_name, *args, &block) + else + super + end + end + + def respond_to_missing?(method_name, include_priv = false) + __target_object__.respond_to?(method_name, include_priv) + end + end + end + end +end diff --git a/lib/grape/util/lazy/value.rb b/lib/grape/util/lazy/value.rb new file mode 100644 index 0000000000..59b7999470 --- /dev/null +++ b/lib/grape/util/lazy/value.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Grape + module Util + module Lazy + class Value + attr_reader :access_keys + + def initialize(value, access_keys = []) + @value = value + @access_keys = access_keys + end + + def evaluate_from(configuration) + matching_lazy_value = configuration.fetch(@access_keys) + matching_lazy_value.evaluate + end + + def evaluate + @value + end + + def lazy? + true + end + + def reached_by(parent_access_keys, access_key) + @access_keys = parent_access_keys + [access_key] + self + end + + def to_s + evaluate.to_s + end + end + end + end +end diff --git a/lib/grape/util/lazy/value_array.rb b/lib/grape/util/lazy/value_array.rb new file mode 100644 index 0000000000..b4c6a88ab1 --- /dev/null +++ b/lib/grape/util/lazy/value_array.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Grape + module Util + module Lazy + class ValueArray < ValueEnumerable + def initialize(array) + super + @value_hash = [] + array.each_with_index do |value, index| + self[index] = value + end + end + + def evaluate + @value_hash.map(&:evaluate) + end + end + end + end +end diff --git a/lib/grape/util/lazy/value_enumerable.rb b/lib/grape/util/lazy/value_enumerable.rb new file mode 100644 index 0000000000..ce15693aac --- /dev/null +++ b/lib/grape/util/lazy/value_enumerable.rb @@ -0,0 +1,34 @@ +# 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 diff --git a/lib/grape/util/lazy/value_hash.rb b/lib/grape/util/lazy/value_hash.rb new file mode 100644 index 0000000000..c3447a0ddc --- /dev/null +++ b/lib/grape/util/lazy/value_hash.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Grape + module Util + module Lazy + class ValueHash < ValueEnumerable + def initialize(hash) + super + @value_hash = ActiveSupport::HashWithIndifferentAccess.new + hash.each do |key, value| + self[key] = value + end + end + + def evaluate + @value_hash.transform_values(&:evaluate) + end + end + end + end +end diff --git a/lib/grape/util/lazy_block.rb b/lib/grape/util/lazy_block.rb deleted file mode 100644 index 6e7d18b8a1..0000000000 --- a/lib/grape/util/lazy_block.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Grape - module Util - class LazyBlock - def initialize(&new_block) - @block = new_block - end - - def evaluate_from(configuration) - @block.call(configuration) - end - - def evaluate - @block.call({}) - end - - def lazy? - true - end - - def to_s - evaluate.to_s - end - end - end -end diff --git a/lib/grape/util/lazy_object.rb b/lib/grape/util/lazy_object.rb deleted file mode 100644 index 22ec7c440d..0000000000 --- a/lib/grape/util/lazy_object.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -# Based on https://github.com/HornsAndHooves/lazy_object - -module Grape - module Util - class LazyObject < BasicObject - attr_reader :callable - - def initialize(&callable) - @callable = callable - end - - def __target_object__ - @__target_object__ ||= callable.call - end - - def ==(other) - __target_object__ == other - end - - def !=(other) - __target_object__ != other - end - - def ! - !__target_object__ - end - - def method_missing(method_name, *args, &block) - if __target_object__.respond_to?(method_name) - __target_object__.send(method_name, *args, &block) - else - super - end - end - - def respond_to_missing?(method_name, include_priv = false) - __target_object__.respond_to?(method_name, include_priv) - end - end - end -end diff --git a/lib/grape/util/lazy_value.rb b/lib/grape/util/lazy_value.rb deleted file mode 100644 index cc732562f9..0000000000 --- a/lib/grape/util/lazy_value.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -module Grape - module Util - class LazyValue - attr_reader :access_keys - - def initialize(value, access_keys = []) - @value = value - @access_keys = access_keys - end - - def evaluate_from(configuration) - matching_lazy_value = configuration.fetch(@access_keys) - matching_lazy_value.evaluate - end - - def evaluate - @value - end - - def lazy? - true - end - - def reached_by(parent_access_keys, access_key) - @access_keys = parent_access_keys + [access_key] - self - end - - def to_s - evaluate.to_s - end - end - - class LazyValueEnumerable < LazyValue - def [](key) - if @value_hash[key].nil? - LazyValue.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 - LazyValueHash.new(value) - when Array - LazyValueArray.new(value) - else - LazyValue.new(value) - end - end - end - - class LazyValueArray < LazyValueEnumerable - def initialize(array) - super - @value_hash = [] - array.each_with_index do |value, index| - self[index] = value - end - end - - def evaluate - @value_hash.map(&:evaluate) - end - end - - class LazyValueHash < LazyValueEnumerable - def initialize(hash) - super - @value_hash = ActiveSupport::HashWithIndifferentAccess.new - hash.each do |key, value| - self[key] = value - end - end - - def evaluate - @value_hash.transform_values(&:evaluate) - end - end - end -end diff --git a/lib/grape/util/reverse_stackable_values.rb b/lib/grape/util/reverse_stackable_values.rb index 171f390f73..3b008a926c 100644 --- a/lib/grape/util/reverse_stackable_values.rb +++ b/lib/grape/util/reverse_stackable_values.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'stackable_values' - module Grape module Util class ReverseStackableValues < StackableValues diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index 01a5681965..c19e2f5828 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'base_inheritable' - module Grape module Util class StackableValues < BaseInheritable diff --git a/lib/grape/validations.rb b/lib/grape/validations.rb index 74d534f377..9ae22ae6a1 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Grape - # Registry to store and locate known Validators. module Validations module_function @@ -12,7 +11,7 @@ def validators # Register a new validator, so it can be used to validate parameters. # @param short_name [String] all lower-case, no spaces # @param klass [Class] the validator class. Should inherit from - # Validations::Base. + # Grape::Validations::Validators::Base. def register_validator(short_name, klass) validators[short_name] = klass end @@ -21,14 +20,11 @@ def deregister_validator(short_name) validators.delete(short_name) end - # Find a validator and if not found will try to load it def require_validator(short_name) str_name = short_name.to_s - validators.fetch(str_name) do - Grape::Validations::Validators.const_get(:"#{str_name.camelize}Validator") - end + validators.fetch(str_name) { Grape::Validations::Validators.const_get(:"#{str_name.camelize}Validator") } rescue NameError - raise Grape::Exceptions::UnknownValidator.new(short_name) + raise Grape::Exceptions::UnknownValidator, short_name end end end diff --git a/lib/grape/validations/attributes_doc.rb b/lib/grape/validations/attributes_doc.rb index a046707039..f9e15c148a 100644 --- a/lib/grape/validations/attributes_doc.rb +++ b/lib/grape/validations/attributes_doc.rb @@ -2,56 +2,55 @@ module Grape module Validations - class ParamsScope - # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an - # internal API), the class only cleans up attributes to avoid junk in RAM. - class AttributesDoc - attr_accessor :type, :values - - # @param api [Grape::API::Instance] - # @param scope [Validations::ParamsScope] - def initialize(api, scope) - @api = api - @scope = scope - @type = type - end + # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an + # internal API), the class only cleans up attributes to avoid junk in RAM. + + class AttributesDoc + attr_accessor :type, :values + + # @param api [Grape::API::Instance] + # @param scope [Validations::ParamsScope] + def initialize(api, scope) + @api = api + @scope = scope + @type = type + end - def extract_details(validations) - details[:required] = validations.key?(:presence) + def extract_details(validations) + details[:required] = validations.key?(:presence) - desc = validations.delete(:desc) || validations.delete(:description) + desc = validations.delete(:desc) || validations.delete(:description) - details[:desc] = desc if desc + details[:desc] = desc if desc - documentation = validations.delete(:documentation) + documentation = validations.delete(:documentation) - details[:documentation] = documentation if documentation + details[:documentation] = documentation if documentation - details[:default] = validations[:default] if validations.key?(:default) - end - - def document(attrs) - return if @api.namespace_inheritable(:do_not_document) + details[:default] = validations[:default] if validations.key?(:default) + end - details[:type] = type.to_s if type - details[:values] = values if values + def document(attrs) + return if @api.namespace_inheritable(:do_not_document) - documented_attrs = attrs.each_with_object({}) do |name, memo| - memo[@scope.full_name(name)] = details - end + details[:type] = type.to_s if type + details[:values] = values if values - @api.namespace_stackable(:params, documented_attrs) + documented_attrs = attrs.each_with_object({}) do |name, memo| + memo[@scope.full_name(name)] = details end - def required - details[:required] - end + @api.namespace_stackable(:params, documented_attrs) + end - protected + def required + details[:required] + end - def details - @details ||= {} - end + protected + + def details + @details ||= {} end end end diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 6eaf7abaa4..026e7df9ce 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'attributes_doc' - module Grape module Validations class ParamsScope diff --git a/lib/grape/validations/types.rb b/lib/grape/validations/types.rb index 1e45c4f7fe..86f9c9b601 100644 --- a/lib/grape/validations/types.rb +++ b/lib/grape/validations/types.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'grape/validations/types/json' -require 'grape/validations/types/file' - module Grape module Validations # Module for code related to grape's system for diff --git a/lib/grape/validations/types/array_coercer.rb b/lib/grape/validations/types/array_coercer.rb index c6d6b106e1..ec4ca41dee 100644 --- a/lib/grape/validations/types/array_coercer.rb +++ b/lib/grape/validations/types/array_coercer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'dry_type_coercer' - module Grape module Validations module Types diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index c55e048dbd..5f15a1a0cb 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -1,94 +1,92 @@ # frozen_string_literal: true -require_relative 'array_coercer' -require_relative 'set_coercer' -require_relative 'primitive_coercer' - module Grape module Validations module Types - # Chooses the best coercer for the given type. For example, if the type - # is Integer, it will return a coercer which will be able to coerce a value - # to the integer. - # - # There are a few very special coercers which might be returned. - # - # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when - # the given type implies values in an array with different types. - # For example, +[Integer, String]+ allows integer and string values in - # an array. - # - # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when - # a method is specified by a user with +coerce_with+ option or the user - # specifies a custom type which implements requirments of - # +Grape::Types::CustomTypeCoercer+. - # - # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the - # previous one, but it expects an array or set of values having a custom - # type implemented by the user. - # - # There is also a group of custom types implemented by Grape, check - # +Grape::Validations::Types::SPECIAL+ to get the full list. - # - # @param type [Class] the type to which input strings - # should be coerced - # @param method [Class,#call] the coercion method to use - # @return [Object] object to be used - # for coercion and type validation - def self.build_coercer(type, method: nil, strict: false) - cache_instance(type, method, strict) do - create_coercer_instance(type, method, strict) + module BuildCoercer + # Chooses the best coercer for the given type. For example, if the type + # is Integer, it will return a coercer which will be able to coerce a value + # to the integer. + # + # There are a few very special coercers which might be returned. + # + # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when + # the given type implies values in an array with different types. + # For example, +[Integer, String]+ allows integer and string values in + # an array. + # + # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when + # a method is specified by a user with +coerce_with+ option or the user + # specifies a custom type which implements requirments of + # +Grape::Types::CustomTypeCoercer+. + # + # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the + # previous one, but it expects an array or set of values having a custom + # type implemented by the user. + # + # There is also a group of custom types implemented by Grape, check + # +Grape::Validations::Types::SPECIAL+ to get the full list. + # + # @param type [Class] the type to which input strings + # should be coerced + # @param method [Class,#call] the coercion method to use + # @return [Object] object to be used + # for coercion and type validation + def self.build_coercer(type, method: nil, strict: false) + cache_instance(type, method, strict) do + create_coercer_instance(type, method, strict) + end end - end - def self.create_coercer_instance(type, method, strict) - # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!! - type = Types.map_special(type) + def self.create_coercer_instance(type, method, strict) + # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!! + type = Types.map_special(type) - # Use a special coercer for multiply-typed parameters. - if Types.multiple?(type) - MultipleTypeCoercer.new(type, method) + # Use a special coercer for multiply-typed parameters. + if Types.multiple?(type) + MultipleTypeCoercer.new(type, method) - # Use a special coercer for custom types and coercion methods. - elsif method || Types.custom?(type) - CustomTypeCoercer.new(type, method) + # Use a special coercer for custom types and coercion methods. + elsif method || Types.custom?(type) + CustomTypeCoercer.new(type, method) - # Special coercer for collections of types that implement a parse method. - # CustomTypeCoercer (above) already handles such types when an explicit coercion - # method is supplied. - elsif Types.collection_of_custom?(type) - Types::CustomTypeCollectionCoercer.new( - Types.map_special(type.first), type.is_a?(Set) - ) - else - DryTypeCoercer.coercer_instance_for(type, strict) + # Special coercer for collections of types that implement a parse method. + # CustomTypeCoercer (above) already handles such types when an explicit coercion + # method is supplied. + elsif Types.collection_of_custom?(type) + Types::CustomTypeCollectionCoercer.new( + Types.map_special(type.first), type.is_a?(Set) + ) + else + DryTypeCoercer.coercer_instance_for(type, strict) + end end - end - def self.cache_instance(type, method, strict, &_block) - key = cache_key(type, method, strict) + def self.cache_instance(type, method, strict, &_block) + key = cache_key(type, method, strict) - return @__cache[key] if @__cache.key?(key) + return @__cache[key] if @__cache.key?(key) - instance = yield + instance = yield - @__cache_write_lock.synchronize do - @__cache[key] = instance - end + @__cache_write_lock.synchronize do + @__cache[key] = instance + end - instance - end + instance + end - def self.cache_key(type, method, strict) - [type, method, strict].each_with_object(+'_') do |val, memo| - next if val.nil? + def self.cache_key(type, method, strict) + [type, method, strict].each_with_object(+'_') do |val, memo| + next if val.nil? - memo << '_' << val.to_s + memo << '_' << val.to_s + end end - end - instance_variable_set(:@__cache, {}) - instance_variable_set(:@__cache_write_lock, Mutex.new) + instance_variable_set(:@__cache, {}) + instance_variable_set(:@__cache_write_lock, Mutex.new) + end end end end diff --git a/lib/grape/validations/types/dry_type_coercer.rb b/lib/grape/validations/types/dry_type_coercer.rb index cc529c07c8..1067eaf3a8 100644 --- a/lib/grape/validations/types/dry_type_coercer.rb +++ b/lib/grape/validations/types/dry_type_coercer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'dry-types' - module DryTypes # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is # a container in this case. Check documentation for more information @@ -24,9 +22,7 @@ class << self # collection_coercer_for(Array) # #=> Grape::Validations::Types::ArrayCoercer def collection_coercer_for(type) - collection_coercers.fetch(type) do - DryTypeCoercer.collection_coercers[type] = Grape::Validations::Types.const_get(:"#{type.name.camelize}Coercer") - end + Grape::Validations::Types.const_get(:"#{type.name.camelize}Coercer") end # Returns an instance of a coercer for a given type @@ -37,12 +33,6 @@ def coercer_instance_for(type, strict = false) # so we need to figure out the actual type collection_coercer_for(type.class).new(type, strict) end - - protected - - def collection_coercers - @collection_coercers ||= {} - end end def initialize(type, strict = false) diff --git a/lib/grape/validations/types/json.rb b/lib/grape/validations/types/json.rb index 3240de27b6..61b01131ce 100644 --- a/lib/grape/validations/types/json.rb +++ b/lib/grape/validations/types/json.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'json' - module Grape module Validations module Types diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb index e2e3f9df54..e59b5c6eb8 100644 --- a/lib/grape/validations/types/primitive_coercer.rb +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'dry_type_coercer' - module Grape module Validations module Types diff --git a/lib/grape/validations/types/set_coercer.rb b/lib/grape/validations/types/set_coercer.rb index 2d385c935d..9b1b311f8c 100644 --- a/lib/grape/validations/types/set_coercer.rb +++ b/lib/grape/validations/types/set_coercer.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'set' -require_relative 'array_coercer' - module Grape module Validations module Types diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index c76eb4b394..3dd49fd797 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -59,6 +59,7 @@ def validate!(params) end def self.inherited(klass) + super return if klass.name.blank? short_validator_name = klass.name.demodulize.underscore.delete_suffix('_validator') diff --git a/lib/grape/util/xml.rb b/lib/grape/xml.rb similarity index 80% rename from lib/grape/util/xml.rb rename to lib/grape/xml.rb index d948f8012f..85287814a3 100644 --- a/lib/grape/util/xml.rb +++ b/lib/grape/xml.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Grape - if Object.const_defined? :MultiXml + if defined?(::MultiXml) Xml = ::MultiXml else Xml = ::ActiveSupport::XmlMini diff --git a/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index a6b7a629c1..49571a803e 100644 --- a/spec/grape/api/custom_validations_spec.rb +++ b/spec/grape/api/custom_validations_spec.rb @@ -35,13 +35,7 @@ def validate_param!(attr_name, params) end let(:app) { Rack::Builder.new(subject) } - before do - described_class.register_validator('default_length', default_length_validator) - end - - after do - described_class.deregister_validator('default_length') - end + before { stub_const('Grape::Validations::Validators::DefaultLengthValidator', default_length_validator) } it 'under 140 characters' do get '/', text: 'abc' @@ -83,13 +77,7 @@ def validate(request) end let(:app) { Rack::Builder.new(subject) } - before do - described_class.register_validator('in_body', in_body_validator) - end - - after do - described_class.deregister_validator('in_body') - end + before { stub_const('Grape::Validations::Validators::InBodyValidator', in_body_validator) } it 'allows field in body' do get '/', text: 'abc' @@ -125,13 +113,7 @@ def validate_param!(attr_name, _params) end let(:app) { Rack::Builder.new(subject) } - before do - described_class.register_validator('with_message_key', message_key_validator) - end - - after do - described_class.deregister_validator('with_message_key') - end + before { stub_const('Grape::Validations::Validators::WithMessageKeyValidator', message_key_validator) } it 'fails with message' do get '/', text: 'foobar' @@ -169,20 +151,15 @@ def validate(request) end def access_header - Grape.lowercase_headers? ? 'x-access-token' : 'X-Access-Token' + Grape::Http::Headers.lowercase? ? 'x-access-token' : 'X-Access-Token' end end end + let(:app) { Rack::Builder.new(subject) } - let(:x_access_token_header) { Grape.lowercase_headers? ? 'x-access-token' : 'X-Access-Token' } + let(:x_access_token_header) { Grape::Http::Headers.lowercase? ? 'x-access-token' : 'X-Access-Token' } - before do - described_class.register_validator('admin', admin_validator) - end - - after do - described_class.deregister_validator('admin') - end + before { stub_const('Grape::Validations::Validators::AdminValidator', admin_validator) } it 'fail when non-admin user sets an admin field' do get '/', admin_field: 'tester', non_admin_field: 'toaster' @@ -216,4 +193,37 @@ def access_header expect(last_response.body).to include 'Can not set Admin only field.' end end + + describe 'using a custom validator with instance variable' do + let(:validator_type) do + Class.new(Grape::Validations::Validators::Base) do + def validate_param!(_attr_name, _params) + if instance_variable_defined?(:@instance_variable) && @instance_variable + raise Grape::Exceptions::Validation.new(params: ['params'], + message: 'This should never happen') + end + @instance_variable = true + end + end + end + let(:app) do + Class.new(Grape::API) do + params do + optional :param_to_validate, instance_validator: true + optional :another_param_to_validate, instance_validator: true + end + get do + 'noop' + end + end + end + + before { stub_const('Grape::Validations::Validators::InstanceValidatorValidator', validator_type) } + + it 'passes validation every time' do + expect(validator_type).to receive(:new).twice.and_call_original + get '/', param_to_validate: 'value', another_param_to_validate: 'value' + expect(last_response.status).to eq 200 + end + end end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index f8bb785ff6..75f626f1a7 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -998,11 +998,6 @@ def to_txt end describe '.compile!' do - it 'requires the grape/eager_load file' do - expect(app).to receive(:require).with('grape/eager_load').and_return(nil) - app.compile! - end - it 'compiles the instance for rack!' do stubbed_object = double(:instance_for_rack) allow(app).to receive(:instance_for_rack) { stubbed_object } diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index d8e88aeb10..c2443cbe7c 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -146,7 +146,7 @@ def app it 'includes additional request headers' do get '/headers', nil, 'HTTP_X_GRAPE_CLIENT' => '1' - x_grape_client_header = Grape.lowercase_headers? ? 'x-grape-client' : 'X-Grape-Client' + x_grape_client_header = Grape::Http::Headers.lowercase? ? 'x-grape-client' : 'X-Grape-Client' expect(JSON.parse(last_response.body)[x_grape_client_header]).to eq('1') end @@ -154,7 +154,7 @@ def app env = Rack::MockRequest.env_for('/headers') env[:HTTP_SYMBOL_HEADER] = 'Goliath passes symbols' body = read_chunks(subject.call(env)[2]).join - symbol_header = Grape.lowercase_headers? ? 'symbol-header' : 'Symbol-Header' + symbol_header = Grape::Http::Headers.lowercase? ? 'symbol-header' : 'Symbol-Header' expect(JSON.parse(body)[symbol_header]).to eq('Goliath passes symbols') end end diff --git a/spec/grape/request_spec.rb b/spec/grape/request_spec.rb index b84b6dffdc..94d9f505a0 100644 --- a/spec/grape/request_spec.rb +++ b/spec/grape/request_spec.rb @@ -90,7 +90,7 @@ module Grape } end let(:x_grape_is_cool_header) do - Grape.lowercase_headers? ? 'x-grape-is-cool' : 'X-Grape-Is-Cool' + Grape::Http::Headers.lowercase? ? 'x-grape-is-cool' : 'X-Grape-Is-Cool' end it 'cuts HTTP_ prefix and capitalizes header name words' do @@ -120,7 +120,7 @@ module Grape default_env.merge(request_headers) end let(:grape_likes_symbolic_header) do - Grape.lowercase_headers? ? 'grape-likes-symbolic' : 'Grape-Likes-Symbolic' + Grape::Http::Headers.lowercase? ? 'grape-likes-symbolic' : 'Grape-Likes-Symbolic' end it 'converts them to string' do diff --git a/spec/grape/util/accept_header_handler_spec.rb b/spec/grape/util/accept_header_handler_spec.rb index ac4ff3f461..de85ed26b5 100644 --- a/spec/grape/util/accept_header_handler_spec.rb +++ b/spec/grape/util/accept_header_handler_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'grape/util/accept_header_handler' - RSpec.describe Grape::Util::AcceptHeaderHandler do subject(:match_best_quality_media_type!) { instance.match_best_quality_media_type! } diff --git a/spec/grape/validations/attributes_doc_spec.rb b/spec/grape/validations/attributes_doc_spec.rb index a21ce3591c..f1ae0c93e1 100644 --- a/spec/grape/validations/attributes_doc_spec.rb +++ b/spec/grape/validations/attributes_doc_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe Grape::Validations::ParamsScope::AttributesDoc do +describe Grape::Validations::AttributesDoc do shared_examples 'an optional doc attribute' do |attr| it 'does not mention it' do expected_opts.delete(attr) diff --git a/spec/grape/validations/instance_behaivour_spec.rb b/spec/grape/validations/instance_behaivour_spec.rb deleted file mode 100644 index c8df967564..0000000000 --- a/spec/grape/validations/instance_behaivour_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -describe 'Validator with instance variables' do - let(:validator_type) do - Class.new(Grape::Validations::Validators::Base) do - def validate_param!(_attr_name, _params) - if instance_variable_defined?(:@instance_variable) && @instance_variable - raise Grape::Exceptions::Validation.new(params: ['params'], - message: 'This should never happen') - end - @instance_variable = true - end - end - end - let(:app) do - Class.new(Grape::API) do - params do - optional :param_to_validate, instance_validator: true - optional :another_param_to_validate, instance_validator: true - end - get do - 'noop' - end - end - end - - before do - Grape::Validations.register_validator('instance_validator', validator_type) - end - - after do - Grape::Validations.deregister_validator('instance_validator') - end - - it 'passes validation every time' do - expect(validator_type).to receive(:new).exactly(4).times.and_call_original - - 2.times do - get '/', param_to_validate: 'value', another_param_to_validate: 'value' - expect(last_response.status).to eq 200 - end - end -end diff --git a/spec/grape/validations/validators/base_spec.rb b/spec/grape/validations/validators/base_spec.rb deleted file mode 100644 index 9f1ff97601..0000000000 --- a/spec/grape/validations/validators/base_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Grape::Validations::Validators::Base do - describe '#inherited' do - context 'when validator is anonymous' do - subject(:custom_validator) { Class.new(described_class) } - - it 'does not register the validator' do - expect(Grape::Validations).not_to receive(:register_validator) - custom_validator - end - end - - # Anonymous class does not have a name and class A < B would leak. - # Simulates inherited callback - context "when validator's underscored name does not end with _validator" do - subject(:custom_validator) { described_class.inherited(TestModule::CustomValidatorABC) } - - before { stub_const('TestModule::CustomValidatorABC', Class.new) } - - it 'registers the custom validator with a short name' do - expect(Grape::Validations).to receive(:register_validator).with('custom_validator_abc', TestModule::CustomValidatorABC) - custom_validator - end - end - - context "when validator's underscored name ends with _validator" do - subject(:custom_validator) { described_class.inherited(TestModule::CustomValidator) } - - before { stub_const('TestModule::CustomValidator', Class.new) } - - it 'registers the custom validator with short name not ending with validator' do - expect(Grape::Validations).to receive(:register_validator).with('custom', TestModule::CustomValidator) - custom_validator - end - end - end -end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 58ab8a5792..12acb33d3c 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -3,9 +3,8 @@ describe Grape::Validations do subject { Class.new(Grape::API) } - def app - subject - end + let(:app) { subject } + let(:declard_params) {} def declared_params subject.namespace_stackable(:declared_params).flatten @@ -499,14 +498,7 @@ def validate_param!(attr_name, params) end before do - described_class.register_validator('date_range', date_range_validator) - end - - after do - described_class.deregister_validator('date_range') - end - - before do + stub_const('Grape::Validations::Validators::DateRangeValidator', date_range_validator) subject.params do optional :date_range, date_range: true, type: Hash do requires :from, type: Integer @@ -1198,13 +1190,7 @@ def validate_param!(attr_name, params) end end - before do - described_class.register_validator('customvalidator', custom_validator) - end - - after do - described_class.deregister_validator('customvalidator') - end + before { stub_const('Grape::Validations::Validators::CustomvalidatorValidator', custom_validator) } context 'when using optional with a custom validator' do before do @@ -1356,14 +1342,8 @@ def validate_param!(attr_name, params) end before do - described_class.register_validator('customvalidator_with_options', custom_validator_with_options) - end + stub_const('Grape::Validations::Validators::CustomvalidatorWithOptionsValidator', custom_validator_with_options) - after do - described_class.deregister_validator('customvalidator_with_options') - end - - before do subject.params do optional :custom, customvalidator_with_options: { text: 'im custom with options', message: 'is not custom with options!' } end diff --git a/spec/integration/eager_load/eager_load_spec.rb b/spec/integration/eager_load/eager_load_spec.rb deleted file mode 100644 index 174ba99440..0000000000 --- a/spec/integration/eager_load/eager_load_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib')) -require 'grape' - -describe Grape do - it 'eager_load!' do - require 'grape/eager_load' - expect { described_class.eager_load! }.not_to raise_error - end - - it 'compile!' do - expect { Class.new(Grape::API).compile! }.not_to raise_error - end -end diff --git a/spec/integration/multi_json/json_spec.rb b/spec/integration/multi_json/json_spec.rb index 18ac22b724..a4227cdde6 100644 --- a/spec/integration/multi_json/json_spec.rb +++ b/spec/integration/multi_json/json_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe Grape::Json do +describe Grape::Json, if: defined?(::MultiJson) do it 'uses multi_json' do expect(described_class).to eq(::MultiJson) end