grape 2.1.3 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +9 -7
- data/UPGRADING.md +27 -0
- data/grape.gemspec +7 -6
- data/lib/grape/api/instance.rb +22 -58
- data/lib/grape/api.rb +2 -11
- data/lib/grape/content_types.rb +13 -8
- data/lib/grape/dsl/desc.rb +27 -24
- data/lib/grape/dsl/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +18 -24
- data/lib/grape/dsl/parameters.rb +2 -2
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +5 -12
- data/lib/grape/endpoint.rb +90 -82
- data/lib/grape/error_formatter/base.rb +51 -21
- data/lib/grape/error_formatter/json.rb +7 -15
- data/lib/grape/error_formatter/jsonapi.rb +7 -0
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +13 -20
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +5 -25
- data/lib/grape/exceptions/base.rb +18 -30
- data/lib/grape/exceptions/validation.rb +5 -4
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +4 -6
- data/lib/grape/formatter/serializable_hash.rb +1 -1
- data/lib/grape/formatter/txt.rb +3 -5
- data/lib/grape/formatter/xml.rb +4 -6
- data/lib/grape/formatter.rb +7 -25
- data/lib/grape/http/headers.rb +1 -0
- data/lib/grape/locale/en.yml +1 -0
- data/lib/grape/middleware/base.rb +14 -13
- data/lib/grape/middleware/error.rb +13 -9
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +7 -30
- data/lib/grape/middleware/versioner/base.rb +82 -0
- data/lib/grape/middleware/versioner/header.rb +89 -10
- data/lib/grape/middleware/versioner/param.rb +4 -22
- data/lib/grape/middleware/versioner/path.rb +10 -32
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +6 -8
- data/lib/grape/parser/jsonapi.rb +7 -0
- data/lib/grape/parser/xml.rb +6 -8
- data/lib/grape/parser.rb +5 -23
- data/lib/grape/path.rb +39 -56
- data/lib/grape/request.rb +2 -2
- data/lib/grape/router/base_route.rb +2 -2
- data/lib/grape/router/greedy_route.rb +2 -2
- data/lib/grape/router/pattern.rb +23 -18
- data/lib/grape/router/route.rb +13 -5
- data/lib/grape/router.rb +5 -5
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/validations/contract_scope.rb +2 -39
- data/lib/grape/validations/params_scope.rb +7 -11
- data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
- data/lib/grape/validations/validator_factory.rb +2 -2
- data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +5 -9
- data/lib/grape/validations/validators/coerce_validator.rb +1 -1
- data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
- data/lib/grape/validations/validators/default_validator.rb +1 -1
- data/lib/grape/validations/validators/except_values_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +11 -4
- data/lib/grape/validations/validators/regexp_validator.rb +1 -1
- data/lib/grape/validations/validators/values_validator.rb +15 -57
- data/lib/grape/validations.rb +8 -17
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +1 -1
- metadata +15 -12
- data/lib/grape/util/accept_header_handler.rb +0 -105
- data/lib/grape/util/registrable.rb +0 -15
- data/lib/grape/validations/types/build_coercer.rb +0 -92
data/lib/grape/formatter/txt.rb
CHANGED
@@ -2,11 +2,9 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Formatter
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
|
9
|
-
end
|
5
|
+
class Txt < Base
|
6
|
+
def self.call(object, _env)
|
7
|
+
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
|
10
8
|
end
|
11
9
|
end
|
12
10
|
end
|
data/lib/grape/formatter/xml.rb
CHANGED
@@ -2,13 +2,11 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Formatter
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
return object.to_xml if object.respond_to?(:to_xml)
|
5
|
+
class Xml < Base
|
6
|
+
def self.call(object, _env)
|
7
|
+
return object.to_xml if object.respond_to?(:to_xml)
|
9
8
|
|
10
|
-
|
11
|
-
end
|
9
|
+
raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
|
12
10
|
end
|
13
11
|
end
|
14
12
|
end
|
data/lib/grape/formatter.rb
CHANGED
@@ -2,34 +2,16 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Formatter
|
5
|
-
extend Util::
|
5
|
+
extend Grape::Util::Registry
|
6
6
|
|
7
|
-
|
8
|
-
def builtin_formatters
|
9
|
-
@builtin_formatters ||= {
|
10
|
-
json: Grape::Formatter::Json,
|
11
|
-
jsonapi: Grape::Formatter::Json,
|
12
|
-
serializable_hash: Grape::Formatter::SerializableHash,
|
13
|
-
txt: Grape::Formatter::Txt,
|
14
|
-
xml: Grape::Formatter::Xml
|
15
|
-
}
|
16
|
-
end
|
7
|
+
module_function
|
17
8
|
|
18
|
-
|
19
|
-
builtin_formatters.merge(default_elements).merge!(options[:formatters] || {})
|
20
|
-
end
|
9
|
+
DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
->(obj, _env) { obj }
|
27
|
-
when Symbol
|
28
|
-
method(spec)
|
29
|
-
else
|
30
|
-
spec
|
31
|
-
end
|
32
|
-
end
|
11
|
+
def formatter_for(api_format, formatters)
|
12
|
+
return formatters[api_format] if formatters&.key?(api_format)
|
13
|
+
|
14
|
+
registry[api_format] || DEFAULT_LAMBDA_FORMATTER
|
33
15
|
end
|
34
16
|
end
|
35
17
|
end
|
data/lib/grape/http/headers.rb
CHANGED
data/lib/grape/locale/en.yml
CHANGED
@@ -11,6 +11,7 @@ en:
|
|
11
11
|
except_values: 'has a value not allowed'
|
12
12
|
same_as: 'is not the same as %{parameter}'
|
13
13
|
length: 'is expected to have length within %{min} and %{max}'
|
14
|
+
length_is: 'is expected to have length exactly equal to %{is}'
|
14
15
|
length_min: 'is expected to have length greater than or equal to %{min}'
|
15
16
|
length_max: 'is expected to have length less than or equal to %{max}'
|
16
17
|
missing_vendor_option:
|
@@ -4,18 +4,17 @@ module Grape
|
|
4
4
|
module Middleware
|
5
5
|
class Base
|
6
6
|
include Helpers
|
7
|
+
include Grape::DSL::Headers
|
7
8
|
|
8
9
|
attr_reader :app, :env, :options
|
9
10
|
|
10
11
|
TEXT_HTML = 'text/html'
|
11
12
|
|
12
|
-
include Grape::DSL::Headers
|
13
|
-
|
14
13
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
15
14
|
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
16
15
|
def initialize(app, *options)
|
17
16
|
@app = app
|
18
|
-
@options = options.any? ? default_options.
|
17
|
+
@options = options.any? ? default_options.deep_merge(options.shift) : default_options
|
19
18
|
@app_response = nil
|
20
19
|
end
|
21
20
|
|
@@ -61,22 +60,20 @@ module Grape
|
|
61
60
|
@app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
|
62
61
|
end
|
63
62
|
|
64
|
-
def
|
65
|
-
|
63
|
+
def content_types
|
64
|
+
@content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])
|
66
65
|
end
|
67
66
|
|
68
|
-
def
|
69
|
-
ContentTypes.
|
67
|
+
def mime_types
|
68
|
+
@mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
|
70
69
|
end
|
71
70
|
|
72
|
-
def
|
73
|
-
|
71
|
+
def content_type_for(format)
|
72
|
+
content_types_indifferent_access[format]
|
74
73
|
end
|
75
74
|
|
76
|
-
def
|
77
|
-
|
78
|
-
types_without_params[v.split(';').first] = k
|
79
|
-
end
|
75
|
+
def content_type
|
76
|
+
content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
|
80
77
|
end
|
81
78
|
|
82
79
|
private
|
@@ -89,6 +86,10 @@ module Grape
|
|
89
86
|
when Array then response[1].merge!(headers)
|
90
87
|
end
|
91
88
|
end
|
89
|
+
|
90
|
+
def content_types_indifferent_access
|
91
|
+
@content_types_indifferent_access ||= content_types.with_indifferent_access
|
92
|
+
end
|
92
93
|
end
|
93
94
|
end
|
94
95
|
end
|
@@ -26,7 +26,7 @@ module Grape
|
|
26
26
|
|
27
27
|
def initialize(app, *options)
|
28
28
|
super
|
29
|
-
self.class.
|
29
|
+
self.class.include(@options[:helpers]) if @options[:helpers]
|
30
30
|
end
|
31
31
|
|
32
32
|
def call!(env)
|
@@ -45,7 +45,7 @@ module Grape
|
|
45
45
|
|
46
46
|
def format_message(message, backtrace, original_exception = nil)
|
47
47
|
format = env[Grape::Env::API_FORMAT] || options[:format]
|
48
|
-
formatter = Grape::ErrorFormatter.formatter_for(format,
|
48
|
+
formatter = Grape::ErrorFormatter.formatter_for(format, options[:error_formatters], options[:default_error_formatter])
|
49
49
|
return formatter.call(message, backtrace, options, env, original_exception) if formatter
|
50
50
|
|
51
51
|
throw :error,
|
@@ -65,6 +65,7 @@ module Grape
|
|
65
65
|
|
66
66
|
def error_response(error = {})
|
67
67
|
status = error[:status] || options[:default_status]
|
68
|
+
env[Grape::Env::API_ENDPOINT].status(status) # error! may not have been called
|
68
69
|
message = error[:message] || options[:default_message]
|
69
70
|
headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
|
70
71
|
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
@@ -79,7 +80,7 @@ module Grape
|
|
79
80
|
end
|
80
81
|
|
81
82
|
def rescue_handler_for_base_only_class(klass)
|
82
|
-
error, handler = options[:base_only_rescue_handlers]
|
83
|
+
error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
|
83
84
|
|
84
85
|
return unless error
|
85
86
|
|
@@ -87,7 +88,7 @@ module Grape
|
|
87
88
|
end
|
88
89
|
|
89
90
|
def rescue_handler_for_class_or_its_ancestor(klass)
|
90
|
-
error, handler = options[:rescue_handlers]
|
91
|
+
error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }
|
91
92
|
|
92
93
|
return unless error
|
93
94
|
|
@@ -120,16 +121,17 @@ module Grape
|
|
120
121
|
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
|
121
122
|
end
|
122
123
|
|
123
|
-
|
124
|
-
|
125
|
-
|
124
|
+
if error?(response)
|
125
|
+
error_response(response)
|
126
|
+
elsif response.is_a?(Rack::Response)
|
126
127
|
response
|
127
128
|
else
|
128
|
-
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
|
129
|
+
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
|
129
130
|
end
|
130
131
|
end
|
131
132
|
|
132
133
|
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
|
134
|
+
env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route
|
133
135
|
rack_response(
|
134
136
|
status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
|
135
137
|
format_message(message, backtrace, original_exception)
|
@@ -137,7 +139,9 @@ module Grape
|
|
137
139
|
end
|
138
140
|
|
139
141
|
def error?(response)
|
140
|
-
response.is_a?(Hash)
|
142
|
+
return false unless response.is_a?(Hash)
|
143
|
+
|
144
|
+
response.key?(:message) && response.key?(:status) && response.key?(:headers)
|
141
145
|
end
|
142
146
|
end
|
143
147
|
end
|
@@ -53,8 +53,8 @@ module Grape
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def fetch_formatter(headers, options)
|
56
|
-
api_format = mime_types[headers[Rack::CONTENT_TYPE]]
|
57
|
-
Grape::Formatter.formatter_for(api_format,
|
56
|
+
api_format = env.fetch(Grape::Env::API_FORMAT) { mime_types[headers[Rack::CONTENT_TYPE]] }
|
57
|
+
Grape::Formatter.formatter_for(api_format, options[:formatters])
|
58
58
|
end
|
59
59
|
|
60
60
|
# Set the content type header for the API format if it is not already present.
|
@@ -97,7 +97,7 @@ module Grape
|
|
97
97
|
fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
|
98
98
|
|
99
99
|
throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
|
100
|
-
parser = Grape::Parser.parser_for fmt,
|
100
|
+
parser = Grape::Parser.parser_for fmt, options[:parsers]
|
101
101
|
if parser
|
102
102
|
begin
|
103
103
|
body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
|
@@ -18,44 +18,21 @@ module Grape
|
|
18
18
|
# route.
|
19
19
|
class AcceptVersionHeader < Base
|
20
20
|
def before
|
21
|
-
potential_version =
|
21
|
+
potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]
|
22
|
+
potential_version = potential_version.scrub unless potential_version.nil?
|
22
23
|
|
23
|
-
if strict? && potential_version.
|
24
|
-
# If no Accept-Version header:
|
25
|
-
throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
|
26
|
-
end
|
24
|
+
not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
|
27
25
|
|
28
|
-
return if potential_version.
|
29
|
-
|
30
|
-
# If the requested version is not supported:
|
31
|
-
throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.' unless versions.any? { |v| v.to_s == potential_version }
|
26
|
+
return if potential_version.blank?
|
32
27
|
|
28
|
+
not_acceptable!('The requested version is not supported.') unless potential_version_match?(potential_version)
|
33
29
|
env[Grape::Env::API_VERSION] = potential_version
|
34
30
|
end
|
35
31
|
|
36
32
|
private
|
37
33
|
|
38
|
-
def
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
def strict?
|
43
|
-
options[:version_options] && options[:version_options][:strict]
|
44
|
-
end
|
45
|
-
|
46
|
-
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
47
|
-
# of routes (see Grape::Router) for more information). To prevent
|
48
|
-
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
49
|
-
def cascade?
|
50
|
-
if options[:version_options]&.key?(:cascade)
|
51
|
-
options[:version_options][:cascade]
|
52
|
-
else
|
53
|
-
true
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def error_headers
|
58
|
-
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
34
|
+
def not_acceptable!(message)
|
35
|
+
throw :error, status: 406, headers: error_headers, message: message
|
59
36
|
end
|
60
37
|
end
|
61
38
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
class Base < Grape::Middleware::Base
|
7
|
+
DEFAULT_PATTERN = /.*/i.freeze
|
8
|
+
DEFAULT_PARAMETER = 'apiver'
|
9
|
+
|
10
|
+
def self.inherited(klass)
|
11
|
+
super
|
12
|
+
Versioner.register(klass)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_options
|
16
|
+
{
|
17
|
+
versions: nil,
|
18
|
+
prefix: nil,
|
19
|
+
mount_path: nil,
|
20
|
+
pattern: DEFAULT_PATTERN,
|
21
|
+
version_options: {
|
22
|
+
strict: false,
|
23
|
+
cascade: true,
|
24
|
+
parameter: DEFAULT_PARAMETER
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def versions
|
30
|
+
options[:versions]
|
31
|
+
end
|
32
|
+
|
33
|
+
def prefix
|
34
|
+
options[:prefix]
|
35
|
+
end
|
36
|
+
|
37
|
+
def mount_path
|
38
|
+
options[:mount_path]
|
39
|
+
end
|
40
|
+
|
41
|
+
def pattern
|
42
|
+
options[:pattern]
|
43
|
+
end
|
44
|
+
|
45
|
+
def version_options
|
46
|
+
options[:version_options]
|
47
|
+
end
|
48
|
+
|
49
|
+
def strict?
|
50
|
+
version_options[:strict]
|
51
|
+
end
|
52
|
+
|
53
|
+
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
54
|
+
# of routes (see Grape::Router) for more information). To prevent
|
55
|
+
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
56
|
+
def cascade?
|
57
|
+
version_options[:cascade]
|
58
|
+
end
|
59
|
+
|
60
|
+
def parameter_key
|
61
|
+
version_options[:parameter]
|
62
|
+
end
|
63
|
+
|
64
|
+
def vendor
|
65
|
+
version_options[:vendor]
|
66
|
+
end
|
67
|
+
|
68
|
+
def error_headers
|
69
|
+
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def potential_version_match?(potential_version)
|
73
|
+
versions.blank? || versions.any? { |v| v.to_s == potential_version }
|
74
|
+
end
|
75
|
+
|
76
|
+
def version_not_found!
|
77
|
+
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -23,16 +23,7 @@ module Grape
|
|
23
23
|
# route.
|
24
24
|
class Header < Base
|
25
25
|
def before
|
26
|
-
|
27
|
-
accept_header: env[Grape::Http::Headers::HTTP_ACCEPT],
|
28
|
-
versions: options[:versions],
|
29
|
-
**options.fetch(:version_options) { {} }
|
30
|
-
)
|
31
|
-
|
32
|
-
handler.match_best_quality_media_type!(
|
33
|
-
content_types: content_types,
|
34
|
-
allowed_methods: env[Grape::Env::GRAPE_ALLOWED_METHODS]
|
35
|
-
) do |media_type|
|
26
|
+
match_best_quality_media_type! do |media_type|
|
36
27
|
env.update(
|
37
28
|
Grape::Env::API_TYPE => media_type.type,
|
38
29
|
Grape::Env::API_SUBTYPE => media_type.subtype,
|
@@ -42,6 +33,94 @@ module Grape
|
|
42
33
|
)
|
43
34
|
end
|
44
35
|
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def match_best_quality_media_type!
|
40
|
+
return unless vendor
|
41
|
+
|
42
|
+
strict_header_checks!
|
43
|
+
media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
|
44
|
+
if media_type
|
45
|
+
yield media_type
|
46
|
+
else
|
47
|
+
fail!
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def accept_header
|
52
|
+
env[Grape::Http::Headers::HTTP_ACCEPT]
|
53
|
+
end
|
54
|
+
|
55
|
+
def strict_header_checks!
|
56
|
+
return unless strict?
|
57
|
+
|
58
|
+
accept_header_check!
|
59
|
+
version_and_vendor_check!
|
60
|
+
end
|
61
|
+
|
62
|
+
def accept_header_check!
|
63
|
+
return if accept_header.present?
|
64
|
+
|
65
|
+
invalid_accept_header!('Accept header must be set.')
|
66
|
+
end
|
67
|
+
|
68
|
+
def version_and_vendor_check!
|
69
|
+
return if versions.blank? || version_and_vendor?
|
70
|
+
|
71
|
+
invalid_accept_header!('API vendor or version not found.')
|
72
|
+
end
|
73
|
+
|
74
|
+
def q_values_mime_types
|
75
|
+
@q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
|
76
|
+
end
|
77
|
+
|
78
|
+
def version_and_vendor?
|
79
|
+
q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def invalid_accept_header!(message)
|
83
|
+
raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
|
84
|
+
end
|
85
|
+
|
86
|
+
def invalid_version_header!(message)
|
87
|
+
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
88
|
+
end
|
89
|
+
|
90
|
+
def fail!
|
91
|
+
return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?
|
92
|
+
|
93
|
+
media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
|
94
|
+
vendor_not_found!(media_types) || version_not_found!(media_types)
|
95
|
+
end
|
96
|
+
|
97
|
+
def vendor_not_found!(media_types)
|
98
|
+
return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
|
99
|
+
|
100
|
+
invalid_accept_header!('API vendor not found.')
|
101
|
+
end
|
102
|
+
|
103
|
+
def version_not_found!(media_types)
|
104
|
+
return unless media_types.all? { |media_type| media_type&.version && versions&.exclude?(media_type.version) }
|
105
|
+
|
106
|
+
invalid_version_header!('API version not found.')
|
107
|
+
end
|
108
|
+
|
109
|
+
def available_media_types
|
110
|
+
[].tap do |available_media_types|
|
111
|
+
base_media_type = "application/vnd.#{vendor}"
|
112
|
+
content_types.each_key do |extension|
|
113
|
+
versions&.reverse_each do |version|
|
114
|
+
available_media_types << "#{base_media_type}-#{version}+#{extension}"
|
115
|
+
available_media_types << "#{base_media_type}-#{version}"
|
116
|
+
end
|
117
|
+
available_media_types << "#{base_media_type}+#{extension}"
|
118
|
+
end
|
119
|
+
|
120
|
+
available_media_types << base_media_type
|
121
|
+
available_media_types.concat(content_types.values.flatten)
|
122
|
+
end
|
123
|
+
end
|
45
124
|
end
|
46
125
|
end
|
47
126
|
end
|
@@ -19,31 +19,13 @@ module Grape
|
|
19
19
|
#
|
20
20
|
# env['api.version'] => 'v1'
|
21
21
|
class Param < Base
|
22
|
-
def default_options
|
23
|
-
{
|
24
|
-
version_options: {
|
25
|
-
parameter: 'apiver'
|
26
|
-
}
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
22
|
def before
|
31
|
-
potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[
|
32
|
-
return if potential_version.
|
23
|
+
potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key]
|
24
|
+
return if potential_version.blank?
|
33
25
|
|
34
|
-
|
26
|
+
version_not_found! unless potential_version_match?(potential_version)
|
35
27
|
env[Grape::Env::API_VERSION] = potential_version
|
36
|
-
env[Rack::RACK_REQUEST_QUERY_HASH].delete(
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def paramkey
|
42
|
-
version_options[:parameter] || default_options[:version_options][:parameter]
|
43
|
-
end
|
44
|
-
|
45
|
-
def version_options
|
46
|
-
options[:version_options]
|
28
|
+
env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
|
47
29
|
end
|
48
30
|
end
|
49
31
|
end
|
@@ -17,44 +17,22 @@ module Grape
|
|
17
17
|
# env['api.version'] => 'v1'
|
18
18
|
#
|
19
19
|
class Path < Base
|
20
|
-
def default_options
|
21
|
-
{
|
22
|
-
pattern: /.*/i
|
23
|
-
}
|
24
|
-
end
|
25
|
-
|
26
20
|
def before
|
27
|
-
|
28
|
-
|
21
|
+
path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
|
22
|
+
return if path_info == '/'
|
29
23
|
|
30
|
-
|
31
|
-
|
32
|
-
path = Grape::Router.normalize_path(path)
|
24
|
+
[mount_path, Grape::Router.normalize_path(prefix)].each do |path|
|
25
|
+
path_info.delete_prefix!(path) if path.present? && path != '/' && path_info.start_with?(path)
|
33
26
|
end
|
34
27
|
|
35
|
-
|
36
|
-
|
37
|
-
return unless potential_version&.match?(options[:pattern])
|
38
|
-
|
39
|
-
throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
40
|
-
env[Grape::Env::API_VERSION] = potential_version
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
28
|
+
slash_position = path_info.index('/', 1) # omit the first one
|
29
|
+
return unless slash_position
|
44
30
|
|
45
|
-
|
46
|
-
return
|
31
|
+
potential_version = path_info[1..slash_position - 1]
|
32
|
+
return unless potential_version.match?(pattern)
|
47
33
|
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def mount_path
|
53
|
-
@mount_path ||= options[:mount_path] && options[:mount_path] != '/' ? options[:mount_path] : ''
|
54
|
-
end
|
55
|
-
|
56
|
-
def prefix
|
57
|
-
Grape::Router.normalize_path(options[:prefix].to_s) if options[:prefix]
|
34
|
+
version_not_found! unless potential_version_match?(potential_version)
|
35
|
+
env[Grape::Env::API_VERSION] = potential_version
|
58
36
|
end
|
59
37
|
end
|
60
38
|
end
|
@@ -4,30 +4,23 @@
|
|
4
4
|
# on the requests. The current methods for determining version are:
|
5
5
|
#
|
6
6
|
# :header - version from HTTP Accept header.
|
7
|
+
# :accept_version_header - version from HTTP Accept-Version header
|
7
8
|
# :path - version from uri. e.g. /v1/resource
|
8
9
|
# :param - version from uri query string, e.g. /v1/resource?apiver=v1
|
9
|
-
#
|
10
10
|
# See individual classes for details.
|
11
11
|
module Grape
|
12
12
|
module Middleware
|
13
13
|
module Versioner
|
14
|
+
extend Grape::Util::Registry
|
15
|
+
|
14
16
|
module_function
|
15
17
|
|
16
|
-
# @param strategy [Symbol] :path, :header or :param
|
18
|
+
# @param strategy [Symbol] :path, :header, :accept_version_header or :param
|
17
19
|
# @return a middleware class based on strategy
|
18
20
|
def using(strategy)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
when :header
|
23
|
-
Header
|
24
|
-
when :param
|
25
|
-
Param
|
26
|
-
when :accept_version_header
|
27
|
-
AcceptVersionHeader
|
28
|
-
else
|
29
|
-
raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
|
30
|
-
end
|
21
|
+
raise Grape::Exceptions::InvalidVersionerOption, strategy unless registry.key?(strategy)
|
22
|
+
|
23
|
+
registry[strategy]
|
31
24
|
end
|
32
25
|
end
|
33
26
|
end
|
data/lib/grape/namespace.rb
CHANGED
@@ -12,7 +12,7 @@ module Grape
|
|
12
12
|
# @option options :requirements [Hash] param-regex pairs, all of which must
|
13
13
|
# be met by a request's params for all endpoints in this namespace, or
|
14
14
|
# validation will fail and return a 422.
|
15
|
-
def initialize(space,
|
15
|
+
def initialize(space, options)
|
16
16
|
@space = space.to_s
|
17
17
|
@options = options
|
18
18
|
end
|