From 8c65c6fb56fa57c38ffb0fb4f1bb7deced4a4564 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 26 Jul 2024 12:24:13 +0300 Subject: [PATCH 1/3] AcceptHeaderHandler is now part of Grape::Middleware::Versioner::Header Use `const_get` to find versioner Grape::Middleware::Versioner::* uses `default_options` like other middlewares Add versioner_helpers for Grape::Middleware::Versioner::* Replace `merge` by `deep_merge` in Grape::Middleware::Base initialize Add specs --- lib/grape/endpoint.rb | 7 +- lib/grape/middleware/base.rb | 5 +- lib/grape/middleware/versioner.rb | 17 +-- .../versioner/accept_version_header.rb | 39 ++---- lib/grape/middleware/versioner/header.rb | 105 ++++++++++++++-- lib/grape/middleware/versioner/param.rb | 26 +--- lib/grape/middleware/versioner/path.rb | 42 ++----- lib/grape/middleware/versioner_helpers.rb | 75 ++++++++++++ lib/grape/util/accept_header_handler.rb | 105 ---------------- spec/grape/middleware/versioner_spec.rb | 34 ++++-- spec/grape/util/accept_header_handler_spec.rb | 112 ------------------ 11 files changed, 228 insertions(+), 339 deletions(-) create mode 100644 lib/grape/middleware/versioner_helpers.rb delete mode 100644 lib/grape/util/accept_header_handler.rb delete mode 100644 spec/grape/util/accept_header_handler_spec.rb diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index cc1fdba833..88605d13b8 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -191,8 +191,7 @@ def prepare_default_route_attributes def prepare_version version = namespace_inheritable(:version) - return unless version - return if version.empty? + return if version.blank? version.length == 1 ? version.first : version end @@ -298,9 +297,9 @@ def build_stack(helpers) stack.concat namespace_stackable(:middleware) - if namespace_inheritable(:version) + if namespace_inheritable(:version).present? stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]), - versions: namespace_inheritable(:version)&.flatten, + versions: namespace_inheritable(:version).flatten, version_options: namespace_inheritable(:version_options), prefix: namespace_inheritable(:root_prefix), mount_path: namespace_stackable(:mount_path).first diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index 6a54aa6d76..af7195f86e 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -4,18 +4,17 @@ module Grape module Middleware class Base include Helpers + include Grape::DSL::Headers attr_reader :app, :env, :options TEXT_HTML = 'text/html' - include Grape::DSL::Headers - # @param [Rack Application] app The standard argument for a Rack middleware. # @param [Hash] options A hash of options, simply stored for use by subclasses. def initialize(app, *options) @app = app - @options = options.any? ? default_options.merge(options.shift) : default_options + @options = options.any? ? default_options.deep_merge(options.shift) : default_options @app_response = nil end diff --git a/lib/grape/middleware/versioner.rb b/lib/grape/middleware/versioner.rb index d40c87a27e..6ee053df50 100644 --- a/lib/grape/middleware/versioner.rb +++ b/lib/grape/middleware/versioner.rb @@ -13,21 +13,12 @@ module Middleware module Versioner module_function - # @param strategy [Symbol] :path, :header or :param + # @param strategy [Symbol] :path, :header, :accept_version_header or :param # @return a middleware class based on strategy def using(strategy) - case strategy - when :path - Path - when :header - Header - when :param - Param - when :accept_version_header - AcceptVersionHeader - else - raise Grape::Exceptions::InvalidVersionerOption.new(strategy) - end + Grape::Middleware::Versioner.const_get(:"#{strategy.to_s.classify}") + rescue NameError + raise Grape::Exceptions::InvalidVersionerOption.new(strategy) end end end diff --git a/lib/grape/middleware/versioner/accept_version_header.rb b/lib/grape/middleware/versioner/accept_version_header.rb index 98237e9476..3d639ddf27 100644 --- a/lib/grape/middleware/versioner/accept_version_header.rb +++ b/lib/grape/middleware/versioner/accept_version_header.rb @@ -17,45 +17,22 @@ module Versioner # X-Cascade header to alert Grape::Router to attempt the next matched # route. class AcceptVersionHeader < Base - def before - potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip - - if strict? && potential_version.empty? - # If no Accept-Version header: - throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.' - end + include VersionerHelpers - return if potential_version.empty? + def before + potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]&.strip + throw_error_not_acceptable('Accept-Version header must be set.') if strict? && potential_version.blank? - # 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 } + return if potential_version.blank? + throw_error_not_acceptable('The requested version is not supported.') unless potential_version_match?(potential_version) env[Grape::Env::API_VERSION] = potential_version end private - def versions - options[:versions] || [] - end - - def strict? - options[:version_options] && options[:version_options][:strict] - end - - # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking - # of routes (see Grape::Router) for more information). To prevent - # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`. - def cascade? - if options[:version_options]&.key?(:cascade) - options[:version_options][:cascade] - else - true - end - end - - def error_headers - cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {} + def throw_error_not_acceptable(message) + throw :error, status: 406, headers: error_headers, message: message end end end diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index 33ea25660b..619549304f 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -22,17 +22,10 @@ module Versioner # X-Cascade header to alert Grape::Router to attempt the next matched # route. class Header < Base + include VersionerHelpers + def before - handler = Grape::Util::AcceptHeaderHandler.new( - accept_header: env[Grape::Http::Headers::HTTP_ACCEPT], - versions: options[:versions], - **options.fetch(:version_options) { {} } - ) - - handler.match_best_quality_media_type!( - content_types: content_types, - allowed_methods: env[Grape::Env::GRAPE_ALLOWED_METHODS] - ) do |media_type| + match_best_quality_media_type! do |media_type| env.update( Grape::Env::API_TYPE => media_type.type, Grape::Env::API_SUBTYPE => media_type.subtype, @@ -42,6 +35,98 @@ def before ) end end + + private + + def match_best_quality_media_type! + return unless vendor + + strict_header_checks! + media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types) + if media_type + yield media_type + else + fail!(allowed_methods) + end + end + + def allowed_methods + env[Grape::Env::GRAPE_ALLOWED_METHODS] + end + + def accept_header + env[Grape::Http::Headers::HTTP_ACCEPT] + end + + def strict_header_checks! + return unless strict? + + accept_header_check! + version_and_vendor_check! + end + + def accept_header_check! + return if accept_header.present? + + invalid_accept_header!('Accept header must be set.') + end + + def version_and_vendor_check! + return if versions.blank? || version_and_vendor? + + invalid_accept_header!('API vendor or version not found.') + end + + def q_values_mime_types + @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first) + end + + def version_and_vendor? + q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) } + end + + def invalid_accept_header!(message) + raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers) + end + + def invalid_version_header!(message) + raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers) + end + + def fail!(grape_allowed_methods) + return grape_allowed_methods if grape_allowed_methods.present? + + media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) } + vendor_not_found!(media_types) || version_not_found!(media_types) + end + + def vendor_not_found!(media_types) + return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor } + + invalid_accept_header!('API vendor not found.') + end + + def version_not_found!(media_types) + return unless media_types.all? { |media_type| media_type&.version && versions&.exclude?(media_type.version) } + + invalid_version_header!('API version not found.') + end + + def available_media_types + [].tap do |available_media_types| + base_media_type = "application/vnd.#{vendor}" + content_types.each_key do |extension| + versions&.reverse_each do |version| + available_media_types << "#{base_media_type}-#{version}+#{extension}" + available_media_types << "#{base_media_type}-#{version}" + end + available_media_types << "#{base_media_type}+#{extension}" + end + + available_media_types << base_media_type + available_media_types.concat(content_types.values.flatten) + end + end end end end diff --git a/lib/grape/middleware/versioner/param.rb b/lib/grape/middleware/versioner/param.rb index d12690c155..30acc3e022 100644 --- a/lib/grape/middleware/versioner/param.rb +++ b/lib/grape/middleware/versioner/param.rb @@ -19,31 +19,15 @@ module Versioner # # env['api.version'] => 'v1' class Param < Base - def default_options - { - version_options: { - parameter: 'apiver' - } - } - end + include VersionerHelpers def before - potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[paramkey] - return if potential_version.nil? + potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key] + return if potential_version.blank? - 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 } + throw_api_version_not_found unless potential_version_match?(potential_version) env[Grape::Env::API_VERSION] = potential_version - env[Rack::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Rack::RACK_REQUEST_QUERY_HASH - end - - private - - def paramkey - version_options[:parameter] || default_options[:version_options][:parameter] - end - - def version_options - options[:version_options] + env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH end end end diff --git a/lib/grape/middleware/versioner/path.rb b/lib/grape/middleware/versioner/path.rb index 24fc9010a9..4aab1db424 100644 --- a/lib/grape/middleware/versioner/path.rb +++ b/lib/grape/middleware/versioner/path.rb @@ -17,44 +17,24 @@ module Versioner # env['api.version'] => 'v1' # class Path < Base - def default_options - { - pattern: /.*/i - } - end + include VersionerHelpers def before - path = env[Rack::PATH_INFO].dup - path.sub!(mount_path, '') if mounted_path?(path) + path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO]) + return if path_info == '/' - if prefix && path.index(prefix) == 0 # rubocop:disable all - path.sub!(prefix, '') - path = Grape::Router.normalize_path(path) + [mount_path, Grape::Router.normalize_path(prefix)].each do |path| + path_info.delete_prefix!(path) if path.present? && path != '/' && path_info.start_with?(path) end - pieces = path.split('/') - potential_version = pieces[1] - 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 - end - - private + slash_position = path_info.index('/', 1) # omit the first one + return unless slash_position - def mounted_path?(path) - return false unless mount_path && path.start_with?(mount_path) + potential_version = path_info[1..slash_position - 1] + return unless potential_version.match?(pattern) - rest = path.slice(mount_path.length..-1) - rest.start_with?('/') || rest.empty? - end - - def mount_path - @mount_path ||= options[:mount_path] && options[:mount_path] != '/' ? options[:mount_path] : '' - end - - def prefix - Grape::Router.normalize_path(options[:prefix].to_s) if options[:prefix] + throw_api_version_not_found unless potential_version_match?(potential_version) + env[Grape::Env::API_VERSION] = potential_version end end end diff --git a/lib/grape/middleware/versioner_helpers.rb b/lib/grape/middleware/versioner_helpers.rb new file mode 100644 index 0000000000..c7ff96a473 --- /dev/null +++ b/lib/grape/middleware/versioner_helpers.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Grape + module Middleware + module VersionerHelpers + DEFAULT_PATTERN = /.*/i.freeze + DEFAULT_PARAMETER = 'apiver' + + def default_options + { + versions: nil, + prefix: nil, + mount_path: nil, + pattern: DEFAULT_PATTERN, + version_options: { + strict: false, + cascade: true, + parameter: DEFAULT_PARAMETER + } + } + end + + def versions + options[:versions] + end + + def prefix + options[:prefix] + end + + def mount_path + options[:mount_path] + end + + def pattern + options[:pattern] + end + + def version_options + options[:version_options] + end + + def strict? + version_options[:strict] + end + + # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking + # of routes (see Grape::Router) for more information). To prevent + # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`. + def cascade? + version_options[:cascade] + end + + def parameter_key + version_options[:parameter] + end + + def vendor + version_options[:vendor] + end + + def error_headers + cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {} + end + + def potential_version_match?(potential_version) + versions.blank? || versions.any? { |v| v.to_s == potential_version } + end + + def throw_api_version_not_found + throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } + end + end + end +end diff --git a/lib/grape/util/accept_header_handler.rb b/lib/grape/util/accept_header_handler.rb deleted file mode 100644 index 3098f3a822..0000000000 --- a/lib/grape/util/accept_header_handler.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -module Grape - module Util - class AcceptHeaderHandler - attr_reader :accept_header, :versions, :vendor, :strict, :cascade - - def initialize(accept_header:, versions:, **options) - @accept_header = accept_header - @versions = versions - @vendor = options.fetch(:vendor, nil) - @strict = options.fetch(:strict, false) - @cascade = options.fetch(:cascade, true) - end - - def match_best_quality_media_type!(content_types: Grape::ContentTypes::DEFAULTS, allowed_methods: nil) - return unless vendor - - strict_header_checks! - media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types(content_types)) - if media_type - yield media_type - else - fail!(allowed_methods) - end - end - - private - - def strict_header_checks! - return unless strict - - accept_header_check! - version_and_vendor_check! - end - - def accept_header_check! - return if accept_header.present? - - invalid_accept_header!('Accept header must be set.') - end - - def version_and_vendor_check! - return if versions.blank? || version_and_vendor? - - invalid_accept_header!('API vendor or version not found.') - end - - def q_values_mime_types - @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first) - end - - def version_and_vendor? - q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) } - end - - def invalid_accept_header!(message) - raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers) - end - - def invalid_version_header!(message) - raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers) - end - - def fail!(grape_allowed_methods) - return grape_allowed_methods if grape_allowed_methods.present? - - media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) } - vendor_not_found!(media_types) || version_not_found!(media_types) - end - - def vendor_not_found!(media_types) - return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor } - - invalid_accept_header!('API vendor not found.') - end - - def version_not_found!(media_types) - return unless media_types.all? { |media_type| media_type&.version && versions.exclude?(media_type.version) } - - invalid_version_header!('API version not found.') - end - - def error_headers - cascade ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {} - end - - def available_media_types(content_types) - [].tap do |available_media_types| - base_media_type = "application/vnd.#{vendor}" - content_types.each_key do |extension| - versions&.reverse_each do |version| - available_media_types << "#{base_media_type}-#{version}+#{extension}" - available_media_types << "#{base_media_type}-#{version}" - end - available_media_types << "#{base_media_type}+#{extension}" - end - - available_media_types << base_media_type - available_media_types.concat(content_types.values.flatten) - end - end - end - end -end diff --git a/spec/grape/middleware/versioner_spec.rb b/spec/grape/middleware/versioner_spec.rb index f42eaa5fe4..eb7b95b3cc 100644 --- a/spec/grape/middleware/versioner_spec.rb +++ b/spec/grape/middleware/versioner_spec.rb @@ -1,21 +1,37 @@ # frozen_string_literal: true describe Grape::Middleware::Versioner do - let(:klass) { described_class } + subject { described_class.using(strategy) } - it 'recognizes :path' do - expect(klass.using(:path)).to eq(Grape::Middleware::Versioner::Path) + context 'when :path' do + let(:strategy) { :path } + + it { is_expected.to eq(Grape::Middleware::Versioner::Path) } end - it 'recognizes :header' do - expect(klass.using(:header)).to eq(Grape::Middleware::Versioner::Header) + context 'when :header' do + let(:strategy) { :header } + + it { is_expected.to eq(Grape::Middleware::Versioner::Header) } end - it 'recognizes :param' do - expect(klass.using(:param)).to eq(Grape::Middleware::Versioner::Param) + context 'when :param' do + let(:strategy) { :param } + + it { is_expected.to eq(Grape::Middleware::Versioner::Param) } end - it 'recognizes :accept_version_header' do - expect(klass.using(:accept_version_header)).to eq(Grape::Middleware::Versioner::AcceptVersionHeader) + context 'when :accept_version_header' do + let(:strategy) { :accept_version_header } + + it { is_expected.to eq(Grape::Middleware::Versioner::AcceptVersionHeader) } + end + + context 'when unknown' do + let(:strategy) { :unknown } + + it 'raises an error' do + expect { subject }.to raise_error Grape::Exceptions::InvalidVersionerOption, Grape::Exceptions::InvalidVersionerOption.new(strategy).message + end end end diff --git a/spec/grape/util/accept_header_handler_spec.rb b/spec/grape/util/accept_header_handler_spec.rb deleted file mode 100644 index 6c94f05ecc..0000000000 --- a/spec/grape/util/accept_header_handler_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Grape::Util::AcceptHeaderHandler do - subject(:match_best_quality_media_type!) { instance.match_best_quality_media_type! } - - let(:instance) do - described_class.new( - accept_header: accept_header, - versions: versions, - **options - ) - end - let(:accept_header) { '*/*' } - let(:versions) { ['v1'] } - let(:options) { {} } - - shared_examples 'an invalid accept header exception' do |message| - before do - allow(Grape::Exceptions::InvalidAcceptHeader).to receive(:new) - .with(message, { Grape::Http::Headers::X_CASCADE => 'pass' }) - .and_call_original - end - - it 'raises a Grape::Exceptions::InvalidAcceptHeader' do - expect { match_best_quality_media_type! }.to raise_error(Grape::Exceptions::InvalidAcceptHeader) - end - end - - describe '#match_best_quality_media_type!' do - context 'when no vendor set' do - let(:options) do - { - vendor: nil - } - end - - it { is_expected.to be_nil } - end - - context 'when strict header check' do - let(:options) do - { - vendor: 'vendor', - strict: true - } - end - - context 'when accept_header blank' do - let(:accept_header) { nil } - - it_behaves_like 'an invalid accept header exception', 'Accept header must be set.' - end - - context 'when vendor not found' do - let(:accept_header) { '*/*' } - - it_behaves_like 'an invalid accept header exception', 'API vendor or version not found.' - end - end - - context 'when media_type found' do - let(:options) do - { - vendor: 'vendor' - } - end - - let(:accept_header) { 'application/vnd.vendor-v1+json' } - - it 'yields a media type' do - expect { |b| instance.match_best_quality_media_type!(&b) }.to yield_with_args(Grape::Util::MediaType.new(type: 'application', subtype: 'vnd.vendor-v1+json')) - end - end - - context 'when media_type is not found' do - let(:options) do - { - vendor: 'vendor' - } - end - - let(:accept_header) { 'application/vnd.another_vendor-v1+json' } - - context 'when allowed_methods present' do - subject { instance.match_best_quality_media_type!(allowed_methods: allowed_methods) } - - let(:allowed_methods) { [Rack::OPTIONS] } - - it { is_expected.to match_array(allowed_methods) } - end - - context 'when vendor not found' do - it_behaves_like 'an invalid accept header exception', 'API vendor not found.' - end - - context 'when version not found' do - let(:versions) { ['v2'] } - let(:accept_header) { 'application/vnd.vendor-v1+json' } - - before do - allow(Grape::Exceptions::InvalidVersionHeader).to receive(:new) - .with('API version not found.', { Grape::Http::Headers::X_CASCADE => 'pass' }) - .and_call_original - end - - it 'raises a Grape::Exceptions::InvalidAcceptHeader' do - expect { match_best_quality_media_type! }.to raise_error(Grape::Exceptions::InvalidVersionHeader) - end - end - end - end -end From 3b1515e12de7d453c880b8adb6f23e29730f0d12 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 26 Jul 2024 13:14:22 +0300 Subject: [PATCH 2/3] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f22f7228e..22ea580d92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * [#2475](https://github.com/ruby-grape/grape/pull/2475): Remove Grape::Util::Registrable - [@ericproulx](https://github.com/ericproulx). +* [#2484](https://github.com/ruby-grape/grape/pull/2484): Refactor versioner middlewares - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes From c2f3578b52871eb78d87eaeb9255a8b782d90a03 Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 27 Jul 2024 13:00:30 +0300 Subject: [PATCH 3/3] Remove prefix throw_ and add! Use `camelize` instead of `classify` --- lib/grape/middleware/versioner.rb | 6 +++--- lib/grape/middleware/versioner/accept_version_header.rb | 6 +++--- lib/grape/middleware/versioner/param.rb | 2 +- lib/grape/middleware/versioner/path.rb | 2 +- lib/grape/middleware/versioner_helpers.rb | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/grape/middleware/versioner.rb b/lib/grape/middleware/versioner.rb index 6ee053df50..1589f9b76c 100644 --- a/lib/grape/middleware/versioner.rb +++ b/lib/grape/middleware/versioner.rb @@ -4,9 +4,9 @@ # on the requests. The current methods for determining version are: # # :header - version from HTTP Accept header. +# :accept_version_header - version from HTTP Accept-Version header # :path - version from uri. e.g. /v1/resource # :param - version from uri query string, e.g. /v1/resource?apiver=v1 -# # See individual classes for details. module Grape module Middleware @@ -16,9 +16,9 @@ module Versioner # @param strategy [Symbol] :path, :header, :accept_version_header or :param # @return a middleware class based on strategy def using(strategy) - Grape::Middleware::Versioner.const_get(:"#{strategy.to_s.classify}") + Grape::Middleware::Versioner.const_get(:"#{strategy.to_s.camelize}") rescue NameError - raise Grape::Exceptions::InvalidVersionerOption.new(strategy) + raise Grape::Exceptions::InvalidVersionerOption, strategy end end end diff --git a/lib/grape/middleware/versioner/accept_version_header.rb b/lib/grape/middleware/versioner/accept_version_header.rb index 3d639ddf27..1cf2bb6748 100644 --- a/lib/grape/middleware/versioner/accept_version_header.rb +++ b/lib/grape/middleware/versioner/accept_version_header.rb @@ -21,17 +21,17 @@ class AcceptVersionHeader < Base def before potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]&.strip - throw_error_not_acceptable('Accept-Version header must be set.') if strict? && potential_version.blank? + not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank? return if potential_version.blank? - throw_error_not_acceptable('The requested version is not supported.') unless potential_version_match?(potential_version) + not_acceptable!('The requested version is not supported.') unless potential_version_match?(potential_version) env[Grape::Env::API_VERSION] = potential_version end private - def throw_error_not_acceptable(message) + def not_acceptable!(message) throw :error, status: 406, headers: error_headers, message: message end end diff --git a/lib/grape/middleware/versioner/param.rb b/lib/grape/middleware/versioner/param.rb index 30acc3e022..0c8f88a47c 100644 --- a/lib/grape/middleware/versioner/param.rb +++ b/lib/grape/middleware/versioner/param.rb @@ -25,7 +25,7 @@ def before potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key] return if potential_version.blank? - throw_api_version_not_found unless potential_version_match?(potential_version) + version_not_found! unless potential_version_match?(potential_version) env[Grape::Env::API_VERSION] = potential_version env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH end diff --git a/lib/grape/middleware/versioner/path.rb b/lib/grape/middleware/versioner/path.rb index 4aab1db424..c824f2df81 100644 --- a/lib/grape/middleware/versioner/path.rb +++ b/lib/grape/middleware/versioner/path.rb @@ -33,7 +33,7 @@ def before potential_version = path_info[1..slash_position - 1] return unless potential_version.match?(pattern) - throw_api_version_not_found unless potential_version_match?(potential_version) + version_not_found! unless potential_version_match?(potential_version) env[Grape::Env::API_VERSION] = potential_version end end diff --git a/lib/grape/middleware/versioner_helpers.rb b/lib/grape/middleware/versioner_helpers.rb index c7ff96a473..0cce2055cb 100644 --- a/lib/grape/middleware/versioner_helpers.rb +++ b/lib/grape/middleware/versioner_helpers.rb @@ -67,7 +67,7 @@ def potential_version_match?(potential_version) versions.blank? || versions.any? { |v| v.to_s == potential_version } end - def throw_api_version_not_found + def version_not_found! throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } end end