grape 2.0.0 → 2.4.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 +151 -1
- data/CONTRIBUTING.md +1 -1
- data/README.md +404 -334
- data/UPGRADING.md +279 -7
- data/grape.gemspec +8 -8
- data/lib/grape/api/instance.rb +34 -66
- data/lib/grape/api.rb +47 -70
- data/lib/grape/content_types.rb +13 -10
- data/lib/grape/cookies.rb +31 -24
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/api.rb +0 -2
- data/lib/grape/dsl/desc.rb +49 -44
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +8 -4
- data/lib/grape/dsl/inside_route.rb +67 -54
- data/lib/grape/dsl/parameters.rb +10 -9
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +34 -17
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +120 -118
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/base.rb +51 -21
- data/lib/grape/error_formatter/json.rb +7 -15
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +11 -17
- 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/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/validation.rb +5 -6
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +4 -6
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
- data/lib/grape/extensions/hash.rb +7 -2
- data/lib/grape/extensions/hashie/mash.rb +3 -5
- 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/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +46 -42
- data/lib/grape/middleware/auth/base.rb +11 -34
- data/lib/grape/middleware/auth/dsl.rb +23 -31
- data/lib/grape/middleware/base.rb +41 -23
- data/lib/grape/middleware/error.rb +77 -76
- data/lib/grape/middleware/formatter.rb +48 -79
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +26 -37
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +74 -0
- data/lib/grape/middleware/versioner/header.rb +59 -126
- data/lib/grape/middleware/versioner/param.rb +4 -25
- data/lib/grape/middleware/versioner/path.rb +10 -34
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/namespace.rb +4 -5
- data/lib/grape/params_builder/base.rb +18 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +32 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +6 -8
- data/lib/grape/parser/xml.rb +6 -8
- data/lib/grape/parser.rb +5 -23
- data/lib/grape/path.rb +38 -60
- data/lib/grape/request.rb +161 -30
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +45 -31
- data/lib/grape/router/route.rb +28 -57
- data/lib/grape/router.rb +56 -43
- 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/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/registry.rb +27 -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 +34 -0
- data/lib/grape/validations/params_scope.rb +36 -32
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +9 -15
- 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/validator_factory.rb +2 -2
- data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +8 -11
- 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 +6 -2
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/except_values_validator.rb +2 -2
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/presence_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +2 -2
- data/lib/grape/validations/validators/values_validator.rb +20 -57
- data/lib/grape/validations.rb +8 -21
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +42 -274
- metadata +45 -44
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/http/headers.rb +0 -71
- data/lib/grape/middleware/helpers.rb +0 -12
- 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
- data/lib/grape/validations/types/build_coercer.rb +0 -94
@@ -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
|
@@ -20,44 +18,19 @@ module Grape
|
|
20
18
|
# route.
|
21
19
|
class AcceptVersionHeader < Base
|
22
20
|
def before
|
23
|
-
potential_version =
|
24
|
-
|
25
|
-
if strict? && potential_version.empty?
|
26
|
-
# If no Accept-Version header:
|
27
|
-
throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
|
28
|
-
end
|
29
|
-
|
30
|
-
return if potential_version.empty?
|
21
|
+
potential_version = env['HTTP_ACCEPT_VERSION'].try(:scrub)
|
22
|
+
not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
|
31
23
|
|
32
|
-
|
33
|
-
throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.' unless versions.any? { |v| v.to_s == potential_version }
|
24
|
+
return if potential_version.blank?
|
34
25
|
|
26
|
+
not_acceptable!('The requested version is not supported.') unless potential_version_match?(potential_version)
|
35
27
|
env[Grape::Env::API_VERSION] = potential_version
|
36
28
|
end
|
37
29
|
|
38
30
|
private
|
39
31
|
|
40
|
-
def
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
def strict?
|
45
|
-
options[:version_options] && options[:version_options][:strict]
|
46
|
-
end
|
47
|
-
|
48
|
-
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
49
|
-
# of routes (see Grape::Router) for more information). To prevent
|
50
|
-
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
51
|
-
def cascade?
|
52
|
-
if options[:version_options]&.key?(:cascade)
|
53
|
-
options[:version_options][:cascade]
|
54
|
-
else
|
55
|
-
true
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def error_headers
|
60
|
-
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
32
|
+
def not_acceptable!(message)
|
33
|
+
throw :error, status: 406, headers: error_headers, message: message
|
61
34
|
end
|
62
35
|
end
|
63
36
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
class Base < Grape::Middleware::Base
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
pattern: /.*/i.freeze,
|
9
|
+
version_options: {
|
10
|
+
strict: false,
|
11
|
+
cascade: true,
|
12
|
+
parameter: 'apiver'
|
13
|
+
}.freeze
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def self.inherited(klass)
|
17
|
+
super
|
18
|
+
Versioner.register(klass)
|
19
|
+
end
|
20
|
+
|
21
|
+
def versions
|
22
|
+
options[:versions]
|
23
|
+
end
|
24
|
+
|
25
|
+
def prefix
|
26
|
+
options[:prefix]
|
27
|
+
end
|
28
|
+
|
29
|
+
def mount_path
|
30
|
+
options[:mount_path]
|
31
|
+
end
|
32
|
+
|
33
|
+
def pattern
|
34
|
+
options[:pattern]
|
35
|
+
end
|
36
|
+
|
37
|
+
def version_options
|
38
|
+
options[:version_options]
|
39
|
+
end
|
40
|
+
|
41
|
+
def strict?
|
42
|
+
version_options[:strict]
|
43
|
+
end
|
44
|
+
|
45
|
+
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
46
|
+
# of routes (see Grape::Router) for more information). To prevent
|
47
|
+
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
48
|
+
def cascade?
|
49
|
+
version_options[:cascade]
|
50
|
+
end
|
51
|
+
|
52
|
+
def parameter_key
|
53
|
+
version_options[:parameter]
|
54
|
+
end
|
55
|
+
|
56
|
+
def vendor
|
57
|
+
version_options[:vendor]
|
58
|
+
end
|
59
|
+
|
60
|
+
def error_headers
|
61
|
+
cascade? ? { 'X-Cascade' => 'pass' } : {}
|
62
|
+
end
|
63
|
+
|
64
|
+
def potential_version_match?(potential_version)
|
65
|
+
versions.blank? || versions.any? { |v| v.to_s == potential_version }
|
66
|
+
end
|
67
|
+
|
68
|
+
def version_not_found!
|
69
|
+
throw :error, status: 404, message: '404 API Version Not Found', headers: { 'X-Cascade' => 'pass' }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -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,104 @@ 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
|
-
VENDOR_VERSION_HEADER_REGEX =
|
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
|
33
|
-
|
34
25
|
def before
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
26
|
+
match_best_quality_media_type! do |media_type|
|
27
|
+
env.update(
|
28
|
+
Grape::Env::API_TYPE => media_type.type,
|
29
|
+
Grape::Env::API_SUBTYPE => media_type.subtype,
|
30
|
+
Grape::Env::API_VENDOR => media_type.vendor,
|
31
|
+
Grape::Env::API_VERSION => media_type.version,
|
32
|
+
Grape::Env::API_FORMAT => media_type.format
|
33
|
+
)
|
43
34
|
end
|
44
35
|
end
|
45
36
|
|
46
37
|
private
|
47
38
|
|
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
|
39
|
+
def match_best_quality_media_type!
|
40
|
+
return unless vendor
|
64
41
|
|
65
|
-
|
66
|
-
|
67
|
-
|
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!
|
68
48
|
end
|
69
49
|
end
|
70
50
|
|
71
|
-
def
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
def media_type
|
76
|
-
@media_type ||= header.best_of(available_media_types)
|
51
|
+
def accept_header
|
52
|
+
env['HTTP_ACCEPT']
|
77
53
|
end
|
78
54
|
|
79
|
-
def
|
80
|
-
|
81
|
-
env[Grape::Env::API_TYPE] = type
|
82
|
-
env[Grape::Env::API_SUBTYPE] = subtype
|
55
|
+
def strict_header_checks!
|
56
|
+
return unless strict?
|
83
57
|
|
84
|
-
|
85
|
-
|
86
|
-
env[Grape::Env::API_VENDOR] = Regexp.last_match[1]
|
87
|
-
env[Grape::Env::API_VERSION] = Regexp.last_match[2]
|
88
|
-
# weird that Grape::Middleware::Formatter also does this
|
89
|
-
env[Grape::Env::API_FORMAT] = Regexp.last_match[3]
|
58
|
+
accept_header_check!
|
59
|
+
version_and_vendor_check!
|
90
60
|
end
|
91
61
|
|
92
|
-
def
|
93
|
-
|
94
|
-
.new(message, error_headers)
|
95
|
-
end
|
62
|
+
def accept_header_check!
|
63
|
+
return if accept_header.present?
|
96
64
|
|
97
|
-
|
98
|
-
raise Grape::Exceptions::InvalidVersionHeader
|
99
|
-
.new(message, error_headers)
|
65
|
+
invalid_accept_header!('Accept header must be set.')
|
100
66
|
end
|
101
67
|
|
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
|
68
|
+
def version_and_vendor_check!
|
69
|
+
return if versions.blank? || version_and_vendor?
|
111
70
|
|
112
|
-
|
113
|
-
available_media_types.concat(content_types.values.flatten)
|
114
|
-
end
|
71
|
+
invalid_accept_header!('API vendor or version not found.')
|
115
72
|
end
|
116
73
|
|
117
|
-
def
|
118
|
-
|
119
|
-
vendor?(header_value) && request_vendor(header_value) != vendor
|
120
|
-
end
|
74
|
+
def q_values_mime_types
|
75
|
+
@q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
|
121
76
|
end
|
122
77
|
|
123
|
-
def
|
124
|
-
|
125
|
-
version?(header_value) && versions.exclude?(request_version(header_value))
|
126
|
-
end
|
78
|
+
def version_and_vendor?
|
79
|
+
q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
|
127
80
|
end
|
128
81
|
|
129
|
-
def
|
130
|
-
|
131
|
-
rescue RuntimeError => e
|
132
|
-
fail_with_invalid_accept_header!(e.message)
|
82
|
+
def invalid_accept_header!(message)
|
83
|
+
raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
|
133
84
|
end
|
134
85
|
|
135
|
-
def
|
136
|
-
|
86
|
+
def invalid_version_header!(message)
|
87
|
+
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
137
88
|
end
|
138
89
|
|
139
|
-
def
|
140
|
-
|
141
|
-
end
|
90
|
+
def fail!
|
91
|
+
return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?
|
142
92
|
|
143
|
-
|
144
|
-
|
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)
|
145
95
|
end
|
146
96
|
|
147
|
-
def
|
148
|
-
|
149
|
-
end
|
97
|
+
def vendor_not_found!(media_types)
|
98
|
+
return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
|
150
99
|
|
151
|
-
|
152
|
-
# which allows nesting and stacking of routes
|
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
|
100
|
+
invalid_accept_header!('API vendor not found.')
|
162
101
|
end
|
163
102
|
|
164
|
-
def
|
165
|
-
|
166
|
-
end
|
103
|
+
def version_not_found!(media_types)
|
104
|
+
return unless media_types.all? { |media_type| media_type&.version && versions&.exclude?(media_type.version) }
|
167
105
|
|
168
|
-
|
169
|
-
# @return [Boolean] whether the content type sets a vendor
|
170
|
-
def vendor?(media_type)
|
171
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
172
|
-
subtype.present? && subtype[HAS_VENDOR_REGEX]
|
106
|
+
invalid_version_header!('API version not found.')
|
173
107
|
end
|
174
108
|
|
175
|
-
def
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
184
119
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
189
|
-
subtype.present? && subtype[HAS_VERSION_REGEX]
|
120
|
+
available_media_types << base_media_type
|
121
|
+
available_media_types.concat(content_types.values.flatten)
|
122
|
+
end
|
190
123
|
end
|
191
124
|
end
|
192
125
|
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,12 @@ module Grape
|
|
21
19
|
#
|
22
20
|
# env['api.version'] => 'v1'
|
23
21
|
class Param < Base
|
24
|
-
def default_options
|
25
|
-
{
|
26
|
-
version_options: {
|
27
|
-
parameter: 'apiver'
|
28
|
-
}
|
29
|
-
}
|
30
|
-
end
|
31
|
-
|
32
22
|
def before
|
33
|
-
potential_version =
|
34
|
-
return if potential_version.
|
35
|
-
|
36
|
-
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
37
|
-
env[Grape::Env::API_VERSION] = potential_version
|
38
|
-
env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def paramkey
|
44
|
-
version_options[:parameter] || default_options[:version_options][:parameter]
|
45
|
-
end
|
23
|
+
potential_version = query_params[parameter_key]
|
24
|
+
return if potential_version.blank?
|
46
25
|
|
47
|
-
|
48
|
-
|
26
|
+
version_not_found! unless potential_version_match?(potential_version)
|
27
|
+
env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key)
|
49
28
|
end
|
50
29
|
end
|
51
30
|
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,22 @@ module Grape
|
|
19
17
|
# env['api.version'] => 'v1'
|
20
18
|
#
|
21
19
|
class Path < Base
|
22
|
-
def default_options
|
23
|
-
{
|
24
|
-
pattern: /.*/i
|
25
|
-
}
|
26
|
-
end
|
27
|
-
|
28
20
|
def before
|
29
|
-
|
30
|
-
|
21
|
+
path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
|
22
|
+
return if path_info == '/'
|
31
23
|
|
32
|
-
|
33
|
-
|
34
|
-
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)
|
35
26
|
end
|
36
27
|
|
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
|
46
|
-
|
47
|
-
def mounted_path?(path)
|
48
|
-
return false unless mount_path && path.start_with?(mount_path)
|
49
|
-
|
50
|
-
rest = path.slice(mount_path.length..-1)
|
51
|
-
rest.start_with?('/') || rest.empty?
|
52
|
-
end
|
28
|
+
slash_position = path_info.index('/', 1) # omit the first one
|
29
|
+
return unless slash_position
|
53
30
|
|
54
|
-
|
55
|
-
|
56
|
-
end
|
31
|
+
potential_version = path_info[1..slash_position - 1]
|
32
|
+
return unless potential_version.match?(pattern)
|
57
33
|
|
58
|
-
|
59
|
-
Grape::
|
34
|
+
version_not_found! unless potential_version_match?(potential_version)
|
35
|
+
env[Grape::Env::API_VERSION] = potential_version
|
60
36
|
end
|
61
37
|
end
|
62
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
@@ -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.
|
@@ -14,7 +12,7 @@ module Grape
|
|
14
12
|
# @option options :requirements [Hash] param-regex pairs, all of which must
|
15
13
|
# be met by a request's params for all endpoints in this namespace, or
|
16
14
|
# validation will fail and return a 422.
|
17
|
-
def initialize(space,
|
15
|
+
def initialize(space, options)
|
18
16
|
@space = space.to_s
|
19
17
|
@options = options
|
20
18
|
end
|
@@ -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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module ParamsBuilder
|
5
|
+
class Base
|
6
|
+
class << self
|
7
|
+
def call(_params)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def inherited(klass)
|
12
|
+
super
|
13
|
+
ParamsBuilder.register(klass)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module ParamsBuilder
|
5
|
+
extend Grape::Util::Registry
|
6
|
+
|
7
|
+
SHORT_NAME_LOOKUP = {
|
8
|
+
'Grape::Extensions::Hash::ParamBuilder' => :hash,
|
9
|
+
'Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder' => :hash_with_indifferent_access,
|
10
|
+
'Grape::Extensions::Hashie::Mash::ParamBuilder' => :hashie_mash
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
module_function
|
14
|
+
|
15
|
+
def params_builder_for(short_name)
|
16
|
+
verified_short_name = verify_short_name!(short_name)
|
17
|
+
|
18
|
+
raise Grape::Exceptions::UnknownParamsBuilder, verified_short_name unless registry.key?(verified_short_name)
|
19
|
+
|
20
|
+
registry[verified_short_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify_short_name!(short_name)
|
24
|
+
return short_name if short_name.is_a?(Symbol)
|
25
|
+
|
26
|
+
class_name = short_name.name
|
27
|
+
SHORT_NAME_LOOKUP[class_name].tap do |real_short_name|
|
28
|
+
Grape.deprecator.warn "#{class_name} has been deprecated. Use short name :#{real_short_name} instead."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|