From 28bffd7b6607c7303eeb91a86d45f4ff400ec0aa Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sat, 28 Oct 2023 21:50:13 +0200 Subject: [PATCH 01/11] Introduce zeitwerk!! Change some classes base on file structure Remove all require 'grape/**/*' Remove active_support/autoload Replace eager_load file by zeitwerk eager_load Move and rename Grape.lowercase_headers? to Grape::Http::Headers.lowercase? Add headers_spec conditions before running spec Move lazy_value_*** to their own classes Replace parse_media_type_patch by Grape::Util::Accept (stop overriding Rack:Accept::Header) --- UPGRADING.md | 2 +- grape.gemspec | 2 + lib/grape.rb | 283 ++---------------- lib/grape/api.rb | 5 +- lib/grape/api/instance.rb | 2 - lib/grape/content_types.rb | 2 - lib/grape/dry_types.rb | 2 - lib/grape/dsl/inside_route.rb | 8 +- lib/grape/eager_load.rb | 20 -- lib/grape/endpoint.rb | 6 +- lib/grape/error_formatter/base.rb | 12 +- lib/grape/error_formatter/json.rb | 2 +- lib/grape/error_formatter/txt.rb | 2 +- lib/grape/exceptions/validation.rb | 2 - lib/grape/exceptions/validation_errors.rb | 2 - .../hash_with_indifferent_access.rb | 2 +- lib/grape/extensions/hash.rb | 2 +- lib/grape/extensions/hashie/mash.rb | 2 +- lib/grape/formatter/json.rb | 2 +- lib/grape/formatter/serializable_hash.rb | 4 +- lib/grape/http/headers.rb | 8 +- lib/grape/middleware/auth/base.rb | 2 - lib/grape/middleware/auth/dsl.rb | 2 - lib/grape/middleware/auth/strategies.rb | 2 + lib/grape/middleware/base.rb | 4 +- lib/grape/middleware/error.rb | 5 +- lib/grape/middleware/formatter.rb | 22 +- lib/grape/middleware/globals.rb | 8 +- lib/grape/middleware/helpers.rb | 2 +- .../versioner/accept_version_header.rb | 4 +- lib/grape/middleware/versioner/header.rb | 27 +- lib/grape/middleware/versioner/param.rb | 6 +- .../versioner/parse_media_type_patch.rb | 24 -- lib/grape/middleware/versioner/path.rb | 4 +- lib/grape/namespace.rb | 2 - lib/grape/parser/json.rb | 4 +- lib/grape/parser/xml.rb | 4 +- lib/grape/path.rb | 2 - lib/grape/request.rb | 6 +- lib/grape/router.rb | 7 +- lib/grape/router/pattern.rb | 4 +- lib/grape/router/route.rb | 5 - lib/grape/util/accept/header.rb | 19 ++ lib/grape/util/accept/media_type.rb | 13 + lib/grape/util/cache.rb | 3 - lib/grape/util/env.rb | 38 +-- lib/grape/util/inheritable_values.rb | 2 - lib/grape/util/json.rb | 14 +- lib/grape/util/lazy_value.rb | 55 ---- lib/grape/util/lazy_value_array.rb | 19 ++ lib/grape/util/lazy_value_enumerable.rb | 32 ++ lib/grape/util/lazy_value_hash.rb | 20 ++ lib/grape/util/reverse_stackable_values.rb | 2 - lib/grape/util/stackable_values.rb | 2 - lib/grape/util/xml.rb | 12 +- lib/grape/validations/attributes_doc.rb | 73 +++-- lib/grape/validations/params_scope.rb | 2 - lib/grape/validations/types.rb | 3 - lib/grape/validations/types/array_coercer.rb | 2 - lib/grape/validations/types/build_coercer.rb | 139 +++++---- .../validations/types/dry_type_coercer.rb | 2 - lib/grape/validations/types/json.rb | 2 - .../validations/types/primitive_coercer.rb | 2 - lib/grape/validations/types/set_coercer.rb | 3 - spec/grape/api/custom_validations_spec.rb | 4 +- spec/grape/api/invalid_format_spec.rb | 6 +- spec/grape/api_spec.rb | 21 +- spec/grape/endpoint/declared_spec.rb | 4 +- spec/grape/endpoint_spec.rb | 12 +- spec/grape/middleware/base_spec.rb | 2 +- spec/grape/middleware/formatter_spec.rb | 2 +- spec/grape/request_spec.rb | 4 +- spec/grape/validations/attributes_doc_spec.rb | 2 +- .../validations/validators/coerce_spec.rb | 8 +- .../integration/eager_load/eager_load_spec.rb | 5 - spec/integration/multi_json/json_spec.rb | 2 +- spec/integration/multi_xml/xml_spec.rb | 2 +- spec/integration/rack/v2/headers_spec.rb | 3 +- spec/integration/rack/v3/headers_spec.rb | 2 +- 79 files changed, 367 insertions(+), 689 deletions(-) delete mode 100644 lib/grape/eager_load.rb delete mode 100644 lib/grape/middleware/versioner/parse_media_type_patch.rb create mode 100644 lib/grape/util/accept/header.rb create mode 100644 lib/grape/util/accept/media_type.rb create mode 100644 lib/grape/util/lazy_value_array.rb create mode 100644 lib/grape/util/lazy_value_enumerable.rb create mode 100644 lib/grape/util/lazy_value_hash.rb diff --git a/UPGRADING.md b/UPGRADING.md index b9877889ee..1e45c85b91 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -523,7 +523,7 @@ For PUT, POST, PATCH, and DELETE requests where a non-empty body and a "Content- Grape no longer uses `multi_json` or `multi_xml` by default and uses `JSON` and `ActiveSupport::XmlMini` instead. This has no visible impact on JSON processing, but the default behavior of the XML parser has changed. For example, an XML POST containing `Bobby T.` was parsed as `Bobby T.` with `multi_xml`, and as now parsed as `{"__content__"=>"Bobby T."}` with `XmlMini`. -If you were using `MultiJson.load`, `MultiJson.dump` or `MultiXml.parse`, you can substitute those with `Grape::Json.load`, `Grape::Json.dump`, `::Grape::Xml.parse`, or directly with `JSON.load`, `JSON.dump`, `XmlMini.parse`, etc. +If you were using `MultiJson.load`, `MultiJson.dump` or `MultiXml.parse`, you can substitute those with `Grape::Util::Json.load`, `Grape::Util::Json.dump`, `::Grape::Util::Xml.parse`, or directly with `JSON.load`, `JSON.dump`, `XmlMini.parse`, etc. To restore previous behavior, add `multi_json` or `multi_xml` to your `Gemfile` and `require` it. diff --git a/grape.gemspec b/grape.gemspec index 9e53ddea75..a5c11e8d89 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -26,6 +26,8 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0' s.add_runtime_dependency 'rack', '>= 1.3.0' s.add_runtime_dependency 'rack-accept' + s.add_runtime_dependency 'zeitwerk' + s.add_runtime_dependency 'i18n' 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 9eeecbde5a..23480c0855 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -4,10 +4,15 @@ require 'rack' require 'rack/builder' require 'rack/accept' -require 'rack/auth/basic' require 'set' +require 'dry-types' require 'bigdecimal' +require 'forwardable' +require 'json' require 'date' +require 'mustermann/grape' +require 'singleton' +require 'pathname' require 'active_support' require 'active_support/concern' require 'active_support/configurable' @@ -25,11 +30,20 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/duplicable' -require 'active_support/dependencies/autoload' +require 'active_support/core_ext/string/output_safety' require 'active_support/deprecation' require 'active_support/inflector' require 'active_support/notifications' -require 'i18n' +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__) @@ -41,275 +55,12 @@ 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 :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') diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 1fc057bfb3..9914c9d926 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,7 @@ def method_missing(method, *args, &block) end def compile! - require 'grape/eager_load' + Zeitwerk::Loader.eager_load_all 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..8def39f999 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 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 bcfd3e84c8..b55a854031 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 @@ -148,7 +146,7 @@ def declared(*) # The API version as specified in the URL. def version - env[Grape::Env::API_VERSION] + env[Grape::Util::Env::API_VERSION] end def configuration @@ -397,7 +395,7 @@ def present(*args) # route.description # end def route - env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info] + env[Grape::Util::Env::GRAPE_ROUTING_ARGS][:route_info] end # Attempt to locate the Entity class for a given object, if not given @@ -433,7 +431,7 @@ def entity_class_for_obj(object, options) # the given entity_class. def entity_representation_for(entity_class, object, options) embeds = { env: env } - embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION) + embeds[:version] = env[Grape::Util::Env::API_VERSION] if env.key?(Grape::Util::Env::API_VERSION) entity_class.represent(object, **embeds.merge(options)) end end 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/endpoint.rb b/lib/grape/endpoint.rb index 663a3e8f1d..cf9fc8503d 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -219,7 +219,7 @@ def call(env) end def call!(env) - env[Grape::Env::API_ENDPOINT] = self + env[Grape::Util::Env::API_ENDPOINT] = self @env = env @app.call(env) end @@ -247,7 +247,7 @@ def run self.class.run_before_each(self) run_filters befores, :before - if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS]) + if (allowed_methods = env[Grape::Util::Env::GRAPE_ALLOWED_METHODS]) raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options? header Grape::Http::Headers::ALLOW, allowed_methods @@ -313,7 +313,7 @@ def build_stack(helpers) parsers: namespace_stackable_with_hash(:parsers) builder = stack.build - builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run } + builder.run ->(env) { env[Grape::Util::Env::API_ENDPOINT].run } builder.to_app end diff --git a/lib/grape/error_formatter/base.rb b/lib/grape/error_formatter/base.rb index 2a1e758eba..095d598aae 100644 --- a/lib/grape/error_formatter/base.rb +++ b/lib/grape/error_formatter/base.rb @@ -11,23 +11,23 @@ def present(message, env) present_options[:with] = presented_message.delete(:with) end - presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options) + presenter = env[Grape::Util::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options) - unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil? + unless presenter || env[Grape::Util::Env::GRAPE_ROUTING_ARGS].nil? # env['api.endpoint'].route does not work when the error occurs within a middleware # the Endpoint does not have a valid env at this moment - http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || [] + http_codes = env[Grape::Util::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || [] found_code = http_codes.find do |http_code| - (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent) - end if env[Grape::Env::API_ENDPOINT].request + (http_code[0].to_i == env[Grape::Util::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent) + end if env[Grape::Util::Env::API_ENDPOINT].request presenter = found_code[2] if found_code end if presenter embeds = { env: env } - embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION) + embeds[:version] = env[Grape::Util::Env::API_VERSION] if env.key?(Grape::Util::Env::API_VERSION) presented_message = presenter.represent(presented_message, embeds).serializable_hash end diff --git a/lib/grape/error_formatter/json.rb b/lib/grape/error_formatter/json.rb index 5359192340..91035126a7 100644 --- a/lib/grape/error_formatter/json.rb +++ b/lib/grape/error_formatter/json.rb @@ -12,7 +12,7 @@ def call(message, backtrace, options = {}, env = nil, original_exception = nil) rescue_options = options[:rescue_options] || {} result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty? result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception - ::Grape::Json.dump(result) + ::Grape::Util::Json.dump(result) end private diff --git a/lib/grape/error_formatter/txt.rb b/lib/grape/error_formatter/txt.rb index 76d3cb3f18..d109113c6a 100644 --- a/lib/grape/error_formatter/txt.rb +++ b/lib/grape/error_formatter/txt.rb @@ -9,7 +9,7 @@ class << self def call(message, backtrace, options = {}, env = nil, original_exception = nil) message = present(message, env) - result = message.is_a?(Hash) ? ::Grape::Json.dump(message) : message + result = message.is_a?(Hash) ? ::Grape::Util::Json.dump(message) : message rescue_options = options[:rescue_options] || {} if rescue_options[:backtrace] && backtrace && !backtrace.empty? result += "\r\n backtrace:" 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 23ed6028a6..244520fa87 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/extensions/active_support/hash_with_indifferent_access.rb b/lib/grape/extensions/active_support/hash_with_indifferent_access.rb index 2129e1c80a..e61861808a 100644 --- a/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +++ b/lib/grape/extensions/active_support/hash_with_indifferent_access.rb @@ -17,7 +17,7 @@ def params_builder def build_params ::ActiveSupport::HashWithIndifferentAccess.new(rack_params).tap do |params| - params.deep_merge!(grape_routing_args) if env.key?(Grape::Env::GRAPE_ROUTING_ARGS) + params.deep_merge!(grape_routing_args) if env.key?(Grape::Util::Env::GRAPE_ROUTING_ARGS) end end end diff --git a/lib/grape/extensions/hash.rb b/lib/grape/extensions/hash.rb index 3ad07b210e..e90d445e00 100644 --- a/lib/grape/extensions/hash.rb +++ b/lib/grape/extensions/hash.rb @@ -12,7 +12,7 @@ module ParamBuilder def build_params rack_params.deep_dup.tap do |params| - params.deep_merge!(grape_routing_args) if env.key?(Grape::Env::GRAPE_ROUTING_ARGS) + params.deep_merge!(grape_routing_args) if env.key?(Grape::Util::Env::GRAPE_ROUTING_ARGS) params.deep_symbolize_keys! end end diff --git a/lib/grape/extensions/hashie/mash.rb b/lib/grape/extensions/hashie/mash.rb index b6ae5061b1..f539ec0b2a 100644 --- a/lib/grape/extensions/hashie/mash.rb +++ b/lib/grape/extensions/hashie/mash.rb @@ -16,7 +16,7 @@ def params_builder def build_params ::Hashie::Mash.new(rack_params).tap do |params| - params.deep_merge!(grape_routing_args) if env.key?(Grape::Env::GRAPE_ROUTING_ARGS) + params.deep_merge!(grape_routing_args) if env.key?(Grape::Util::Env::GRAPE_ROUTING_ARGS) end end end diff --git a/lib/grape/formatter/json.rb b/lib/grape/formatter/json.rb index 0f0152f6c4..5e99201030 100644 --- a/lib/grape/formatter/json.rb +++ b/lib/grape/formatter/json.rb @@ -7,7 +7,7 @@ class << self def call(object, _env) return object.to_json if object.respond_to?(:to_json) - ::Grape::Json.dump(object) + ::Grape::Util::Json.dump(object) end end end diff --git a/lib/grape/formatter/serializable_hash.rb b/lib/grape/formatter/serializable_hash.rb index cb9bfb7c14..63214c2f7a 100644 --- a/lib/grape/formatter/serializable_hash.rb +++ b/lib/grape/formatter/serializable_hash.rb @@ -6,10 +6,10 @@ module SerializableHash class << self def call(object, _env) return object if object.is_a?(String) - return ::Grape::Json.dump(serialize(object)) if serializable?(object) + return ::Grape::Util::Json.dump(serialize(object)) if serializable?(object) return object.to_json if object.respond_to?(:to_json) - ::Grape::Json.dump(object) + ::Grape::Util::Json.dump(object) end private diff --git a/lib/grape/http/headers.rb b/lib/grape/http/headers.rb index a7e5984f75..66117bcb8f 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' 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/auth/strategies.rb b/lib/grape/middleware/auth/strategies.rb index 56855263e4..865a25dc49 100644 --- a/lib/grape/middleware/auth/strategies.rb +++ b/lib/grape/middleware/auth/strategies.rb @@ -1,5 +1,7 @@ # 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..b03ceda3b2 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 @@ -72,7 +70,7 @@ def content_types end def content_type - content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML + content_type_for(env[Grape::Util::Env::API_FORMAT] || options[:format]) || TEXT_HTML end def mime_types diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index 05fc0312f2..2cf79e39c3 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'grape/middleware/base' -require 'active_support/core_ext/string/output_safety' + module Grape module Middleware @@ -76,7 +75,7 @@ def rack_response(message, status = options[:default_status], headers = { Rack:: end def format_message(message, backtrace, original_exception = nil) - format = env[Grape::Env::API_FORMAT] || options[:format] + format = env[Grape::Util::Env::API_FORMAT] || options[:format] formatter = Grape::ErrorFormatter.formatter_for(format, **options) throw :error, status: 406, diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index 0f8e7cdb3c..5e7c390920 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 @@ -54,7 +52,7 @@ def build_formatted_response(status, headers, bodies) end def fetch_formatter(headers, options) - api_format = mime_types[headers[Rack::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT] + api_format = mime_types[headers[Rack::CONTENT_TYPE]] || env[Grape::Util::Env::API_FORMAT] Grape::Formatter.formatter_for(api_format, **options) end @@ -66,7 +64,7 @@ def ensure_content_type(headers) if headers[Rack::CONTENT_TYPE] headers else - headers.merge(Rack::CONTENT_TYPE => content_type_for(env[Grape::Env::API_FORMAT])) + headers.merge(Rack::CONTENT_TYPE => content_type_for(env[Grape::Util::Env::API_FORMAT])) end end @@ -82,10 +80,10 @@ def read_body_input !request.parseable_data? && (request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED) - return unless (input = env[Grape::Env::RACK_INPUT]) + return unless (input = env[Grape::Util::Env::RACK_INPUT]) input.rewind - body = env[Grape::Env::API_REQUEST_INPUT] = input.read + body = env[Grape::Util::Env::API_REQUEST_INPUT] = input.read begin read_rack_input(body) if body && !body.empty? ensure @@ -101,14 +99,14 @@ def read_rack_input(body) parser = Grape::Parser.parser_for fmt, **options if parser begin - body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env)) + body = (env[Grape::Util::Env::API_REQUEST_BODY] = parser.call(body, env)) if body.is_a?(Hash) - env[Grape::Env::RACK_REQUEST_FORM_HASH] = if env.key?(Grape::Env::RACK_REQUEST_FORM_HASH) - env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body) + env[Grape::Util::Env::RACK_REQUEST_FORM_HASH] = if env.key?(Grape::Util::Env::RACK_REQUEST_FORM_HASH) + env[Grape::Util::Env::RACK_REQUEST_FORM_HASH].merge(body) else body end - env[Grape::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Env::RACK_INPUT] + env[Grape::Util::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Util::Env::RACK_INPUT] end rescue Grape::Exceptions::Base => e raise e @@ -116,14 +114,14 @@ def read_rack_input(body) throw :error, status: 400, message: e.message, backtrace: e.backtrace, original_exception: e end else - env[Grape::Env::API_REQUEST_BODY] = body + env[Grape::Util::Env::API_REQUEST_BODY] = body end end def negotiate_content_type fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format] if content_type_for(fmt) - env[Grape::Env::API_FORMAT] = fmt + env[Grape::Util::Env::API_FORMAT] = fmt else throw :error, status: 406, message: "The requested format '#{fmt}' is not supported." end diff --git a/lib/grape/middleware/globals.rb b/lib/grape/middleware/globals.rb index 850f241967..238a98af47 100644 --- a/lib/grape/middleware/globals.rb +++ b/lib/grape/middleware/globals.rb @@ -1,15 +1,13 @@ # frozen_string_literal: true -require 'grape/middleware/base' - module Grape module Middleware class Globals < Base def before request = Grape::Request.new(@env, build_params_with: @options[:build_params_with]) - @env[Grape::Env::GRAPE_REQUEST] = request - @env[Grape::Env::GRAPE_REQUEST_HEADERS] = request.headers - @env[Grape::Env::GRAPE_REQUEST_PARAMS] = request.params if @env[Grape::Env::RACK_INPUT] + @env[Grape::Util::Env::GRAPE_REQUEST] = request + @env[Grape::Util::Env::GRAPE_REQUEST_HEADERS] = request.headers + @env[Grape::Util::Env::GRAPE_REQUEST_PARAMS] = request.params if @env[Grape::Util::Env::RACK_INPUT] end end end diff --git a/lib/grape/middleware/helpers.rb b/lib/grape/middleware/helpers.rb index 013a9587df..94f3cb4fd1 100644 --- a/lib/grape/middleware/helpers.rb +++ b/lib/grape/middleware/helpers.rb @@ -5,7 +5,7 @@ module Middleware # Common methods for all types of Grape middleware module Helpers def context - env[Grape::Env::API_ENDPOINT] + env[Grape::Util::Env::API_ENDPOINT] end end end diff --git a/lib/grape/middleware/versioner/accept_version_header.rb b/lib/grape/middleware/versioner/accept_version_header.rb index 6198ae0cfa..92b547d5d1 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 @@ -32,7 +30,7 @@ def before # If the requested version is not supported: throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.' unless versions.any? { |v| v.to_s == potential_version } - env[Grape::Env::API_VERSION] = potential_version + env[Grape::Util::Env::API_VERSION] = potential_version end private diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index 785f392e4f..6913b2bc72 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'grape/middleware/base' -require 'grape/middleware/versioner/parse_media_type_patch' - module Grape module Middleware module Versioner @@ -34,7 +31,7 @@ class Header < Base def before strict_header_checks if strict? - if media_type || env[Grape::Env::GRAPE_ALLOWED_METHODS] + if media_type || env[Grape::Util::Env::GRAPE_ALLOWED_METHODS] media_type_header_handler elsif headers_contain_wrong_vendor? fail_with_invalid_accept_header!('API vendor not found.') @@ -77,16 +74,16 @@ def media_type end def media_type_header_handler - type, subtype = Rack::Accept::Header.parse_media_type(media_type) - env[Grape::Env::API_TYPE] = type - env[Grape::Env::API_SUBTYPE] = subtype + type, subtype = Grape::Util::Accept::Header.parse_media_type(media_type) + env[Grape::Util::Env::API_TYPE] = type + env[Grape::Util::Env::API_SUBTYPE] = subtype return unless VENDOR_VERSION_HEADER_REGEX =~ subtype - env[Grape::Env::API_VENDOR] = Regexp.last_match[1] - env[Grape::Env::API_VERSION] = Regexp.last_match[2] + env[Grape::Util::Env::API_VENDOR] = Regexp.last_match[1] + env[Grape::Util::Env::API_VERSION] = Regexp.last_match[2] # weird that Grape::Middleware::Formatter also does this - env[Grape::Env::API_FORMAT] = Regexp.last_match[3] + env[Grape::Util::Env::API_FORMAT] = Regexp.last_match[3] end def fail_with_invalid_accept_header!(message) @@ -127,7 +124,7 @@ def headers_contain_wrong_version? end def rack_accept_header - Rack::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT] + Grape::Util::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT] rescue RuntimeError => e fail_with_invalid_accept_header!(e.message) end @@ -168,24 +165,24 @@ def error_headers # @param [String] media_type a content type # @return [Boolean] whether the content type sets a vendor def vendor?(media_type) - _, subtype = Rack::Accept::Header.parse_media_type(media_type) + _, subtype = Grape::Util::Accept::Header.parse_media_type(media_type) subtype.present? && subtype[HAS_VENDOR_REGEX] end def request_vendor(media_type) - _, subtype = Rack::Accept::Header.parse_media_type(media_type) + _, subtype = Grape::Util::Accept::Header.parse_media_type(media_type) subtype.match(VENDOR_VERSION_HEADER_REGEX)[1] end def request_version(media_type) - _, subtype = Rack::Accept::Header.parse_media_type(media_type) + _, subtype = Grape::Util::Accept::Header.parse_media_type(media_type) subtype.match(VENDOR_VERSION_HEADER_REGEX)[2] end # @param [String] media_type a content type # @return [Boolean] whether the content type sets an API version def version?(media_type) - _, subtype = Rack::Accept::Header.parse_media_type(media_type) + _, subtype = Grape::Util::Accept::Header.parse_media_type(media_type) subtype.present? && subtype[HAS_VERSION_REGEX] end end diff --git a/lib/grape/middleware/versioner/param.rb b/lib/grape/middleware/versioner/param.rb index 8e9fe36c0f..3ad1944f6f 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 @@ -34,8 +32,8 @@ def before return if potential_version.nil? throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version } - env[Grape::Env::API_VERSION] = potential_version - env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH + env[Grape::Util::Env::API_VERSION] = potential_version + env[Grape::Util::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Util::Env::RACK_REQUEST_QUERY_HASH end private diff --git a/lib/grape/middleware/versioner/parse_media_type_patch.rb b/lib/grape/middleware/versioner/parse_media_type_patch.rb deleted file mode 100644 index 798068ff85..0000000000 --- a/lib/grape/middleware/versioner/parse_media_type_patch.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require 'English' -module Rack - 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 - - class MediaType - def parse_media_type(media_type) - Header.parse_media_type(media_type) - end - end - end -end diff --git a/lib/grape/middleware/versioner/path.rb b/lib/grape/middleware/versioner/path.rb index ec15a6d7cf..d1ff581150 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 @@ -39,7 +37,7 @@ def before return unless potential_version&.match?(options[:pattern]) throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version } - env[Grape::Env::API_VERSION] = potential_version + env[Grape::Util::Env::API_VERSION] = potential_version end private 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/parser/json.rb b/lib/grape/parser/json.rb index 4e665a1ec2..fab7e658e9 100644 --- a/lib/grape/parser/json.rb +++ b/lib/grape/parser/json.rb @@ -5,8 +5,8 @@ module Parser module Json class << self def call(object, _env) - ::Grape::Json.load(object) - rescue ::Grape::Json::ParseError + ::Grape::Util::Json.load(object) + rescue ::Grape::Util::Json::ParseError # handle JSON parsing errors via the rescue handlers or provide error message raise Grape::Exceptions::InvalidMessageBody.new('application/json') end diff --git a/lib/grape/parser/xml.rb b/lib/grape/parser/xml.rb index 930c57f13e..b141a4da98 100644 --- a/lib/grape/parser/xml.rb +++ b/lib/grape/parser/xml.rb @@ -5,8 +5,8 @@ module Parser module Xml class << self def call(object, _env) - ::Grape::Xml.parse(object) - rescue ::Grape::Xml::ParseError + ::Grape::Util::Xml.parse(object) + rescue ::Grape::Util::Xml::ParseError # handle XML parsing errors via the rescue handlers or provide error message raise Grape::Exceptions::InvalidMessageBody.new('application/xml') end diff --git a/lib/grape/path.rb b/lib/grape/path.rb index 574855f3c7..cad5db3583 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..f231aa1137 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_' @@ -28,7 +26,7 @@ def headers private def grape_routing_args - args = env[Grape::Env::GRAPE_ROUTING_ARGS].dup + args = env[Grape::Util::Env::GRAPE_ROUTING_ARGS].dup # preserve version from query string parameters args.delete(:version) args.delete(:route_info) @@ -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 691889cd60..069b025972 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'grape/router/route' -require 'grape/util/cache' - module Grape class Router attr_reader :map, :compiled @@ -157,13 +154,13 @@ def greedy_match?(input) def call_with_allow_headers(env, route) prepare_env_from_route(env, route) - env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze + env[Grape::Util::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze route.endpoint.call(env) end def prepare_env_from_route(env, route) input, = *extract_input_and_method(env) - env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input) + env[Grape::Util::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Util::Env::GRAPE_ROUTING_ARGS], route, input) end def cascade?(response) diff --git a/lib/grape/router/pattern.rb b/lib/grape/router/pattern.rb index a239980484..609071bba5 100644 --- a/lib/grape/router/pattern.rb +++ b/lib/grape/router/pattern.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require 'forwardable' -require 'mustermann/grape' -require 'grape/util/cache' + module Grape class Router diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index d5600df1c9..0a960391cf 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -1,10 +1,5 @@ # frozen_string_literal: true -require 'grape/router/pattern' -require 'grape/router/attribute_translator' -require 'forwardable' -require 'pathname' - 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..aed3c3512f --- /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*&\^\-_#{$!}.+]+)(?:;([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/media_type.rb b/lib/grape/util/accept/media_type.rb new file mode 100644 index 0000000000..6fa90a989b --- /dev/null +++ b/lib/grape/util/accept/media_type.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Grape + module Util + module Accept + class MediaType < Rack::Accept::MediaType + def parse_media_type(media_type) + Grape::Util::Accept::Header.parse_media_type(media_type) + end + end + end + end +end 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/env.rb b/lib/grape/util/env.rb index a6023bcc1e..61a6ef5cb4 100644 --- a/lib/grape/util/env.rb +++ b/lib/grape/util/env.rb @@ -1,25 +1,27 @@ # frozen_string_literal: true module Grape - module Env - API_VERSION = 'api.version' - API_ENDPOINT = 'api.endpoint' - API_REQUEST_INPUT = 'api.request.input' - API_REQUEST_BODY = 'api.request.body' - API_TYPE = 'api.type' - API_SUBTYPE = 'api.subtype' - API_VENDOR = 'api.vendor' - API_FORMAT = 'api.format' + module Util + module Env + API_VERSION = 'api.version' + API_ENDPOINT = 'api.endpoint' + API_REQUEST_INPUT = 'api.request.input' + API_REQUEST_BODY = 'api.request.body' + API_TYPE = 'api.type' + API_SUBTYPE = 'api.subtype' + API_VENDOR = 'api.vendor' + API_FORMAT = 'api.format' - RACK_INPUT = 'rack.input' - RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash' - RACK_REQUEST_FORM_HASH = 'rack.request.form_hash' - RACK_REQUEST_FORM_INPUT = 'rack.request.form_input' + RACK_INPUT = 'rack.input' + RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash' + RACK_REQUEST_FORM_HASH = 'rack.request.form_hash' + RACK_REQUEST_FORM_INPUT = 'rack.request.form_input' - GRAPE_REQUEST = 'grape.request' - GRAPE_REQUEST_HEADERS = 'grape.request.headers' - GRAPE_REQUEST_PARAMS = 'grape.request.params' - GRAPE_ROUTING_ARGS = 'grape.routing_args' - GRAPE_ALLOWED_METHODS = 'grape.allowed_methods' + GRAPE_REQUEST = 'grape.request' + GRAPE_REQUEST_HEADERS = 'grape.request.headers' + GRAPE_REQUEST_PARAMS = 'grape.request.params' + GRAPE_ROUTING_ARGS = 'grape.routing_args' + GRAPE_ALLOWED_METHODS = 'grape.allowed_methods' + 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/json.rb b/lib/grape/util/json.rb index 26695e92ac..13c2f3060b 100644 --- a/lib/grape/util/json.rb +++ b/lib/grape/util/json.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'json' - module Grape - if Object.const_defined? :MultiJson - Json = ::MultiJson - else - Json = ::JSON - Json::ParseError = Json::ParserError + module Util + if defined?(::MultiJson) + Json = ::MultiJson + else + Json = ::JSON + Json::ParseError = Json::ParserError + end end end diff --git a/lib/grape/util/lazy_value.rb b/lib/grape/util/lazy_value.rb index cc732562f9..9090fa5020 100644 --- a/lib/grape/util/lazy_value.rb +++ b/lib/grape/util/lazy_value.rb @@ -32,60 +32,5 @@ 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/lazy_value_array.rb b/lib/grape/util/lazy_value_array.rb new file mode 100644 index 0000000000..300c56aa90 --- /dev/null +++ b/lib/grape/util/lazy_value_array.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Grape + module Util + 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 + 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..65c440ebc4 --- /dev/null +++ b/lib/grape/util/lazy_value_enumerable.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Grape + module Util + 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 + 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..02e9b461b4 --- /dev/null +++ b/lib/grape/util/lazy_value_hash.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Grape + module Util + 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/util/xml.rb b/lib/grape/util/xml.rb index d948f8012f..0fb3faac16 100644 --- a/lib/grape/util/xml.rb +++ b/lib/grape/util/xml.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true module Grape - if Object.const_defined? :MultiXml - Xml = ::MultiXml - else - Xml = ::ActiveSupport::XmlMini - Xml::ParseError = StandardError + module Util + if defined?(::MultiXml) + Xml = ::MultiXml + else + Xml = ::ActiveSupport::XmlMini + Xml::ParseError = StandardError + 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 b419d65145..c2caf14ce5 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..0648bdfea8 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -1,94 +1,93 @@ # 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 97a1f82872..e3627acad7 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 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/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index a6b7a629c1..19280ecc7a 100644 --- a/spec/grape/api/custom_validations_spec.rb +++ b/spec/grape/api/custom_validations_spec.rb @@ -169,12 +169,12 @@ 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) diff --git a/spec/grape/api/invalid_format_spec.rb b/spec/grape/api/invalid_format_spec.rb index 79da1ac1db..2239158fe9 100644 --- a/spec/grape/api/invalid_format_spec.rb +++ b/spec/grape/api/invalid_format_spec.rb @@ -27,19 +27,19 @@ def app it 'no format' do get '/foo' expect(last_response.status).to eq 200 - expect(last_response.body).to eq(::Grape::Json.dump(id: 'foo', format: nil)) + expect(last_response.body).to eq(::Grape::Util::Json.dump(id: 'foo', format: nil)) end it 'json format' do get '/foo.json' expect(last_response.status).to eq 200 - expect(last_response.body).to eq(::Grape::Json.dump(id: 'foo', format: 'json')) + expect(last_response.body).to eq(::Grape::Util::Json.dump(id: 'foo', format: 'json')) end it 'invalid format' do get '/foo.invalid' expect(last_response.status).to eq 200 - expect(last_response.body).to eq(::Grape::Json.dump(id: 'foo', format: 'invalid')) + expect(last_response.body).to eq(::Grape::Util::Json.dump(id: 'foo', format: 'invalid')) end end end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 161b561cbb..1773604a41 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -441,9 +441,9 @@ class DummyFormatClass subject.send(verb) do env['api.request.body'] end - send verb, '/', ::Grape::Json.dump(object), 'CONTENT_TYPE' => 'application/json' + send verb, '/', ::Grape::Util::Json.dump(object), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(verb == :post ? 201 : 200) - expect(last_response.body).to eql ::Grape::Json.dump(object) + expect(last_response.body).to eql ::Grape::Util::Json.dump(object) expect(last_request.params).to eql({}) end @@ -452,9 +452,9 @@ class DummyFormatClass subject.send(verb) do env['api.request.input'] end - send verb, '/', ::Grape::Json.dump(object), 'CONTENT_TYPE' => 'application/json' + send verb, '/', ::Grape::Util::Json.dump(object), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(verb == :post ? 201 : 200) - expect(last_response.body).to eql ::Grape::Json.dump(object).to_json + expect(last_response.body).to eql ::Grape::Util::Json.dump(object).to_json end context 'chunked transfer encoding' do @@ -463,9 +463,9 @@ class DummyFormatClass subject.send(verb) do env['api.request.input'] end - send verb, '/', ::Grape::Json.dump(object), 'CONTENT_TYPE' => 'application/json', 'HTTP_TRANSFER_ENCODING' => 'chunked', 'CONTENT_LENGTH' => nil + send verb, '/', ::Grape::Util::Json.dump(object), 'CONTENT_TYPE' => 'application/json', 'HTTP_TRANSFER_ENCODING' => 'chunked', 'CONTENT_LENGTH' => nil expect(last_response.status).to eq(verb == :post ? 201 : 200) - expect(last_response.body).to eql ::Grape::Json.dump(object).to_json + expect(last_response.body).to eql ::Grape::Util::Json.dump(object).to_json end end end @@ -996,11 +996,6 @@ class DummyFormatClass 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 } @@ -2502,7 +2497,7 @@ def self.call(message, _backtrace, _option, _env, _original_exception) raise 'rain!' end get '/exception' - json = ::Grape::Json.load(last_response.body) + json = ::Grape::Util::Json.load(last_response.body) expect(json['error']).to eql 'rain!' expect(json['backtrace'].length).to be > 0 end @@ -3735,7 +3730,7 @@ def my_method it 'path' do get '/endpoint/options' - options = ::Grape::Json.load(last_response.body) + options = ::Grape::Util::Json.load(last_response.body) expect(options['path']).to eq(['/endpoint/options']) expect(options['source_location'][0]).to include 'api_spec.rb' expect(options['source_location'][1].to_i).to be > 0 diff --git a/spec/grape/endpoint/declared_spec.rb b/spec/grape/endpoint/declared_spec.rb index f4402416d5..59abd08db2 100644 --- a/spec/grape/endpoint/declared_spec.rb +++ b/spec/grape/endpoint/declared_spec.rb @@ -294,7 +294,7 @@ def app '' end - post '/declared', ::Grape::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json' + post '/declared', ::Grape::Util::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(201) end @@ -309,7 +309,7 @@ def app '' end - post '/declared', ::Grape::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json' + post '/declared', ::Grape::Util::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(201) end diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 400b45c404..eb041fe11e 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 @@ -384,7 +384,7 @@ def app end it 'converts JSON bodies to params' do - post '/request_body', ::Grape::Json.dump(user: 'Bobby T.'), 'CONTENT_TYPE' => 'application/json' + post '/request_body', ::Grape::Util::Json.dump(user: 'Bobby T.'), 'CONTENT_TYPE' => 'application/json' expect(last_response.body).to eq('Bobby T.') end @@ -420,7 +420,7 @@ def app error! 400, 'expected nil' if params[:version] params[:user] end - post '/omitted_params', ::Grape::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'application/json' + post '/omitted_params', ::Grape::Util::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(201) expect(last_response.body).to eq('Bob') end @@ -477,7 +477,7 @@ def app subject.put '/request_body' do params[:user] end - put '/request_body', ::Grape::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'text/plain' + put '/request_body', ::Grape::Util::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'text/plain' expect(last_response.status).to eq(415) expect(last_response.body).to eq('{"error":"The provided content-type \'text/plain\' is not supported."}') @@ -491,7 +491,7 @@ def app subject.post do params[:data] end - post '/', ::Grape::Json.dump(data: { some: 'payload' }), 'CONTENT_TYPE' => 'application/json' + post '/', ::Grape::Util::Json.dump(data: { some: 'payload' }), 'CONTENT_TYPE' => 'application/json' end it 'does not response with 406 for same type without params' do diff --git a/spec/grape/middleware/base_spec.rb b/spec/grape/middleware/base_spec.rb index cbba974e1a..070e1769d0 100644 --- a/spec/grape/middleware/base_spec.rb +++ b/spec/grape/middleware/base_spec.rb @@ -128,7 +128,7 @@ subject { described_class.new(blank_app) } it 'allows access to response context' do - subject.call(Grape::Env::API_ENDPOINT => { header: 'some header' }) + subject.call(Grape::Util::Env::API_ENDPOINT => { header: 'some header' }) expect(subject.context).to eq(header: 'some header') end end diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index 29f49f88ba..bd3be97706 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -13,7 +13,7 @@ it 'looks at the bodies for possibly serializable data' do _, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json') - bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) } # rubocop:disable RSpec/IteratedExpectation + bodies.each { |b| expect(b).to eq(::Grape::Util::Json.dump(body)) } # rubocop:disable RSpec/IteratedExpectation end context 'default format' do 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/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/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index cd1d9dcbd0..364358488e 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -655,19 +655,19 @@ def self.parse(_val) params[:values] end - post '/coerce_nested_strings', ::Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json' + post '/coerce_nested_strings', ::Grape::Util::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(201) expect(JSON.parse(last_response.body)).to eq([%w[a b c d]]) - post '/coerce_nested_strings', ::Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json' + post '/coerce_nested_strings', ::Grape::Util::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(201) expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]]) - post '/coerce_nested_strings', ::Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json' + post '/coerce_nested_strings', ::Grape::Util::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(201) expect(JSON.parse(last_response.body)).to eq([[]]) - post '/coerce_nested_strings', ::Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json' + post '/coerce_nested_strings', ::Grape::Util::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json' expect(last_response.status).to eq(400) end diff --git a/spec/integration/eager_load/eager_load_spec.rb b/spec/integration/eager_load/eager_load_spec.rb index 174ba99440..2f73eb1aa7 100644 --- a/spec/integration/eager_load/eager_load_spec.rb +++ b/spec/integration/eager_load/eager_load_spec.rb @@ -4,11 +4,6 @@ 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 diff --git a/spec/integration/multi_json/json_spec.rb b/spec/integration/multi_json/json_spec.rb index 18ac22b724..51f7eb496c 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::Util::Json, if: defined?(::MultiJson) do it 'uses multi_json' do expect(described_class).to eq(::MultiJson) end diff --git a/spec/integration/multi_xml/xml_spec.rb b/spec/integration/multi_xml/xml_spec.rb index 54d918e558..49bf4cea39 100644 --- a/spec/integration/multi_xml/xml_spec.rb +++ b/spec/integration/multi_xml/xml_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe Grape::Xml do +describe Grape::Util::Xml, if: defined?(::MultiXml) do it 'uses multi_xml' do expect(described_class).to eq(::MultiXml) end diff --git a/spec/integration/rack/v2/headers_spec.rb b/spec/integration/rack/v2/headers_spec.rb index 4819f21dc1..0c0b0b4861 100644 --- a/spec/integration/rack/v2/headers_spec.rb +++ b/spec/integration/rack/v2/headers_spec.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true -describe Grape::Http::Headers do +describe Grape::Http::Headers, if: Gem::Version.new(Rack.release) < Gem::Version.new('3') do it { expect(described_class::ALLOW).to eq('Allow') } it { expect(described_class::LOCATION).to eq('Location') } it { expect(described_class::TRANSFER_ENCODING).to eq('Transfer-Encoding') } it { expect(described_class::X_CASCADE).to eq('X-Cascade') } end + diff --git a/spec/integration/rack/v3/headers_spec.rb b/spec/integration/rack/v3/headers_spec.rb index 3be2c1e28b..1007cd4389 100644 --- a/spec/integration/rack/v3/headers_spec.rb +++ b/spec/integration/rack/v3/headers_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe Grape::Http::Headers do +describe Grape::Http::Headers, if: Gem::Version.new(Rack.release) >= Gem::Version.new('3') do it { expect(described_class::ALLOW).to eq('allow') } it { expect(described_class::LOCATION).to eq('location') } it { expect(described_class::TRANSFER_ENCODING).to eq('transfer-encoding') } From ba82ceb725510b92d10e0e268dfca6a28ffccb4a Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sat, 28 Oct 2023 22:17:17 +0200 Subject: [PATCH 02/11] Create module Lazy in Util Remove validator registration and leverage Zeitwerk --- lib/grape/api/instance.rb | 2 +- lib/grape/http/headers.rb | 4 +- lib/grape/request.rb | 2 +- lib/grape/util/endpoint_configuration.rb | 2 +- lib/grape/util/lazy/block.rb | 29 ++++++++++++ lib/grape/util/lazy/object.rb | 45 +++++++++++++++++++ lib/grape/util/lazy/value.rb | 38 ++++++++++++++++ lib/grape/util/lazy/value_array.rb | 21 +++++++++ lib/grape/util/lazy/value_enumerable.rb | 34 ++++++++++++++ lib/grape/util/lazy/value_hash.rb | 21 +++++++++ lib/grape/util/lazy_block.rb | 27 ----------- lib/grape/util/lazy_object.rb | 43 ------------------ lib/grape/util/lazy_value.rb | 36 --------------- lib/grape/util/lazy_value_array.rb | 19 -------- lib/grape/util/lazy_value_enumerable.rb | 32 ------------- lib/grape/util/lazy_value_hash.rb | 20 --------- lib/grape/validations.rb | 21 +-------- lib/grape/validations/validators/base.rb | 7 --- spec/grape/api/custom_validations_spec.rb | 33 +++----------- ...our_spec.rb => instance_behaviour_spec.rb} | 8 +--- .../grape/validations/validators/base_spec.rb | 38 ---------------- spec/grape/validations_spec.rb | 25 ++--------- 22 files changed, 203 insertions(+), 304 deletions(-) create mode 100644 lib/grape/util/lazy/block.rb create mode 100644 lib/grape/util/lazy/object.rb create mode 100644 lib/grape/util/lazy/value.rb create mode 100644 lib/grape/util/lazy/value_array.rb create mode 100644 lib/grape/util/lazy/value_enumerable.rb create mode 100644 lib/grape/util/lazy/value_hash.rb delete mode 100644 lib/grape/util/lazy_block.rb delete mode 100644 lib/grape/util/lazy_object.rb delete mode 100644 lib/grape/util/lazy_value.rb delete mode 100644 lib/grape/util/lazy_value_array.rb delete mode 100644 lib/grape/util/lazy_value_enumerable.rb delete mode 100644 lib/grape/util/lazy_value_hash.rb rename spec/grape/validations/{instance_behaivour_spec.rb => instance_behaviour_spec.rb} (85%) delete mode 100644 spec/grape/validations/validators/base_spec.rb diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 8def39f999..c0c6ba2fd5 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -110,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/http/headers.rb b/lib/grape/http/headers.rb index 66117bcb8f..ae0989a3b3 100644 --- a/lib/grape/http/headers.rb +++ b/lib/grape/http/headers.rb @@ -34,7 +34,7 @@ def self.lowercase? 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' @@ -42,7 +42,7 @@ def self.lowercase? 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/request.rb b/lib/grape/request.rb index f231aa1137..5f258ba5d3 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -34,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 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/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 9090fa5020..0000000000 --- a/lib/grape/util/lazy_value.rb +++ /dev/null @@ -1,36 +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 - end -end diff --git a/lib/grape/util/lazy_value_array.rb b/lib/grape/util/lazy_value_array.rb deleted file mode 100644 index 300c56aa90..0000000000 --- a/lib/grape/util/lazy_value_array.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Grape - module Util - 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 - end -end diff --git a/lib/grape/util/lazy_value_enumerable.rb b/lib/grape/util/lazy_value_enumerable.rb deleted file mode 100644 index 65c440ebc4..0000000000 --- a/lib/grape/util/lazy_value_enumerable.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Grape - module Util - 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 - end -end diff --git a/lib/grape/util/lazy_value_hash.rb b/lib/grape/util/lazy_value_hash.rb deleted file mode 100644 index 02e9b461b4..0000000000 --- a/lib/grape/util/lazy_value_hash.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module Grape - module Util - 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/validations.rb b/lib/grape/validations.rb index 4b2e97baf9..dc87cbe6c2 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -5,28 +5,9 @@ module Grape module Validations module_function - def validators - @validators ||= {} - end - - # 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. - def register_validator(short_name, klass) - validators[short_name] = klass - end - - 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 + Grape::Validations::Validators.const_get("#{short_name.to_s.camelize}Validator") rescue NameError raise Grape::Exceptions::UnknownValidator.new(short_name) end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index c76eb4b394..eeb0bcfc1c 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -58,13 +58,6 @@ def validate!(params) raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any? end - def self.inherited(klass) - return if klass.name.blank? - - short_validator_name = klass.name.demodulize.underscore.delete_suffix('_validator') - Validations.register_validator(short_validator_name, klass) - end - def message(default_key = nil) options = instance_variable_get(:@option) options_key?(:message) ? options[:message] : default_key diff --git a/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index 19280ecc7a..443ba31706 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' @@ -173,16 +155,11 @@ def access_header end end end + let(:app) { Rack::Builder.new(subject) } 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' diff --git a/spec/grape/validations/instance_behaivour_spec.rb b/spec/grape/validations/instance_behaviour_spec.rb similarity index 85% rename from spec/grape/validations/instance_behaivour_spec.rb rename to spec/grape/validations/instance_behaviour_spec.rb index c8df967564..682d985f72 100644 --- a/spec/grape/validations/instance_behaivour_spec.rb +++ b/spec/grape/validations/instance_behaviour_spec.rb @@ -24,13 +24,7 @@ def validate_param!(_attr_name, _params) end end - before do - Grape::Validations.register_validator('instance_validator', validator_type) - end - - after do - Grape::Validations.deregister_validator('instance_validator') - end + before { stub_const('Grape::Validations::Validators::InstanceValidatorValidator', validator_type) } it 'passes validation every time' do expect(validator_type).to receive(:new).exactly(4).times.and_call_original 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..ac34da2373 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -499,14 +499,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 +1191,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 +1343,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 From a0452642aa4b46728637f69f21f8b8cbbac1d374 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sat, 28 Oct 2023 22:29:50 +0200 Subject: [PATCH 03/11] Rubocop Move instance_behaviour_spec.rb specs in custom_validations_spec.rb --- grape.gemspec | 2 +- lib/grape.rb | 32 ++++++++-------- lib/grape/middleware/error.rb | 2 - lib/grape/middleware/formatter.rb | 8 ++-- lib/grape/router/pattern.rb | 2 - lib/grape/util/accept/header.rb | 4 +- lib/grape/validations/types/build_coercer.rb | 1 - spec/grape/api/custom_validations_spec.rb | 33 +++++++++++++++++ .../validations/instance_behaviour_spec.rb | 37 ------------------- spec/grape/validations_spec.rb | 5 +-- spec/integration/rack/v2/headers_spec.rb | 1 - 11 files changed, 59 insertions(+), 68 deletions(-) delete mode 100644 spec/grape/validations/instance_behaviour_spec.rb diff --git a/grape.gemspec b/grape.gemspec index a5c11e8d89..b03eeb959f 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -23,11 +23,11 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'activesupport', '>= 5' s.add_runtime_dependency 'builder' s.add_runtime_dependency 'dry-types', '>= 1.1' + s.add_runtime_dependency 'i18n' s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0' s.add_runtime_dependency 'rack', '>= 1.3.0' s.add_runtime_dependency 'rack-accept' s.add_runtime_dependency 'zeitwerk' - s.add_runtime_dependency 'i18n' 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 23480c0855..c110ec22ed 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -1,18 +1,5 @@ # frozen_string_literal: true -require 'logger' -require 'rack' -require 'rack/builder' -require 'rack/accept' -require 'set' -require 'dry-types' -require 'bigdecimal' -require 'forwardable' -require 'json' -require 'date' -require 'mustermann/grape' -require 'singleton' -require 'pathname' require 'active_support' require 'active_support/concern' require 'active_support/configurable' @@ -34,12 +21,26 @@ require 'active_support/deprecation' require 'active_support/inflector' require 'active_support/notifications' + +require 'bigdecimal' +require 'date' +require 'dry-types' +require 'forwardable' +require 'json' +require 'logger' +require 'mustermann/grape' +require 'pathname' +require 'rack' +require 'rack/accept' +require 'rack/builder' +require 'set' +require 'singleton' require 'zeitwerk' loader = Zeitwerk::Loader.for_gem loader.inflector.inflect( - "api" => 'API', - "dsl" => 'DSL' + 'api' => 'API', + 'dsl' => 'DSL' ) railtie = "#{__dir__}/grape/railtie.rb" loader.do_not_eager_load(railtie) @@ -49,7 +50,6 @@ module Grape include ActiveSupport::Configurable - extend ::ActiveSupport::Autoload def self.deprecator @deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape') diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index 2cf79e39c3..6fb2df61f8 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true - - module Grape module Middleware class Error < Base diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index 5e7c390920..ea74081114 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -102,10 +102,10 @@ def read_rack_input(body) body = (env[Grape::Util::Env::API_REQUEST_BODY] = parser.call(body, env)) if body.is_a?(Hash) env[Grape::Util::Env::RACK_REQUEST_FORM_HASH] = if env.key?(Grape::Util::Env::RACK_REQUEST_FORM_HASH) - env[Grape::Util::Env::RACK_REQUEST_FORM_HASH].merge(body) - else - body - end + env[Grape::Util::Env::RACK_REQUEST_FORM_HASH].merge(body) + else + body + end env[Grape::Util::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Util::Env::RACK_INPUT] end rescue Grape::Exceptions::Base => e diff --git a/lib/grape/router/pattern.rb b/lib/grape/router/pattern.rb index 609071bba5..bf4d408502 100644 --- a/lib/grape/router/pattern.rb +++ b/lib/grape/router/pattern.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true - - module Grape class Router class Pattern diff --git a/lib/grape/util/accept/header.rb b/lib/grape/util/accept/header.rb index aed3c3512f..7b9944c659 100644 --- a/lib/grape/util/accept/header.rb +++ b/lib/grape/util/accept/header.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true +require 'English' + module Grape module Util module Accept module Header - ALLOWED_CHARACTERS = %r{^([a-z*]+)/([a-z0-9*&\^\-_#{$!}.+]+)(?:;([a-z0-9=;]+))?$}.freeze + 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) diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index 0648bdfea8..5f15a1a0cb 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true - module Grape module Validations module Types diff --git a/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index 443ba31706..e1f9e9cff2 100644 --- a/spec/grape/api/custom_validations_spec.rb +++ b/spec/grape/api/custom_validations_spec.rb @@ -193,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).exactly(2).times.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/validations/instance_behaviour_spec.rb b/spec/grape/validations/instance_behaviour_spec.rb deleted file mode 100644 index 682d985f72..0000000000 --- a/spec/grape/validations/instance_behaviour_spec.rb +++ /dev/null @@ -1,37 +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 { stub_const('Grape::Validations::Validators::InstanceValidatorValidator', validator_type) } - - 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_spec.rb b/spec/grape/validations_spec.rb index ac34da2373..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 diff --git a/spec/integration/rack/v2/headers_spec.rb b/spec/integration/rack/v2/headers_spec.rb index 0c0b0b4861..c716765ee3 100644 --- a/spec/integration/rack/v2/headers_spec.rb +++ b/spec/integration/rack/v2/headers_spec.rb @@ -6,4 +6,3 @@ it { expect(described_class::TRANSFER_ENCODING).to eq('Transfer-Encoding') } it { expect(described_class::X_CASCADE).to eq('X-Cascade') } end - From ce19591d2170c304a85f6a02468c1788f14adbeb Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sat, 28 Oct 2023 22:31:40 +0200 Subject: [PATCH 04/11] Fix rubocop Remove i18n dependency --- grape.gemspec | 1 - spec/grape/api/custom_validations_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/grape.gemspec b/grape.gemspec index b03eeb959f..796e75c78d 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -23,7 +23,6 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'activesupport', '>= 5' s.add_runtime_dependency 'builder' s.add_runtime_dependency 'dry-types', '>= 1.1' - s.add_runtime_dependency 'i18n' s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0' s.add_runtime_dependency 'rack', '>= 1.3.0' s.add_runtime_dependency 'rack-accept' diff --git a/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index e1f9e9cff2..49571a803e 100644 --- a/spec/grape/api/custom_validations_spec.rb +++ b/spec/grape/api/custom_validations_spec.rb @@ -221,7 +221,7 @@ def validate_param!(_attr_name, _params) before { stub_const('Grape::Validations::Validators::InstanceValidatorValidator', validator_type) } it 'passes validation every time' do - expect(validator_type).to receive(:new).exactly(2).times.and_call_original + 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 From 95dbfa9802f665ff99e4f233d5a1ee52cdf98359 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 29 Oct 2023 11:58:40 +0100 Subject: [PATCH 05/11] Remove validator register/unregister Remove Coercer collection --- lib/grape/validations.rb | 10 +++------- lib/grape/validations/types/dry_type_coercer.rb | 10 +--------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/grape/validations.rb b/lib/grape/validations.rb index dc87cbe6c2..8e95e63ebc 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -1,15 +1,11 @@ # frozen_string_literal: true module Grape - # Registry to store and locate known Validators. module Validations - module_function - - # Find a validator and if not found will try to load it - def require_validator(short_name) - Grape::Validations::Validators.const_get("#{short_name.to_s.camelize}Validator") + def self.require_validator(short_name) + Grape::Validations::Validators.const_get("#{short_name.to_s.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/types/dry_type_coercer.rb b/lib/grape/validations/types/dry_type_coercer.rb index e3627acad7..fe74617580 100644 --- a/lib/grape/validations/types/dry_type_coercer.rb +++ b/lib/grape/validations/types/dry_type_coercer.rb @@ -22,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 @@ -35,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) From c67ec82099394ef42f542023ad6609f09ac5e1cf Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 29 Oct 2023 12:01:39 +0100 Subject: [PATCH 06/11] Update rubocop-todo --- .rubocop_todo.yml | 32 ++++++++++++-------------------- lib/grape/validations.rb | 2 +- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 57e7a83bd8..9a4044e36b 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 2023-07-04 00:22:04 UTC using RuboCop version 1.50.2. +# on 2023-10-29 11:00:47 UTC using RuboCop version 1.50.2. # 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 @@ -20,7 +20,7 @@ Lint/AmbiguousBlockAssociation: Exclude: - 'spec/grape/dsl/routing_spec.rb' -# Offense count: 40 +# Offense count: 39 # Configuration parameters: AllowedMethods. # AllowedMethods: enums Lint/ConstantDefinitionInBlock: @@ -32,7 +32,6 @@ Lint/ConstantDefinitionInBlock: - 'spec/grape/api_spec.rb' - 'spec/grape/entity_spec.rb' - 'spec/grape/loading_spec.rb' - - 'spec/grape/middleware/auth/strategies_spec.rb' - 'spec/grape/middleware/base_spec.rb' - 'spec/grape/middleware/error_spec.rb' - 'spec/grape/middleware/formatter_spec.rb' @@ -50,7 +49,7 @@ Lint/DuplicateBranch: Exclude: - 'spec/support/versioned_helpers.rb' -# Offense count: 71 +# Offense count: 72 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: @@ -82,7 +81,7 @@ Lint/EmptyClass: - 'spec/grape/entity_spec.rb' - 'spec/grape/middleware/stack_spec.rb' -# Offense count: 6 +# Offense count: 5 Lint/MissingSuper: Exclude: - 'lib/grape/api/instance.rb' @@ -90,7 +89,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. @@ -132,7 +130,7 @@ RSpec/AnyInstance: - 'spec/grape/api_spec.rb' - 'spec/grape/middleware/base_spec.rb' -# Offense count: 343 +# Offense count: 342 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: @@ -189,7 +187,7 @@ RSpec/ContextWording: - 'spec/grape/validations_spec.rb' - 'spec/shared/versioning_examples.rb' -# Offense count: 2 +# Offense count: 1 # Configuration parameters: IgnoredMetadata. RSpec/DescribeClass: Exclude: @@ -199,7 +197,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). @@ -224,7 +221,7 @@ RSpec/ExpectInHook: - 'spec/grape/api_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 43 +# Offense count: 44 # Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly. # Include: **/*_spec*rb*, **/spec/**/* RSpec/FilePath: @@ -256,7 +253,6 @@ RSpec/FilePath: - 'spec/grape/integration/rack_sendfile_spec.rb' - 'spec/grape/loading_spec.rb' - 'spec/grape/middleware/exception_spec.rb' - - 'spec/grape/validations/attributes_doc_spec.rb' - 'spec/grape/validations/validators/all_or_none_spec.rb' - 'spec/grape/validations/validators/allow_blank_spec.rb' - 'spec/grape/validations/validators/at_least_one_of_spec.rb' @@ -296,7 +292,7 @@ RSpec/InstanceVariable: - 'spec/grape/middleware/versioner/header_spec.rb' - 'spec/grape/validations/validators/except_values_spec.rb' -# Offense count: 84 +# Offense count: 81 RSpec/LeakyConstantDeclaration: Exclude: - 'spec/grape/api/defines_boolean_in_params_spec.rb' @@ -306,7 +302,6 @@ RSpec/LeakyConstantDeclaration: - 'spec/grape/api_spec.rb' - 'spec/grape/entity_spec.rb' - 'spec/grape/loading_spec.rb' - - 'spec/grape/middleware/auth/strategies_spec.rb' - 'spec/grape/middleware/base_spec.rb' - 'spec/grape/middleware/error_spec.rb' - 'spec/grape/middleware/exception_spec.rb' @@ -324,7 +319,7 @@ RSpec/MessageChain: Exclude: - 'spec/grape/middleware/formatter_spec.rb' -# Offense count: 147 +# Offense count: 143 # Configuration parameters: . # SupportedStyles: have_received, receive RSpec/MessageSpies: @@ -384,7 +379,6 @@ RSpec/MultipleExpectations: - 'spec/grape/util/reverse_stackable_values_spec.rb' - 'spec/grape/util/stackable_values_spec.rb' - 'spec/grape/validations/attributes_doc_spec.rb' - - 'spec/grape/validations/instance_behaivour_spec.rb' - 'spec/grape/validations/params_scope_spec.rb' - 'spec/grape/validations/types/array_coercer_spec.rb' - 'spec/grape/validations/types/primitive_coercer_spec.rb' @@ -542,17 +536,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' @@ -622,7 +614,7 @@ Style/OptionalBooleanParameter: - 'lib/grape/validations/types/primitive_coercer.rb' - 'lib/grape/validations/types/set_coercer.rb' -# Offense count: 28 +# Offense count: 30 # This cop supports safe autocorrection (--autocorrect). Style/RedundantConstantBase: Exclude: diff --git a/lib/grape/validations.rb b/lib/grape/validations.rb index 8e95e63ebc..54cfc8205e 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -3,7 +3,7 @@ module Grape module Validations def self.require_validator(short_name) - Grape::Validations::Validators.const_get("#{short_name.to_s.camelize}Validator") + Grape::Validations::Validators.const_get("#{short_name.to_s.camelize}Validator") rescue NameError raise Grape::Exceptions::UnknownValidator, short_name end From e8f271c29971468d7bd2ff847a04d6ea6b5ae013 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 29 Oct 2023 12:02:42 +0100 Subject: [PATCH 07/11] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aabf664f24..fcee331d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * [#2355](https://github.com/ruby-grape/grape/pull/2355): Set response headers based on Rack version - [@schinery](https://github.com/schinery). * [#2360](https://github.com/ruby-grape/grape/pull/2360): Reduce gem size by removing specs - [@ericproulx](https://github.com/ericproulx). * [#2361](https://github.com/ruby-grape/grape/pull/2361): Remove `Rack::Auth::Digest` - [@ninoseki](https://github.com/ninoseki). +* [#2363](https://github.com/ruby-grape/grape/pull/2363): Replace autoload by zeitwerk - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes From 1569f65b03ae43f45280bd15a022f5eff34cd086 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 12 Nov 2023 22:22:26 -0500 Subject: [PATCH 08/11] Fixed rubocop --- lib/grape.rb | 1 + lib/grape/middleware/auth/strategies.rb | 2 -- spec/grape/middleware/error_spec.rb | 10 ---------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/grape.rb b/lib/grape.rb index af581613d6..34a728ddb4 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -34,6 +34,7 @@ require 'mustermann/grape' require 'pathname' require 'rack' +require 'rack/auth/basic' require 'rack/accept/header' require 'rack/accept/media_type' require 'rack/builder' diff --git a/lib/grape/middleware/auth/strategies.rb b/lib/grape/middleware/auth/strategies.rb index 865a25dc49..56855263e4 100644 --- a/lib/grape/middleware/auth/strategies.rb +++ b/lib/grape/middleware/auth/strategies.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'rack/auth/basic' - module Grape module Middleware module Auth diff --git a/spec/grape/middleware/error_spec.rb b/spec/grape/middleware/error_spec.rb index 2698218100..d056ca7e2a 100644 --- a/spec/grape/middleware/error_spec.rb +++ b/spec/grape/middleware/error_spec.rb @@ -82,14 +82,4 @@ def app expect(last_response.body).to eq({ code: 200, static: 'static text' }.to_json) end end - - context 'NotImplementedError' do - let(:api) do - Class.new(Grape::Api) do - get :test do - raise NotImplementedError - end - end - end - end end From 5d35fc1d62f63cb56856d078edad734e9d9fe1ca Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 12 Nov 2023 23:00:58 -0500 Subject: [PATCH 09/11] eager_load --- lib/grape.rb | 3 +++ lib/grape/api.rb | 1 - lib/grape/util/accept/header.rb | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/grape.rb b/lib/grape.rb index 34a728ddb4..05dff2e7d8 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -25,6 +25,7 @@ require 'active_support/inflector' require 'active_support/notifications' +require 'English' require 'bigdecimal' require 'date' require 'dry-types' @@ -38,6 +39,7 @@ require 'rack/accept/header' require 'rack/accept/media_type' require 'rack/builder' +require 'rack/head' require 'set' require 'singleton' require 'zeitwerk' @@ -69,3 +71,4 @@ def self.deprecator # 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 9914c9d926..b7ffad3081 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -125,7 +125,6 @@ def method_missing(method, *args, &block) end def compile! - Zeitwerk::Loader.eager_load_all instance_for_rack.compile! # See API::Instance.compile! end diff --git a/lib/grape/util/accept/header.rb b/lib/grape/util/accept/header.rb index 7b9944c659..3f6b3a67a9 100644 --- a/lib/grape/util/accept/header.rb +++ b/lib/grape/util/accept/header.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'English' - module Grape module Util module Accept From d705e06dc2375c2a4daaf43adda808923ac335d0 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 13 Nov 2023 11:57:18 -0500 Subject: [PATCH 10/11] Remove eager_load_spec since eager_load by default --- .github/workflows/test.yml | 5 ----- .rubocop_todo.yml | 1 - benchmark/compile_many_routes.rb | 3 --- spec/integration/eager_load/eager_load_spec.rb | 10 ---------- 4 files changed, 19 deletions(-) delete mode 100644 spec/integration/eager_load/eager_load_spec.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 150f588407..a049588704 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,11 +50,6 @@ jobs: - name: Run tests run: bundle exec rake spec - - name: Run tests (spec/integration/eager_load) - # rack_2_0.gemfile is equals to Gemfile - if: ${{ matrix.gemfile == 'rack_2_0' }} - run: bundle exec rspec spec/integration/eager_load - - name: Run tests (spec/integration/multi_json) if: ${{ matrix.gemfile == 'multi_json' }} run: bundle exec rspec spec/integration/multi_json diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9a4044e36b..c45594bb13 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -265,7 +265,6 @@ RSpec/FilePath: - 'spec/grape/validations/validators/regexp_spec.rb' - 'spec/grape/validations/validators/same_as_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' - - 'spec/integration/eager_load/eager_load_spec.rb' - 'spec/integration/multi_json/json_spec.rb' - 'spec/integration/multi_xml/xml_spec.rb' - 'spec/integration/rack/v2/headers_spec.rb' 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/spec/integration/eager_load/eager_load_spec.rb b/spec/integration/eager_load/eager_load_spec.rb deleted file mode 100644 index 2f73eb1aa7..0000000000 --- a/spec/integration/eager_load/eager_load_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib')) -require 'grape' - -describe Grape do - it 'compile!' do - expect { Class.new(Grape::API).compile! }.not_to raise_error - end -end From 55e37536856c87555e5809195ef7e7ca00911ce0 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 24 Mar 2024 19:08:55 +0100 Subject: [PATCH 11/11] Remove requires Rebuild rubocop --- .rubocop_todo.yml | 51 +++++-------------- lib/grape/router/greedy_route.rb | 3 -- lib/grape/util/accept_header_handler.rb | 2 - lib/grape/validations.rb | 2 +- spec/grape/util/accept_header_handler_spec.rb | 2 - 5 files changed, 15 insertions(+), 45 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6f5f76b8e6..dded0333e5 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-01-01 22:17:14 UTC using RuboCop version 1.59.0. +# on 2024-03-24 18:07:49 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 @@ -52,7 +52,7 @@ Lint/DuplicateBranch: Exclude: - 'spec/support/versioned_helpers.rb' -# Offense count: 75 +# Offense count: 76 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: @@ -84,7 +84,7 @@ Lint/EmptyClass: - 'spec/grape/entity_spec.rb' - 'spec/grape/middleware/stack_spec.rb' -# Offense count: 6 +# Offense count: 5 # Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: @@ -93,7 +93,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. @@ -136,7 +135,7 @@ RSpec/AnyInstance: - 'spec/grape/api_spec.rb' - 'spec/grape/middleware/base_spec.rb' -# Offense count: 344 +# Offense count: 345 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: @@ -193,7 +192,7 @@ RSpec/ContextWording: - 'spec/grape/validations_spec.rb' - 'spec/shared/versioning_examples.rb' -# Offense count: 2 +# Offense count: 1 # Configuration parameters: IgnoredMetadata. RSpec/DescribeClass: Exclude: @@ -203,7 +202,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). @@ -228,7 +226,7 @@ RSpec/ExpectInHook: - 'spec/grape/api_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 48 +# Offense count: 46 # Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly. # Include: **/*_spec*rb*, **/spec/**/* RSpec/FilePath: @@ -262,7 +260,6 @@ RSpec/FilePath: - 'spec/grape/integration/rack_sendfile_spec.rb' - 'spec/grape/loading_spec.rb' - 'spec/grape/middleware/exception_spec.rb' - - 'spec/grape/validations/attributes_doc_spec.rb' - 'spec/grape/validations/validators/all_or_none_spec.rb' - 'spec/grape/validations/validators/allow_blank_spec.rb' - 'spec/grape/validations/validators/at_least_one_of_spec.rb' @@ -275,7 +272,6 @@ RSpec/FilePath: - 'spec/grape/validations/validators/regexp_spec.rb' - 'spec/grape/validations/validators/same_as_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' - - 'spec/integration/eager_load/eager_load_spec.rb' - 'spec/integration/multi_json/json_spec.rb' - 'spec/integration/multi_xml/xml_spec.rb' - 'spec/integration/no_dry_validation/no_dry_validation_spec.rb' @@ -331,7 +327,7 @@ RSpec/MessageChain: Exclude: - 'spec/grape/middleware/formatter_spec.rb' -# Offense count: 147 +# Offense count: 144 # Configuration parameters: . # SupportedStyles: have_received, receive RSpec/MessageSpies: @@ -394,7 +390,6 @@ RSpec/MultipleExpectations: - 'spec/grape/util/stackable_values_spec.rb' - 'spec/grape/validations/attributes_doc_spec.rb' - 'spec/grape/validations/contract_scope_spec.rb' - - 'spec/grape/validations/instance_behaivour_spec.rb' - 'spec/grape/validations/params_scope_spec.rb' - 'spec/grape/validations/types/array_coercer_spec.rb' - 'spec/grape/validations/types/primitive_coercer_spec.rb' @@ -424,7 +419,7 @@ RSpec/MultipleMemoizedHelpers: - 'spec/grape/request_spec.rb' - 'spec/grape/validations/attributes_doc_spec.rb' -# Offense count: 2182 +# Offense count: 2180 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: @@ -489,29 +484,14 @@ RSpec/NamedSubject: - 'spec/grape/validations/validators/presence_spec.rb' - 'spec/grape/validations_spec.rb' -# Offense count: 174 +# Offense count: 28 # Configuration parameters: Max, AllowedGroups. RSpec/NestedGroups: Exclude: - 'spec/grape/api_remount_spec.rb' - 'spec/grape/api_spec.rb' - - 'spec/grape/dsl/headers_spec.rb' - 'spec/grape/dsl/inside_route_spec.rb' - - 'spec/grape/endpoint_spec.rb' - - 'spec/grape/exceptions/base_spec.rb' - - 'spec/grape/exceptions/invalid_accept_header_spec.rb' - - 'spec/grape/middleware/formatter_spec.rb' - - 'spec/grape/presenters/presenter_spec.rb' - - 'spec/grape/validations/attributes_doc_spec.rb' - - 'spec/grape/validations/params_scope_spec.rb' - - 'spec/grape/validations/single_attribute_iterator_spec.rb' - - 'spec/grape/validations/types/primitive_coercer_spec.rb' - - 'spec/grape/validations/validators/all_or_none_spec.rb' - - 'spec/grape/validations/validators/allow_blank_spec.rb' - - 'spec/grape/validations/validators/at_least_one_of_spec.rb' - 'spec/grape/validations/validators/coerce_spec.rb' - - 'spec/grape/validations/validators/exactly_one_of_spec.rb' - - 'spec/grape/validations/validators/mutual_exclusion_spec.rb' - 'spec/grape/validations_spec.rb' # Offense count: 18 @@ -553,14 +533,13 @@ 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: 48 +# Offense count: 46 # Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. # Include: **/*_spec.rb RSpec/SpecFilePathFormat: @@ -595,7 +574,6 @@ RSpec/SpecFilePathFormat: - 'spec/grape/integration/rack_sendfile_spec.rb' - 'spec/grape/loading_spec.rb' - 'spec/grape/middleware/exception_spec.rb' - - 'spec/grape/validations/attributes_doc_spec.rb' - 'spec/grape/validations/validators/all_or_none_spec.rb' - 'spec/grape/validations/validators/allow_blank_spec.rb' - 'spec/grape/validations/validators/at_least_one_of_spec.rb' @@ -608,17 +586,15 @@ RSpec/SpecFilePathFormat: - 'spec/grape/validations/validators/regexp_spec.rb' - 'spec/grape/validations/validators/same_as_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' - - 'spec/integration/eager_load/eager_load_spec.rb' - 'spec/integration/multi_json/json_spec.rb' - 'spec/integration/multi_xml/xml_spec.rb' - 'spec/integration/no_dry_validation/no_dry_validation_spec.rb' - 'spec/integration/rack_2_0/headers_spec.rb' - 'spec/integration/rack_3_0/headers_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' @@ -660,6 +636,7 @@ RSpec/VoidExpect: - 'spec/grape/dsl/headers_spec.rb' # Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). Style/CombinableLoops: Exclude: - 'spec/grape/endpoint_spec.rb' @@ -696,7 +673,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/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/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/validations.rb b/lib/grape/validations.rb index 54cfc8205e..8ece4db1df 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -3,7 +3,7 @@ module Grape module Validations def self.require_validator(short_name) - Grape::Validations::Validators.const_get("#{short_name.to_s.camelize}Validator") + Grape::Validations::Validators.const_get(:"#{short_name.to_s.camelize}Validator") rescue NameError raise Grape::Exceptions::UnknownValidator, short_name end 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! }