grape 2.0.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 +96 -1
- data/README.md +364 -317
- data/UPGRADING.md +205 -7
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +14 -11
- data/lib/grape/api.rb +19 -10
- data/lib/grape/content_types.rb +13 -10
- data/lib/grape/cookies.rb +2 -1
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/desc.rb +22 -20
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +51 -15
- data/lib/grape/dsl/parameters.rb +5 -4
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +43 -35
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/json.rb +13 -4
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/error_formatter.rb +13 -25
- data/lib/grape/exceptions/base.rb +3 -3
- data/lib/grape/exceptions/validation.rb +0 -2
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +2 -4
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/formatter.rb +15 -25
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +4 -0
- data/lib/grape/middleware/auth/base.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +0 -2
- data/lib/grape/middleware/base.rb +14 -15
- data/lib/grape/middleware/error.rb +61 -54
- data/lib/grape/middleware/formatter.rb +18 -15
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +4 -5
- data/lib/grape/middleware/versioner/accept_version_header.rb +8 -33
- data/lib/grape/middleware/versioner/header.rb +62 -123
- data/lib/grape/middleware/versioner/param.rb +5 -23
- data/lib/grape/middleware/versioner/path.rb +11 -33
- data/lib/grape/middleware/versioner.rb +5 -14
- data/lib/grape/middleware/versioner_helpers.rb +75 -0
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/parser.rb +8 -24
- data/lib/grape/path.rb +24 -29
- data/lib/grape/request.rb +4 -12
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +39 -30
- data/lib/grape/router/route.rb +22 -59
- data/lib/grape/router.rb +32 -37
- data/lib/grape/util/base_inheritable.rb +4 -4
- data/lib/grape/util/cache.rb +0 -3
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_values.rb +0 -2
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/object.rb +45 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +1 -6
- data/lib/grape/util/strict_hash_configuration.rb +3 -3
- data/lib/grape/validations/attributes_doc.rb +38 -36
- data/lib/grape/validations/attributes_iterator.rb +1 -0
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +22 -19
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/build_coercer.rb +69 -71
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +0 -2
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +0 -3
- data/lib/grape/validations/validators/base.rb +1 -0
- data/lib/grape/validations/validators/default_validator.rb +5 -1
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/values_validator.rb +6 -1
- data/lib/grape/validations.rb +3 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +30 -274
- metadata +31 -38
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
- data/lib/grape/util/registrable.rb +0 -15
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/middleware/base'
|
4
|
-
require 'grape/middleware/versioner/parse_media_type_patch'
|
5
|
-
|
6
3
|
module Grape
|
7
4
|
module Middleware
|
8
5
|
module Versioner
|
@@ -25,168 +22,110 @@ module Grape
|
|
25
22
|
# X-Cascade header to alert Grape::Router to attempt the next matched
|
26
23
|
# route.
|
27
24
|
class Header < Base
|
28
|
-
|
29
|
-
/\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
|
30
|
-
|
31
|
-
HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#{Regexp.last_match(0)}\^]+/.freeze
|
32
|
-
HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))+/.freeze
|
25
|
+
include VersionerHelpers
|
33
26
|
|
34
27
|
def before
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
28
|
+
match_best_quality_media_type! do |media_type|
|
29
|
+
env.update(
|
30
|
+
Grape::Env::API_TYPE => media_type.type,
|
31
|
+
Grape::Env::API_SUBTYPE => media_type.subtype,
|
32
|
+
Grape::Env::API_VENDOR => media_type.vendor,
|
33
|
+
Grape::Env::API_VERSION => media_type.version,
|
34
|
+
Grape::Env::API_FORMAT => media_type.format
|
35
|
+
)
|
43
36
|
end
|
44
37
|
end
|
45
38
|
|
46
39
|
private
|
47
40
|
|
48
|
-
def
|
49
|
-
|
50
|
-
strict_version_vendor_accept_header_presence_check
|
51
|
-
end
|
52
|
-
|
53
|
-
def strict_accept_header_presence_check
|
54
|
-
return unless header.qvalues.empty?
|
55
|
-
|
56
|
-
fail_with_invalid_accept_header!('Accept header must be set.')
|
57
|
-
end
|
58
|
-
|
59
|
-
def strict_version_vendor_accept_header_presence_check
|
60
|
-
return if versions.blank? || an_accept_header_with_version_and_vendor_is_present?
|
61
|
-
|
62
|
-
fail_with_invalid_accept_header!('API vendor or version not found.')
|
63
|
-
end
|
41
|
+
def match_best_quality_media_type!
|
42
|
+
return unless vendor
|
64
43
|
|
65
|
-
|
66
|
-
|
67
|
-
|
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)
|
68
50
|
end
|
69
51
|
end
|
70
52
|
|
71
|
-
def
|
72
|
-
|
53
|
+
def allowed_methods
|
54
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS]
|
73
55
|
end
|
74
56
|
|
75
|
-
def
|
76
|
-
|
57
|
+
def accept_header
|
58
|
+
env[Grape::Http::Headers::HTTP_ACCEPT]
|
77
59
|
end
|
78
60
|
|
79
|
-
def
|
80
|
-
|
81
|
-
env[Grape::Env::API_TYPE] = type
|
82
|
-
env[Grape::Env::API_SUBTYPE] = subtype
|
83
|
-
|
84
|
-
return unless VENDOR_VERSION_HEADER_REGEX =~ subtype
|
61
|
+
def strict_header_checks!
|
62
|
+
return unless strict?
|
85
63
|
|
86
|
-
|
87
|
-
|
88
|
-
# weird that Grape::Middleware::Formatter also does this
|
89
|
-
env[Grape::Env::API_FORMAT] = Regexp.last_match[3]
|
64
|
+
accept_header_check!
|
65
|
+
version_and_vendor_check!
|
90
66
|
end
|
91
67
|
|
92
|
-
def
|
93
|
-
|
94
|
-
.new(message, error_headers)
|
95
|
-
end
|
68
|
+
def accept_header_check!
|
69
|
+
return if accept_header.present?
|
96
70
|
|
97
|
-
|
98
|
-
raise Grape::Exceptions::InvalidVersionHeader
|
99
|
-
.new(message, error_headers)
|
71
|
+
invalid_accept_header!('Accept header must be set.')
|
100
72
|
end
|
101
73
|
|
102
|
-
def
|
103
|
-
|
104
|
-
content_types.each_key do |extension|
|
105
|
-
versions.reverse_each do |version|
|
106
|
-
available_media_types << "application/vnd.#{vendor}-#{version}+#{extension}"
|
107
|
-
available_media_types << "application/vnd.#{vendor}-#{version}"
|
108
|
-
end
|
109
|
-
available_media_types << "application/vnd.#{vendor}+#{extension}"
|
110
|
-
end
|
111
|
-
|
112
|
-
available_media_types << "application/vnd.#{vendor}"
|
113
|
-
available_media_types.concat(content_types.values.flatten)
|
114
|
-
end
|
115
|
-
end
|
74
|
+
def version_and_vendor_check!
|
75
|
+
return if versions.blank? || version_and_vendor?
|
116
76
|
|
117
|
-
|
118
|
-
header.values.all? do |header_value|
|
119
|
-
vendor?(header_value) && request_vendor(header_value) != vendor
|
120
|
-
end
|
77
|
+
invalid_accept_header!('API vendor or version not found.')
|
121
78
|
end
|
122
79
|
|
123
|
-
def
|
124
|
-
|
125
|
-
version?(header_value) && versions.exclude?(request_version(header_value))
|
126
|
-
end
|
80
|
+
def q_values_mime_types
|
81
|
+
@q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
|
127
82
|
end
|
128
83
|
|
129
|
-
def
|
130
|
-
|
131
|
-
rescue RuntimeError => e
|
132
|
-
fail_with_invalid_accept_header!(e.message)
|
84
|
+
def version_and_vendor?
|
85
|
+
q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
|
133
86
|
end
|
134
87
|
|
135
|
-
def
|
136
|
-
|
88
|
+
def invalid_accept_header!(message)
|
89
|
+
raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
|
137
90
|
end
|
138
91
|
|
139
|
-
def
|
140
|
-
|
92
|
+
def invalid_version_header!(message)
|
93
|
+
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
141
94
|
end
|
142
95
|
|
143
|
-
def
|
144
|
-
|
145
|
-
end
|
96
|
+
def fail!(grape_allowed_methods)
|
97
|
+
return grape_allowed_methods if grape_allowed_methods.present?
|
146
98
|
|
147
|
-
|
148
|
-
|
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)
|
149
101
|
end
|
150
102
|
|
151
|
-
|
152
|
-
|
153
|
-
# (see Grape::Router for more
|
154
|
-
# information). To prevent # this behavior, and not add the `X-Cascade`
|
155
|
-
# header, one can set the `:cascade` option to `false`.
|
156
|
-
def cascade?
|
157
|
-
if version_options&.key?(:cascade)
|
158
|
-
version_options[:cascade]
|
159
|
-
else
|
160
|
-
true
|
161
|
-
end
|
162
|
-
end
|
103
|
+
def vendor_not_found!(media_types)
|
104
|
+
return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
|
163
105
|
|
164
|
-
|
165
|
-
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
106
|
+
invalid_accept_header!('API vendor not found.')
|
166
107
|
end
|
167
108
|
|
168
|
-
|
169
|
-
|
170
|
-
def vendor?(media_type)
|
171
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
172
|
-
subtype.present? && subtype[HAS_VENDOR_REGEX]
|
173
|
-
end
|
109
|
+
def version_not_found!(media_types)
|
110
|
+
return unless media_types.all? { |media_type| media_type&.version && versions&.exclude?(media_type.version) }
|
174
111
|
|
175
|
-
|
176
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
177
|
-
subtype.match(VENDOR_VERSION_HEADER_REGEX)[1]
|
112
|
+
invalid_version_header!('API version not found.')
|
178
113
|
end
|
179
114
|
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
184
125
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
189
|
-
subtype.present? && subtype[HAS_VERSION_REGEX]
|
126
|
+
available_media_types << base_media_type
|
127
|
+
available_media_types.concat(content_types.values.flatten)
|
128
|
+
end
|
190
129
|
end
|
191
130
|
end
|
192
131
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/middleware/base'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Middleware
|
7
5
|
module Versioner
|
@@ -21,31 +19,15 @@ module Grape
|
|
21
19
|
#
|
22
20
|
# env['api.version'] => 'v1'
|
23
21
|
class Param < Base
|
24
|
-
|
25
|
-
{
|
26
|
-
version_options: {
|
27
|
-
parameter: 'apiver'
|
28
|
-
}
|
29
|
-
}
|
30
|
-
end
|
22
|
+
include VersionerHelpers
|
31
23
|
|
32
24
|
def before
|
33
|
-
potential_version = Rack::Utils.parse_nested_query(env[
|
34
|
-
return if potential_version.
|
25
|
+
potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key]
|
26
|
+
return if potential_version.blank?
|
35
27
|
|
36
|
-
|
28
|
+
version_not_found! unless potential_version_match?(potential_version)
|
37
29
|
env[Grape::Env::API_VERSION] = potential_version
|
38
|
-
env[
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def paramkey
|
44
|
-
version_options[:parameter] || default_options[:version_options][:parameter]
|
45
|
-
end
|
46
|
-
|
47
|
-
def version_options
|
48
|
-
options[:version_options]
|
30
|
+
env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
|
49
31
|
end
|
50
32
|
end
|
51
33
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/middleware/base'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Middleware
|
7
5
|
module Versioner
|
@@ -19,44 +17,24 @@ module Grape
|
|
19
17
|
# env['api.version'] => 'v1'
|
20
18
|
#
|
21
19
|
class Path < Base
|
22
|
-
|
23
|
-
{
|
24
|
-
pattern: /.*/i
|
25
|
-
}
|
26
|
-
end
|
20
|
+
include VersionerHelpers
|
27
21
|
|
28
22
|
def before
|
29
|
-
|
30
|
-
|
23
|
+
path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
|
24
|
+
return if path_info == '/'
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
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)
|
35
28
|
end
|
36
29
|
|
37
|
-
|
38
|
-
|
39
|
-
return unless potential_version&.match?(options[:pattern])
|
40
|
-
|
41
|
-
throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
42
|
-
env[Grape::Env::API_VERSION] = potential_version
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
30
|
+
slash_position = path_info.index('/', 1) # omit the first one
|
31
|
+
return unless slash_position
|
46
32
|
|
47
|
-
|
48
|
-
return
|
33
|
+
potential_version = path_info[1..slash_position - 1]
|
34
|
+
return unless potential_version.match?(pattern)
|
49
35
|
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
def mount_path
|
55
|
-
@mount_path ||= options[:mount_path] && options[:mount_path] != '/' ? options[:mount_path] : ''
|
56
|
-
end
|
57
|
-
|
58
|
-
def prefix
|
59
|
-
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
|
60
38
|
end
|
61
39
|
end
|
62
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/namespace.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/util/cache'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
# A container for endpoints or other namespaces, which allows for both
|
7
5
|
# logical grouping of endpoints as well as sharing common configuration.
|
@@ -33,13 +31,14 @@ module Grape
|
|
33
31
|
# Join the namespaces from a list of settings to create a path prefix.
|
34
32
|
# @param settings [Array] list of Grape::Util::InheritableSettings.
|
35
33
|
def self.joined_space_path(settings)
|
36
|
-
|
34
|
+
JoinedSpaceCache[joined_space(settings)]
|
37
35
|
end
|
38
36
|
|
39
37
|
class JoinedSpaceCache < Grape::Util::Cache
|
40
38
|
def initialize
|
39
|
+
super
|
41
40
|
@cache = Hash.new do |h, joined_space|
|
42
|
-
h[joined_space] =
|
41
|
+
h[joined_space] = Grape::Router.normalize_path(joined_space.join('/'))
|
43
42
|
end
|
44
43
|
end
|
45
44
|
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/path.rb
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/util/cache'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
# Represents a path to an endpoint.
|
7
5
|
class Path
|
8
|
-
def self.prepare(raw_path, namespace, settings)
|
9
|
-
Path.new(raw_path, namespace, settings)
|
10
|
-
end
|
11
|
-
|
12
6
|
attr_reader :raw_path, :namespace, :settings
|
13
7
|
|
14
8
|
def initialize(raw_path, namespace, settings)
|
@@ -22,31 +16,27 @@ module Grape
|
|
22
16
|
end
|
23
17
|
|
24
18
|
def root_prefix
|
25
|
-
|
19
|
+
settings[:root_prefix]
|
26
20
|
end
|
27
21
|
|
28
22
|
def uses_specific_format?
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
false
|
33
|
-
end
|
23
|
+
return false unless settings.key?(:format) && settings.key?(:content_types)
|
24
|
+
|
25
|
+
settings[:format] && Array(settings[:content_types]).size == 1
|
34
26
|
end
|
35
27
|
|
36
28
|
def uses_path_versioning?
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
false
|
41
|
-
end
|
29
|
+
return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)
|
30
|
+
|
31
|
+
settings[:version] && settings[:version_options][:using] == :path
|
42
32
|
end
|
43
33
|
|
44
34
|
def namespace?
|
45
|
-
namespace&.match?(/^\S/) && namespace
|
35
|
+
namespace&.match?(/^\S/) && not_slash?(namespace)
|
46
36
|
end
|
47
37
|
|
48
38
|
def path?
|
49
|
-
raw_path&.match?(/^\S/) && raw_path
|
39
|
+
raw_path&.match?(/^\S/) && not_slash?(raw_path)
|
50
40
|
end
|
51
41
|
|
52
42
|
def suffix
|
@@ -60,7 +50,7 @@ module Grape
|
|
60
50
|
end
|
61
51
|
|
62
52
|
def path
|
63
|
-
|
53
|
+
PartsCache[parts]
|
64
54
|
end
|
65
55
|
|
66
56
|
def path_with_suffix
|
@@ -75,24 +65,29 @@ module Grape
|
|
75
65
|
|
76
66
|
class PartsCache < Grape::Util::Cache
|
77
67
|
def initialize
|
68
|
+
super
|
78
69
|
@cache = Hash.new do |h, parts|
|
79
|
-
h[parts] =
|
70
|
+
h[parts] = Grape::Router.normalize_path(parts.join('/'))
|
80
71
|
end
|
81
72
|
end
|
82
73
|
end
|
83
74
|
|
84
75
|
def parts
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
76
|
+
[].tap do |parts|
|
77
|
+
add_part(parts, mount_path)
|
78
|
+
add_part(parts, root_prefix)
|
79
|
+
parts << ':version' if uses_path_versioning?
|
80
|
+
add_part(parts, namespace)
|
81
|
+
add_part(parts, raw_path)
|
82
|
+
end
|
90
83
|
end
|
91
84
|
|
92
|
-
def
|
93
|
-
|
85
|
+
def add_part(parts, value)
|
86
|
+
parts << value if value && not_slash?(value)
|
87
|
+
end
|
94
88
|
|
95
|
-
|
89
|
+
def not_slash?(value)
|
90
|
+
value != '/'
|
96
91
|
end
|
97
92
|
end
|
98
93
|
end
|
data/lib/grape/request.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/util/lazy_object'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
class Request < Rack::Request
|
7
5
|
HTTP_PREFIX = 'HTTP_'
|
@@ -36,8 +34,8 @@ module Grape
|
|
36
34
|
end
|
37
35
|
|
38
36
|
def build_headers
|
39
|
-
Grape::Util::
|
40
|
-
env.each_pair.with_object(
|
37
|
+
Grape::Util::Lazy::Object.new do
|
38
|
+
env.each_pair.with_object(Grape::Util::Header.new) do |(k, v), headers|
|
41
39
|
next unless k.to_s.start_with? HTTP_PREFIX
|
42
40
|
|
43
41
|
transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
|
@@ -46,14 +44,8 @@ module Grape
|
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
49
|
-
|
50
|
-
|
51
|
-
-header[5..].tr('_', '-').downcase
|
52
|
-
end
|
53
|
-
else
|
54
|
-
def transform_header(header)
|
55
|
-
-header[5..].split('_').map(&:capitalize).join('-')
|
56
|
-
end
|
47
|
+
def transform_header(header)
|
48
|
+
-header[5..].tr('_', '-').downcase
|
57
49
|
end
|
58
50
|
end
|
59
51
|
end
|