grape 2.1.0 → 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 +46 -0
- data/README.md +4 -3
- data/UPGRADING.md +8 -0
- data/grape.gemspec +2 -1
- data/lib/grape/api/instance.rb +1 -1
- data/lib/grape/api.rb +2 -2
- data/lib/grape/content_types.rb +13 -8
- data/lib/grape/dsl/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +14 -3
- data/lib/grape/dsl/parameters.rb +1 -1
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/endpoint.rb +34 -23
- data/lib/grape/error_formatter/json.rb +13 -4
- data/lib/grape/error_formatter.rb +13 -25
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- 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 +13 -11
- data/lib/grape/middleware/formatter.rb +2 -2
- data/lib/grape/middleware/stack.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/router.rb +2 -1
- data/lib/grape/validations/attributes_iterator.rb +1 -0
- data/lib/grape/validations/params_scope.rb +12 -10
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +10 -3
- data/lib/grape/version.rb +1 -1
- metadata +8 -9
- data/lib/grape/util/accept/header.rb +0 -19
- data/lib/grape/util/accept_header_handler.rb +0 -105
- data/lib/grape/util/registrable.rb +0 -15
@@ -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,
|
@@ -74,12 +74,12 @@ module Grape
|
|
74
74
|
rack_response(status, headers, format_message(message, backtrace, original_exception))
|
75
75
|
end
|
76
76
|
|
77
|
-
def default_rescue_handler(
|
78
|
-
error_response(message:
|
77
|
+
def default_rescue_handler(exception)
|
78
|
+
error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)
|
79
79
|
end
|
80
80
|
|
81
81
|
def rescue_handler_for_base_only_class(klass)
|
82
|
-
error, handler = options[:base_only_rescue_handlers]
|
82
|
+
error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
|
83
83
|
|
84
84
|
return unless error
|
85
85
|
|
@@ -87,7 +87,7 @@ module Grape
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def rescue_handler_for_class_or_its_ancestor(klass)
|
90
|
-
error, handler = options[:rescue_handlers]
|
90
|
+
error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }
|
91
91
|
|
92
92
|
return unless error
|
93
93
|
|
@@ -120,12 +120,12 @@ module Grape
|
|
120
120
|
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
|
121
121
|
end
|
122
122
|
|
123
|
-
|
124
|
-
|
125
|
-
|
123
|
+
if error?(response)
|
124
|
+
error_response(response)
|
125
|
+
elsif response.is_a?(Rack::Response)
|
126
126
|
response
|
127
127
|
else
|
128
|
-
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
|
128
|
+
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
@@ -137,7 +137,9 @@ module Grape
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def error?(response)
|
140
|
-
response.is_a?(Hash)
|
140
|
+
return false unless response.is_a?(Hash)
|
141
|
+
|
142
|
+
response.key?(:message) && response.key?(:status) && response.key?(:headers)
|
141
143
|
end
|
142
144
|
end
|
143
145
|
end
|
@@ -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
|
data/lib/grape/router.rb
CHANGED
@@ -138,7 +138,8 @@ module Grape
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def default_response
|
141
|
-
|
141
|
+
headers = Grape::Util::Header.new.merge(Grape::Http::Headers::X_CASCADE => 'pass')
|
142
|
+
[404, headers, ['404 Not Found']]
|
142
143
|
end
|
143
144
|
|
144
145
|
def match?(input, method)
|
@@ -21,6 +21,7 @@ module Grape
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def do_each(params_to_process, parent_indicies = [], &block)
|
24
|
+
@scope.reset_index # gets updated depending on the size of params_to_process
|
24
25
|
params_to_process.each_with_index do |resource_params, index|
|
25
26
|
# when we get arrays of arrays it means that target element located inside array
|
26
27
|
# we need this because we want to know parent arrays indicies
|
@@ -93,9 +93,7 @@ module Grape
|
|
93
93
|
|
94
94
|
def meets_dependency?(params, request_params)
|
95
95
|
return true unless @dependent_on
|
96
|
-
|
97
96
|
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
98
|
-
|
99
97
|
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
|
100
98
|
|
101
99
|
meets_hash_dependency?(params)
|
@@ -103,7 +101,6 @@ module Grape
|
|
103
101
|
|
104
102
|
def attr_meets_dependency?(params)
|
105
103
|
return true unless @dependent_on
|
106
|
-
|
107
104
|
return false if @parent.present? && !@parent.attr_meets_dependency?(params)
|
108
105
|
|
109
106
|
meets_hash_dependency?(params)
|
@@ -169,6 +166,10 @@ module Grape
|
|
169
166
|
!@optional
|
170
167
|
end
|
171
168
|
|
169
|
+
def reset_index
|
170
|
+
@index = nil
|
171
|
+
end
|
172
|
+
|
172
173
|
protected
|
173
174
|
|
174
175
|
# Adds a parameter declaration to our list of validations.
|
@@ -189,7 +190,13 @@ module Grape
|
|
189
190
|
#
|
190
191
|
# @return [Array<Symbol>] the nesting/path of the current parameter scope
|
191
192
|
def full_path
|
192
|
-
nested?
|
193
|
+
if nested?
|
194
|
+
(@parent.full_path + [@element])
|
195
|
+
elsif lateral?
|
196
|
+
@parent.full_path
|
197
|
+
else
|
198
|
+
[]
|
199
|
+
end
|
193
200
|
end
|
194
201
|
|
195
202
|
private
|
@@ -297,12 +304,7 @@ module Grape
|
|
297
304
|
# `optional` invocation that opened this scope.
|
298
305
|
# @yield parameter scope
|
299
306
|
def new_group_scope(attrs, &block)
|
300
|
-
self.class.new(
|
301
|
-
api: @api,
|
302
|
-
parent: self,
|
303
|
-
group: attrs.first,
|
304
|
-
&block
|
305
|
-
)
|
307
|
+
self.class.new(api: @api, parent: self, group: attrs.first, &block)
|
306
308
|
end
|
307
309
|
|
308
310
|
# Pushes declared params to parent or settings
|
@@ -7,7 +7,7 @@ module Grape
|
|
7
7
|
def validate_params!(params)
|
8
8
|
keys = keys_in_common(params)
|
9
9
|
return if keys.length == 1
|
10
|
-
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.
|
10
|
+
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.empty?
|
11
11
|
|
12
12
|
raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
|
13
13
|
end
|