grape 2.1.2 → 2.2.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 +27 -0
- data/README.md +4 -3
- data/UPGRADING.md +8 -0
- data/grape.gemspec +2 -1
- data/lib/grape/content_types.rb +13 -8
- data/lib/grape/dsl/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +10 -3
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/endpoint.rb +32 -21
- data/lib/grape/error_formatter/json.rb +13 -4
- data/lib/grape/error_formatter.rb +13 -25
- data/lib/grape/formatter.rb +15 -25
- data/lib/grape/locale/en.yml +1 -0
- data/lib/grape/middleware/base.rb +14 -13
- data/lib/grape/middleware/error.rb +11 -9
- data/lib/grape/middleware/formatter.rb +2 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +8 -31
- data/lib/grape/middleware/versioner/header.rb +95 -10
- data/lib/grape/middleware/versioner/param.rb +5 -21
- data/lib/grape/middleware/versioner/path.rb +11 -31
- data/lib/grape/middleware/versioner.rb +5 -14
- data/lib/grape/middleware/versioner_helpers.rb +75 -0
- data/lib/grape/parser.rb +8 -24
- data/lib/grape/validations/params_scope.rb +7 -1
- data/lib/grape/validations/validators/length_validator.rb +10 -3
- data/lib/grape/version.rb +1 -1
- metadata +7 -7
- data/lib/grape/util/accept_header_handler.rb +0 -105
- data/lib/grape/util/registrable.rb +0 -15
@@ -54,7 +54,7 @@ module Grape
|
|
54
54
|
|
55
55
|
def fetch_formatter(headers, options)
|
56
56
|
api_format = mime_types[headers[Rack::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
|
57
|
-
Grape::Formatter.formatter_for(api_format,
|
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))
|
@@ -17,45 +17,22 @@ module Grape
|
|
17
17
|
# X-Cascade header to alert Grape::Router to attempt the next matched
|
18
18
|
# route.
|
19
19
|
class AcceptVersionHeader < Base
|
20
|
-
|
21
|
-
potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip
|
22
|
-
|
23
|
-
if strict? && potential_version.empty?
|
24
|
-
# If no Accept-Version header:
|
25
|
-
throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
|
26
|
-
end
|
20
|
+
include VersionerHelpers
|
27
21
|
|
28
|
-
|
22
|
+
def before
|
23
|
+
potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]&.strip
|
24
|
+
not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
|
29
25
|
|
30
|
-
|
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
|
@@ -22,17 +22,10 @@ module Grape
|
|
22
22
|
# X-Cascade header to alert Grape::Router to attempt the next matched
|
23
23
|
# route.
|
24
24
|
class Header < Base
|
25
|
+
include VersionerHelpers
|
26
|
+
|
25
27
|
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|
|
28
|
+
match_best_quality_media_type! do |media_type|
|
36
29
|
env.update(
|
37
30
|
Grape::Env::API_TYPE => media_type.type,
|
38
31
|
Grape::Env::API_SUBTYPE => media_type.subtype,
|
@@ -42,6 +35,98 @@ module Grape
|
|
42
35
|
)
|
43
36
|
end
|
44
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def match_best_quality_media_type!
|
42
|
+
return unless vendor
|
43
|
+
|
44
|
+
strict_header_checks!
|
45
|
+
media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
|
46
|
+
if media_type
|
47
|
+
yield media_type
|
48
|
+
else
|
49
|
+
fail!(allowed_methods)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def allowed_methods
|
54
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS]
|
55
|
+
end
|
56
|
+
|
57
|
+
def accept_header
|
58
|
+
env[Grape::Http::Headers::HTTP_ACCEPT]
|
59
|
+
end
|
60
|
+
|
61
|
+
def strict_header_checks!
|
62
|
+
return unless strict?
|
63
|
+
|
64
|
+
accept_header_check!
|
65
|
+
version_and_vendor_check!
|
66
|
+
end
|
67
|
+
|
68
|
+
def accept_header_check!
|
69
|
+
return if accept_header.present?
|
70
|
+
|
71
|
+
invalid_accept_header!('Accept header must be set.')
|
72
|
+
end
|
73
|
+
|
74
|
+
def version_and_vendor_check!
|
75
|
+
return if versions.blank? || version_and_vendor?
|
76
|
+
|
77
|
+
invalid_accept_header!('API vendor or version not found.')
|
78
|
+
end
|
79
|
+
|
80
|
+
def q_values_mime_types
|
81
|
+
@q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
|
82
|
+
end
|
83
|
+
|
84
|
+
def version_and_vendor?
|
85
|
+
q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def invalid_accept_header!(message)
|
89
|
+
raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
|
90
|
+
end
|
91
|
+
|
92
|
+
def invalid_version_header!(message)
|
93
|
+
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
94
|
+
end
|
95
|
+
|
96
|
+
def fail!(grape_allowed_methods)
|
97
|
+
return grape_allowed_methods if grape_allowed_methods.present?
|
98
|
+
|
99
|
+
media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
|
100
|
+
vendor_not_found!(media_types) || version_not_found!(media_types)
|
101
|
+
end
|
102
|
+
|
103
|
+
def vendor_not_found!(media_types)
|
104
|
+
return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
|
105
|
+
|
106
|
+
invalid_accept_header!('API vendor not found.')
|
107
|
+
end
|
108
|
+
|
109
|
+
def version_not_found!(media_types)
|
110
|
+
return unless media_types.all? { |media_type| media_type&.version && versions&.exclude?(media_type.version) }
|
111
|
+
|
112
|
+
invalid_version_header!('API version not found.')
|
113
|
+
end
|
114
|
+
|
115
|
+
def available_media_types
|
116
|
+
[].tap do |available_media_types|
|
117
|
+
base_media_type = "application/vnd.#{vendor}"
|
118
|
+
content_types.each_key do |extension|
|
119
|
+
versions&.reverse_each do |version|
|
120
|
+
available_media_types << "#{base_media_type}-#{version}+#{extension}"
|
121
|
+
available_media_types << "#{base_media_type}-#{version}"
|
122
|
+
end
|
123
|
+
available_media_types << "#{base_media_type}+#{extension}"
|
124
|
+
end
|
125
|
+
|
126
|
+
available_media_types << base_media_type
|
127
|
+
available_media_types.concat(content_types.values.flatten)
|
128
|
+
end
|
129
|
+
end
|
45
130
|
end
|
46
131
|
end
|
47
132
|
end
|
@@ -19,31 +19,15 @@ module Grape
|
|
19
19
|
#
|
20
20
|
# env['api.version'] => 'v1'
|
21
21
|
class Param < Base
|
22
|
-
|
23
|
-
{
|
24
|
-
version_options: {
|
25
|
-
parameter: 'apiver'
|
26
|
-
}
|
27
|
-
}
|
28
|
-
end
|
22
|
+
include VersionerHelpers
|
29
23
|
|
30
24
|
def before
|
31
|
-
potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[
|
32
|
-
return if potential_version.
|
25
|
+
potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key]
|
26
|
+
return if potential_version.blank?
|
33
27
|
|
34
|
-
|
28
|
+
version_not_found! unless potential_version_match?(potential_version)
|
35
29
|
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]
|
30
|
+
env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
|
47
31
|
end
|
48
32
|
end
|
49
33
|
end
|
@@ -17,44 +17,24 @@ module Grape
|
|
17
17
|
# env['api.version'] => 'v1'
|
18
18
|
#
|
19
19
|
class Path < Base
|
20
|
-
|
21
|
-
{
|
22
|
-
pattern: /.*/i
|
23
|
-
}
|
24
|
-
end
|
20
|
+
include VersionerHelpers
|
25
21
|
|
26
22
|
def before
|
27
|
-
|
28
|
-
|
23
|
+
path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
|
24
|
+
return if path_info == '/'
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
path = Grape::Router.normalize_path(path)
|
26
|
+
[mount_path, Grape::Router.normalize_path(prefix)].each do |path|
|
27
|
+
path_info.delete_prefix!(path) if path.present? && path != '/' && path_info.start_with?(path)
|
33
28
|
end
|
34
29
|
|
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
|
30
|
+
slash_position = path_info.index('/', 1) # omit the first one
|
31
|
+
return unless slash_position
|
44
32
|
|
45
|
-
|
46
|
-
return
|
33
|
+
potential_version = path_info[1..slash_position - 1]
|
34
|
+
return unless potential_version.match?(pattern)
|
47
35
|
|
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]
|
36
|
+
version_not_found! unless potential_version_match?(potential_version)
|
37
|
+
env[Grape::Env::API_VERSION] = potential_version
|
58
38
|
end
|
59
39
|
end
|
60
40
|
end
|
@@ -4,30 +4,21 @@
|
|
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
14
|
module_function
|
15
15
|
|
16
|
-
# @param strategy [Symbol] :path, :header or :param
|
16
|
+
# @param strategy [Symbol] :path, :header, :accept_version_header or :param
|
17
17
|
# @return a middleware class based on strategy
|
18
18
|
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
|
19
|
+
Grape::Middleware::Versioner.const_get(:"#{strategy.to_s.camelize}")
|
20
|
+
rescue NameError
|
21
|
+
raise Grape::Exceptions::InvalidVersionerOption, strategy
|
31
22
|
end
|
32
23
|
end
|
33
24
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module VersionerHelpers
|
6
|
+
DEFAULT_PATTERN = /.*/i.freeze
|
7
|
+
DEFAULT_PARAMETER = 'apiver'
|
8
|
+
|
9
|
+
def default_options
|
10
|
+
{
|
11
|
+
versions: nil,
|
12
|
+
prefix: nil,
|
13
|
+
mount_path: nil,
|
14
|
+
pattern: DEFAULT_PATTERN,
|
15
|
+
version_options: {
|
16
|
+
strict: false,
|
17
|
+
cascade: true,
|
18
|
+
parameter: DEFAULT_PARAMETER
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def versions
|
24
|
+
options[:versions]
|
25
|
+
end
|
26
|
+
|
27
|
+
def prefix
|
28
|
+
options[:prefix]
|
29
|
+
end
|
30
|
+
|
31
|
+
def mount_path
|
32
|
+
options[:mount_path]
|
33
|
+
end
|
34
|
+
|
35
|
+
def pattern
|
36
|
+
options[:pattern]
|
37
|
+
end
|
38
|
+
|
39
|
+
def version_options
|
40
|
+
options[:version_options]
|
41
|
+
end
|
42
|
+
|
43
|
+
def strict?
|
44
|
+
version_options[:strict]
|
45
|
+
end
|
46
|
+
|
47
|
+
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
48
|
+
# of routes (see Grape::Router) for more information). To prevent
|
49
|
+
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
50
|
+
def cascade?
|
51
|
+
version_options[:cascade]
|
52
|
+
end
|
53
|
+
|
54
|
+
def parameter_key
|
55
|
+
version_options[:parameter]
|
56
|
+
end
|
57
|
+
|
58
|
+
def vendor
|
59
|
+
version_options[:vendor]
|
60
|
+
end
|
61
|
+
|
62
|
+
def error_headers
|
63
|
+
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def potential_version_match?(potential_version)
|
67
|
+
versions.blank? || versions.any? { |v| v.to_s == potential_version }
|
68
|
+
end
|
69
|
+
|
70
|
+
def version_not_found!
|
71
|
+
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/grape/parser.rb
CHANGED
@@ -2,32 +2,16 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Parser
|
5
|
-
|
5
|
+
module_function
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
xml: Grape::Parser::Xml
|
13
|
-
}
|
14
|
-
end
|
7
|
+
DEFAULTS = {
|
8
|
+
json: Grape::Parser::Json,
|
9
|
+
jsonapi: Grape::Parser::Json,
|
10
|
+
xml: Grape::Parser::Xml
|
11
|
+
}.freeze
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def parser_for(api_format, **options)
|
21
|
-
spec = parsers(**options)[api_format]
|
22
|
-
case spec
|
23
|
-
when nil
|
24
|
-
nil
|
25
|
-
when Symbol
|
26
|
-
method(spec)
|
27
|
-
else
|
28
|
-
spec
|
29
|
-
end
|
30
|
-
end
|
13
|
+
def parser_for(format, parsers = nil)
|
14
|
+
parsers&.key?(format) ? parsers[format] : DEFAULTS[format]
|
31
15
|
end
|
32
16
|
end
|
33
17
|
end
|
@@ -190,7 +190,13 @@ module Grape
|
|
190
190
|
#
|
191
191
|
# @return [Array<Symbol>] the nesting/path of the current parameter scope
|
192
192
|
def full_path
|
193
|
-
nested?
|
193
|
+
if nested?
|
194
|
+
(@parent.full_path + [@element])
|
195
|
+
elsif lateral?
|
196
|
+
@parent.full_path
|
197
|
+
else
|
198
|
+
[]
|
199
|
+
end
|
194
200
|
end
|
195
201
|
|
196
202
|
private
|
@@ -7,20 +7,25 @@ module Grape
|
|
7
7
|
def initialize(attrs, options, required, scope, **opts)
|
8
8
|
@min = options[:min]
|
9
9
|
@max = options[:max]
|
10
|
+
@is = options[:is]
|
10
11
|
|
11
12
|
super
|
12
13
|
|
13
14
|
raise ArgumentError, 'min must be an integer greater than or equal to zero' if !@min.nil? && (!@min.is_a?(Integer) || @min.negative?)
|
14
15
|
raise ArgumentError, 'max must be an integer greater than or equal to zero' if !@max.nil? && (!@max.is_a?(Integer) || @max.negative?)
|
15
16
|
raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if !@min.nil? && !@max.nil? && @min > @max
|
17
|
+
|
18
|
+
return if @is.nil?
|
19
|
+
raise ArgumentError, 'is must be an integer greater than zero' if !@is.is_a?(Integer) || !@is.positive?
|
20
|
+
raise ArgumentError, 'is cannot be combined with min or max' if !@min.nil? || !@max.nil?
|
16
21
|
end
|
17
22
|
|
18
23
|
def validate_param!(attr_name, params)
|
19
24
|
param = params[attr_name]
|
20
25
|
|
21
|
-
|
26
|
+
return unless param.respond_to?(:length)
|
22
27
|
|
23
|
-
return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max)
|
28
|
+
return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)
|
24
29
|
|
25
30
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: build_message)
|
26
31
|
end
|
@@ -32,8 +37,10 @@ module Grape
|
|
32
37
|
format I18n.t(:length, scope: 'grape.errors.messages'), min: @min, max: @max
|
33
38
|
elsif @min
|
34
39
|
format I18n.t(:length_min, scope: 'grape.errors.messages'), min: @min
|
35
|
-
|
40
|
+
elsif @max
|
36
41
|
format I18n.t(:length_max, scope: 'grape.errors.messages'), max: @max
|
42
|
+
else
|
43
|
+
format I18n.t(:length_is, scope: 'grape.errors.messages'), is: @is
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|
data/lib/grape/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bleigh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -172,6 +172,7 @@ files:
|
|
172
172
|
- lib/grape/middleware/versioner/header.rb
|
173
173
|
- lib/grape/middleware/versioner/param.rb
|
174
174
|
- lib/grape/middleware/versioner/path.rb
|
175
|
+
- lib/grape/middleware/versioner_helpers.rb
|
175
176
|
- lib/grape/namespace.rb
|
176
177
|
- lib/grape/parser.rb
|
177
178
|
- lib/grape/parser/json.rb
|
@@ -189,7 +190,6 @@ files:
|
|
189
190
|
- lib/grape/serve_stream/sendfile_response.rb
|
190
191
|
- lib/grape/serve_stream/stream_response.rb
|
191
192
|
- lib/grape/types/invalid_value.rb
|
192
|
-
- lib/grape/util/accept_header_handler.rb
|
193
193
|
- lib/grape/util/base_inheritable.rb
|
194
194
|
- lib/grape/util/cache.rb
|
195
195
|
- lib/grape/util/endpoint_configuration.rb
|
@@ -203,7 +203,6 @@ files:
|
|
203
203
|
- lib/grape/util/lazy/value_enumerable.rb
|
204
204
|
- lib/grape/util/lazy/value_hash.rb
|
205
205
|
- lib/grape/util/media_type.rb
|
206
|
-
- lib/grape/util/registrable.rb
|
207
206
|
- lib/grape/util/reverse_stackable_values.rb
|
208
207
|
- lib/grape/util/stackable_values.rb
|
209
208
|
- lib/grape/util/strict_hash_configuration.rb
|
@@ -251,9 +250,10 @@ licenses:
|
|
251
250
|
- MIT
|
252
251
|
metadata:
|
253
252
|
bug_tracker_uri: https://github.com/ruby-grape/grape/issues
|
254
|
-
changelog_uri: https://github.com/ruby-grape/grape/blob/v2.
|
255
|
-
documentation_uri: https://www.rubydoc.info/gems/grape/2.
|
256
|
-
source_code_uri: https://github.com/ruby-grape/grape/tree/v2.
|
253
|
+
changelog_uri: https://github.com/ruby-grape/grape/blob/v2.2.0/CHANGELOG.md
|
254
|
+
documentation_uri: https://www.rubydoc.info/gems/grape/2.2.0
|
255
|
+
source_code_uri: https://github.com/ruby-grape/grape/tree/v2.2.0
|
256
|
+
rubygems_mfa_required: 'true'
|
257
257
|
post_install_message:
|
258
258
|
rdoc_options: []
|
259
259
|
require_paths:
|
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Grape
|
4
|
-
module Util
|
5
|
-
class AcceptHeaderHandler
|
6
|
-
attr_reader :accept_header, :versions, :vendor, :strict, :cascade
|
7
|
-
|
8
|
-
def initialize(accept_header:, versions:, **options)
|
9
|
-
@accept_header = accept_header
|
10
|
-
@versions = versions
|
11
|
-
@vendor = options.fetch(:vendor, nil)
|
12
|
-
@strict = options.fetch(:strict, false)
|
13
|
-
@cascade = options.fetch(:cascade, true)
|
14
|
-
end
|
15
|
-
|
16
|
-
def match_best_quality_media_type!(content_types: Grape::ContentTypes::CONTENT_TYPES, allowed_methods: nil)
|
17
|
-
return unless vendor
|
18
|
-
|
19
|
-
strict_header_checks!
|
20
|
-
media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types(content_types))
|
21
|
-
if media_type
|
22
|
-
yield media_type
|
23
|
-
else
|
24
|
-
fail!(allowed_methods)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def strict_header_checks!
|
31
|
-
return unless strict
|
32
|
-
|
33
|
-
accept_header_check!
|
34
|
-
version_and_vendor_check!
|
35
|
-
end
|
36
|
-
|
37
|
-
def accept_header_check!
|
38
|
-
return if accept_header.present?
|
39
|
-
|
40
|
-
invalid_accept_header!('Accept header must be set.')
|
41
|
-
end
|
42
|
-
|
43
|
-
def version_and_vendor_check!
|
44
|
-
return if versions.blank? || version_and_vendor?
|
45
|
-
|
46
|
-
invalid_accept_header!('API vendor or version not found.')
|
47
|
-
end
|
48
|
-
|
49
|
-
def q_values_mime_types
|
50
|
-
@q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
|
51
|
-
end
|
52
|
-
|
53
|
-
def version_and_vendor?
|
54
|
-
q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
|
55
|
-
end
|
56
|
-
|
57
|
-
def invalid_accept_header!(message)
|
58
|
-
raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
|
59
|
-
end
|
60
|
-
|
61
|
-
def invalid_version_header!(message)
|
62
|
-
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
63
|
-
end
|
64
|
-
|
65
|
-
def fail!(grape_allowed_methods)
|
66
|
-
return grape_allowed_methods if grape_allowed_methods.present?
|
67
|
-
|
68
|
-
media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
|
69
|
-
vendor_not_found!(media_types) || version_not_found!(media_types)
|
70
|
-
end
|
71
|
-
|
72
|
-
def vendor_not_found!(media_types)
|
73
|
-
return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
|
74
|
-
|
75
|
-
invalid_accept_header!('API vendor not found.')
|
76
|
-
end
|
77
|
-
|
78
|
-
def version_not_found!(media_types)
|
79
|
-
return unless media_types.all? { |media_type| media_type&.version && versions.exclude?(media_type.version) }
|
80
|
-
|
81
|
-
invalid_version_header!('API version not found.')
|
82
|
-
end
|
83
|
-
|
84
|
-
def error_headers
|
85
|
-
cascade ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
86
|
-
end
|
87
|
-
|
88
|
-
def available_media_types(content_types)
|
89
|
-
[].tap do |available_media_types|
|
90
|
-
base_media_type = "application/vnd.#{vendor}"
|
91
|
-
content_types.each_key do |extension|
|
92
|
-
versions&.reverse_each do |version|
|
93
|
-
available_media_types << "#{base_media_type}-#{version}+#{extension}"
|
94
|
-
available_media_types << "#{base_media_type}-#{version}"
|
95
|
-
end
|
96
|
-
available_media_types << "#{base_media_type}+#{extension}"
|
97
|
-
end
|
98
|
-
|
99
|
-
available_media_types << base_media_type
|
100
|
-
available_media_types.concat(content_types.values.flatten)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Grape
|
4
|
-
module Util
|
5
|
-
module Registrable
|
6
|
-
def default_elements
|
7
|
-
@default_elements ||= {}
|
8
|
-
end
|
9
|
-
|
10
|
-
def register(format, element)
|
11
|
-
default_elements[format] = element unless default_elements[format]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|