grape 2.2.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 +55 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +41 -18
- data/UPGRADING.md +75 -1
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +25 -60
- data/lib/grape/api.rb +44 -76
- data/lib/grape/cookies.rb +31 -25
- data/lib/grape/dsl/api.rb +0 -2
- data/lib/grape/dsl/desc.rb +27 -24
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/helpers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +17 -40
- data/lib/grape/dsl/parameters.rb +5 -5
- data/lib/grape/dsl/routing.rb +14 -13
- data/lib/grape/endpoint.rb +100 -106
- data/lib/grape/error_formatter/base.rb +51 -21
- data/lib/grape/error_formatter/json.rb +7 -24
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +13 -20
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +4 -12
- 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 -4
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
- data/lib/grape/extensions/hash.rb +2 -1
- 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 +4 -12
- data/lib/grape/locale/en.yml +44 -44
- data/lib/grape/middleware/auth/base.rb +11 -32
- data/lib/grape/middleware/auth/dsl.rb +23 -29
- data/lib/grape/middleware/base.rb +30 -11
- data/lib/grape/middleware/error.rb +18 -24
- data/lib/grape/middleware/formatter.rb +39 -73
- data/lib/grape/middleware/stack.rb +26 -36
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -3
- data/lib/grape/middleware/versioner/base.rb +74 -0
- data/lib/grape/middleware/versioner/header.rb +4 -10
- data/lib/grape/middleware/versioner/param.rb +2 -5
- data/lib/grape/middleware/versioner/path.rb +0 -2
- data/lib/grape/middleware/versioner.rb +5 -3
- data/lib/grape/namespace.rb +1 -1
- 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 -7
- data/lib/grape/path.rb +39 -56
- data/lib/grape/request.rb +162 -23
- data/lib/grape/router/base_route.rb +2 -2
- data/lib/grape/router/greedy_route.rb +2 -2
- data/lib/grape/router/pattern.rb +23 -18
- data/lib/grape/router/route.rb +14 -6
- data/lib/grape/router.rb +30 -12
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/validations/contract_scope.rb +2 -39
- data/lib/grape/validations/params_scope.rb +15 -14
- data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
- data/lib/grape/validations/validator_factory.rb +2 -2
- data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +7 -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 +1 -1
- data/lib/grape/validations/validators/except_values_validator.rb +2 -2
- data/lib/grape/validations/validators/length_validator.rb +1 -1
- 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 +15 -57
- data/lib/grape/validations.rb +8 -17
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +14 -2
- metadata +24 -16
- data/lib/grape/http/headers.rb +0 -55
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/middleware/versioner_helpers.rb +0 -75
- data/lib/grape/util/lazy/object.rb +0 -45
- data/lib/grape/validations/types/build_coercer.rb +0 -92
data/lib/grape/path.rb
CHANGED
@@ -3,65 +3,66 @@
|
|
3
3
|
module Grape
|
4
4
|
# Represents a path to an endpoint.
|
5
5
|
class Path
|
6
|
-
|
6
|
+
DEFAULT_FORMAT_SEGMENT = '(/.:format)'
|
7
|
+
NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT = '(.:format)'
|
8
|
+
VERSION_SEGMENT = ':version'
|
7
9
|
|
8
|
-
|
9
|
-
@raw_path = raw_path
|
10
|
-
@namespace = namespace
|
11
|
-
@settings = settings
|
12
|
-
end
|
10
|
+
attr_reader :origin, :suffix
|
13
11
|
|
14
|
-
def
|
15
|
-
settings
|
12
|
+
def initialize(raw_path, raw_namespace, settings)
|
13
|
+
@origin = PartsCache[build_parts(raw_path, raw_namespace, settings)]
|
14
|
+
@suffix = build_suffix(raw_path, raw_namespace, settings)
|
16
15
|
end
|
17
16
|
|
18
|
-
def
|
19
|
-
|
17
|
+
def to_s
|
18
|
+
"#{origin}#{suffix}"
|
20
19
|
end
|
21
20
|
|
22
|
-
|
23
|
-
return false unless settings.key?(:format) && settings.key?(:content_types)
|
21
|
+
private
|
24
22
|
|
25
|
-
|
23
|
+
def build_suffix(raw_path, raw_namespace, settings)
|
24
|
+
if uses_specific_format?(settings)
|
25
|
+
"(.#{settings[:format]})"
|
26
|
+
elsif !uses_path_versioning?(settings) || (valid_part?(raw_namespace) || valid_part?(raw_path))
|
27
|
+
NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT
|
28
|
+
else
|
29
|
+
DEFAULT_FORMAT_SEGMENT
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
def build_parts(raw_path, raw_namespace, settings)
|
34
|
+
[].tap do |parts|
|
35
|
+
add_part(parts, settings[:mount_path])
|
36
|
+
add_part(parts, settings[:root_prefix])
|
37
|
+
parts << VERSION_SEGMENT if uses_path_versioning?(settings)
|
38
|
+
add_part(parts, raw_namespace)
|
39
|
+
add_part(parts, raw_path)
|
40
|
+
end
|
32
41
|
end
|
33
42
|
|
34
|
-
def
|
35
|
-
|
43
|
+
def add_part(parts, value)
|
44
|
+
parts << value if value && not_slash?(value)
|
36
45
|
end
|
37
46
|
|
38
|
-
def
|
39
|
-
|
47
|
+
def not_slash?(value)
|
48
|
+
value != '/'
|
40
49
|
end
|
41
50
|
|
42
|
-
def
|
43
|
-
|
44
|
-
"(.#{settings[:format]})"
|
45
|
-
elsif !uses_path_versioning? || (namespace? || path?)
|
46
|
-
'(.:format)'
|
47
|
-
else
|
48
|
-
'(/.:format)'
|
49
|
-
end
|
50
|
-
end
|
51
|
+
def uses_specific_format?(settings)
|
52
|
+
return false unless settings.key?(:format) && settings.key?(:content_types)
|
51
53
|
|
52
|
-
|
53
|
-
PartsCache[parts]
|
54
|
+
settings[:format] && Array(settings[:content_types]).size == 1
|
54
55
|
end
|
55
56
|
|
56
|
-
def
|
57
|
-
|
58
|
-
end
|
57
|
+
def uses_path_versioning?(settings)
|
58
|
+
return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)
|
59
59
|
|
60
|
-
|
61
|
-
path_with_suffix
|
60
|
+
settings[:version] && settings[:version_options][:using] == :path
|
62
61
|
end
|
63
62
|
|
64
|
-
|
63
|
+
def valid_part?(part)
|
64
|
+
part&.match?(/^\S/) && not_slash?(part)
|
65
|
+
end
|
65
66
|
|
66
67
|
class PartsCache < Grape::Util::Cache
|
67
68
|
def initialize
|
@@ -71,23 +72,5 @@ module Grape
|
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
74
|
-
|
75
|
-
def parts
|
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
|
83
|
-
end
|
84
|
-
|
85
|
-
def add_part(parts, value)
|
86
|
-
parts << value if value && not_slash?(value)
|
87
|
-
end
|
88
|
-
|
89
|
-
def not_slash?(value)
|
90
|
-
value != '/'
|
91
|
-
end
|
92
75
|
end
|
93
76
|
end
|
data/lib/grape/request.rb
CHANGED
@@ -2,50 +2,189 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
class Request < Rack::Request
|
5
|
-
|
5
|
+
# Based on rack 3 KNOWN_HEADERS
|
6
|
+
# https://github.com/rack/rack/blob/4f15e7b814922af79605be4b02c5b7c3044ba206/lib/rack/headers.rb#L10
|
7
|
+
|
8
|
+
KNOWN_HEADERS = %w[
|
9
|
+
Accept
|
10
|
+
Accept-CH
|
11
|
+
Accept-Encoding
|
12
|
+
Accept-Language
|
13
|
+
Accept-Patch
|
14
|
+
Accept-Ranges
|
15
|
+
Accept-Version
|
16
|
+
Access-Control-Allow-Credentials
|
17
|
+
Access-Control-Allow-Headers
|
18
|
+
Access-Control-Allow-Methods
|
19
|
+
Access-Control-Allow-Origin
|
20
|
+
Access-Control-Expose-Headers
|
21
|
+
Access-Control-Max-Age
|
22
|
+
Age
|
23
|
+
Allow
|
24
|
+
Alt-Svc
|
25
|
+
Authorization
|
26
|
+
Cache-Control
|
27
|
+
Client-Ip
|
28
|
+
Connection
|
29
|
+
Content-Disposition
|
30
|
+
Content-Encoding
|
31
|
+
Content-Language
|
32
|
+
Content-Length
|
33
|
+
Content-Location
|
34
|
+
Content-MD5
|
35
|
+
Content-Range
|
36
|
+
Content-Security-Policy
|
37
|
+
Content-Security-Policy-Report-Only
|
38
|
+
Content-Type
|
39
|
+
Cookie
|
40
|
+
Date
|
41
|
+
Delta-Base
|
42
|
+
Dnt
|
43
|
+
ETag
|
44
|
+
Expect-CT
|
45
|
+
Expires
|
46
|
+
Feature-Policy
|
47
|
+
Forwarded
|
48
|
+
Host
|
49
|
+
If-Modified-Since
|
50
|
+
If-None-Match
|
51
|
+
IM
|
52
|
+
Last-Modified
|
53
|
+
Link
|
54
|
+
Location
|
55
|
+
NEL
|
56
|
+
P3P
|
57
|
+
Permissions-Policy
|
58
|
+
Pragma
|
59
|
+
Preference-Applied
|
60
|
+
Proxy-Authenticate
|
61
|
+
Public-Key-Pins
|
62
|
+
Range
|
63
|
+
Referer
|
64
|
+
Referrer-Policy
|
65
|
+
Refresh
|
66
|
+
Report-To
|
67
|
+
Retry-After
|
68
|
+
Sec-Fetch-Dest
|
69
|
+
Sec-Fetch-Mode
|
70
|
+
Sec-Fetch-Site
|
71
|
+
Sec-Fetch-User
|
72
|
+
Server
|
73
|
+
Set-Cookie
|
74
|
+
Status
|
75
|
+
Strict-Transport-Security
|
76
|
+
Timing-Allow-Origin
|
77
|
+
Tk
|
78
|
+
Trailer
|
79
|
+
Transfer-Encoding
|
80
|
+
Upgrade
|
81
|
+
Upgrade-Insecure-Requests
|
82
|
+
User-Agent
|
83
|
+
Vary
|
84
|
+
Version
|
85
|
+
Via
|
86
|
+
Warning
|
87
|
+
WWW-Authenticate
|
88
|
+
X-Accel-Buffering
|
89
|
+
X-Accel-Charset
|
90
|
+
X-Accel-Expires
|
91
|
+
X-Accel-Limit-Rate
|
92
|
+
X-Accel-Mapping
|
93
|
+
X-Accel-Redirect
|
94
|
+
X-Access-Token
|
95
|
+
X-Auth-Request-Access-Token
|
96
|
+
X-Auth-Request-Email
|
97
|
+
X-Auth-Request-Groups
|
98
|
+
X-Auth-Request-Preferred-Username
|
99
|
+
X-Auth-Request-Redirect
|
100
|
+
X-Auth-Request-Token
|
101
|
+
X-Auth-Request-User
|
102
|
+
X-Cascade
|
103
|
+
X-Client-Ip
|
104
|
+
X-Content-Duration
|
105
|
+
X-Content-Security-Policy
|
106
|
+
X-Content-Type-Options
|
107
|
+
X-Correlation-Id
|
108
|
+
X-Download-Options
|
109
|
+
X-Forwarded-Access-Token
|
110
|
+
X-Forwarded-Email
|
111
|
+
X-Forwarded-For
|
112
|
+
X-Forwarded-Groups
|
113
|
+
X-Forwarded-Host
|
114
|
+
X-Forwarded-Port
|
115
|
+
X-Forwarded-Preferred-Username
|
116
|
+
X-Forwarded-Proto
|
117
|
+
X-Forwarded-Scheme
|
118
|
+
X-Forwarded-Ssl
|
119
|
+
X-Forwarded-Uri
|
120
|
+
X-Forwarded-User
|
121
|
+
X-Frame-Options
|
122
|
+
X-HTTP-Method-Override
|
123
|
+
X-Permitted-Cross-Domain-Policies
|
124
|
+
X-Powered-By
|
125
|
+
X-Real-IP
|
126
|
+
X-Redirect-By
|
127
|
+
X-Request-Id
|
128
|
+
X-Requested-With
|
129
|
+
X-Runtime
|
130
|
+
X-Sendfile
|
131
|
+
X-Sendfile-Type
|
132
|
+
X-UA-Compatible
|
133
|
+
X-WebKit-CS
|
134
|
+
X-XSS-Protection
|
135
|
+
].each_with_object({}) do |header, response|
|
136
|
+
response["HTTP_#{header.upcase.tr('-', '_')}"] = header
|
137
|
+
end.freeze
|
6
138
|
|
7
139
|
alias rack_params params
|
140
|
+
alias rack_cookies cookies
|
8
141
|
|
9
|
-
def initialize(env,
|
10
|
-
extend options[:build_params_with] || Grape.config.param_builder
|
142
|
+
def initialize(env, build_params_with: nil)
|
11
143
|
super(env)
|
144
|
+
@params_builder = Grape::ParamsBuilder.params_builder_for(build_params_with || Grape.config.param_builder)
|
12
145
|
end
|
13
146
|
|
14
147
|
def params
|
15
|
-
@params ||=
|
16
|
-
rescue EOFError
|
17
|
-
raise Grape::Exceptions::EmptyMessageBody.new(content_type)
|
18
|
-
rescue Rack::Multipart::MultipartPartLimitError
|
19
|
-
raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
|
148
|
+
@params ||= make_params
|
20
149
|
end
|
21
150
|
|
22
151
|
def headers
|
23
152
|
@headers ||= build_headers
|
24
153
|
end
|
25
154
|
|
26
|
-
|
155
|
+
def cookies
|
156
|
+
@cookies ||= Grape::Cookies.new(-> { rack_cookies })
|
157
|
+
end
|
27
158
|
|
159
|
+
# needs to be public until extensions param_builder are removed
|
28
160
|
def grape_routing_args
|
29
|
-
args = env[Grape::Env::GRAPE_ROUTING_ARGS].dup
|
30
161
|
# preserve version from query string parameters
|
31
|
-
|
32
|
-
args.delete(:route_info)
|
33
|
-
args
|
162
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS]&.except(:version, :route_info) || {}
|
34
163
|
end
|
35
164
|
|
36
|
-
|
37
|
-
Grape::Util::Lazy::Object.new do
|
38
|
-
env.each_pair.with_object(Grape::Util::Header.new) do |(k, v), headers|
|
39
|
-
next unless k.to_s.start_with? HTTP_PREFIX
|
165
|
+
private
|
40
166
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
167
|
+
def make_params
|
168
|
+
@params_builder.call(rack_params).deep_merge!(grape_routing_args)
|
169
|
+
rescue EOFError
|
170
|
+
raise Grape::Exceptions::EmptyMessageBody.new(content_type)
|
171
|
+
rescue Rack::Multipart::MultipartPartLimitError, Rack::Multipart::MultipartTotalPartLimitError
|
172
|
+
raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
|
173
|
+
rescue Rack::QueryParser::ParamsTooDeepError
|
174
|
+
raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
|
175
|
+
rescue Rack::Utils::ParameterTypeError
|
176
|
+
raise Grape::Exceptions::ConflictingTypes
|
177
|
+
rescue Rack::Utils::InvalidParameterError
|
178
|
+
raise Grape::Exceptions::InvalidParameters
|
45
179
|
end
|
46
180
|
|
47
|
-
def
|
48
|
-
|
181
|
+
def build_headers
|
182
|
+
each_header.with_object(Grape::Util::Header.new) do |(k, v), headers|
|
183
|
+
next unless k.start_with? 'HTTP_'
|
184
|
+
|
185
|
+
transformed_header = KNOWN_HEADERS.fetch(k) { -k[5..].tr('_', '-').downcase }
|
186
|
+
headers[transformed_header] = v
|
187
|
+
end
|
49
188
|
end
|
50
189
|
end
|
51
190
|
end
|
@@ -7,8 +7,8 @@ module Grape
|
|
7
7
|
|
8
8
|
attr_reader :index, :pattern, :options
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@options = ActiveSupport::OrderedOptions.new.update(options)
|
10
|
+
def initialize(options)
|
11
|
+
@options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)
|
12
12
|
end
|
13
13
|
|
14
14
|
alias attributes options
|
@@ -6,9 +6,9 @@
|
|
6
6
|
module Grape
|
7
7
|
class Router
|
8
8
|
class GreedyRoute < BaseRoute
|
9
|
-
def initialize(pattern
|
9
|
+
def initialize(pattern, options)
|
10
10
|
@pattern = pattern
|
11
|
-
super(
|
11
|
+
super(options)
|
12
12
|
end
|
13
13
|
|
14
14
|
# Grape::Router:Route defines params as a function
|
data/lib/grape/router/pattern.rb
CHANGED
@@ -9,14 +9,14 @@ module Grape
|
|
9
9
|
|
10
10
|
attr_reader :origin, :path, :pattern, :to_regexp
|
11
11
|
|
12
|
-
def_delegators :pattern, :
|
12
|
+
def_delegators :pattern, :params
|
13
13
|
def_delegators :to_regexp, :===
|
14
14
|
alias match? ===
|
15
15
|
|
16
|
-
def initialize(
|
17
|
-
@origin =
|
18
|
-
@path = build_path(
|
19
|
-
@pattern = build_pattern(@path, options)
|
16
|
+
def initialize(origin, suffix, options)
|
17
|
+
@origin = origin
|
18
|
+
@path = build_path(origin, options[:anchor], suffix)
|
19
|
+
@pattern = build_pattern(@path, options[:params], options[:format], options[:version], options[:requirements])
|
20
20
|
@to_regexp = @pattern.to_regexp
|
21
21
|
end
|
22
22
|
|
@@ -28,30 +28,31 @@ module Grape
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
-
def build_pattern(path,
|
31
|
+
def build_pattern(path, params, format, version, requirements)
|
32
32
|
Mustermann::Grape.new(
|
33
33
|
path,
|
34
34
|
uri_decode: true,
|
35
|
-
params:
|
36
|
-
capture: extract_capture(
|
35
|
+
params: params,
|
36
|
+
capture: extract_capture(format, version, requirements)
|
37
37
|
)
|
38
38
|
end
|
39
39
|
|
40
|
-
def build_path(pattern, anchor
|
41
|
-
PatternCache[[build_path_from_pattern(pattern, anchor
|
40
|
+
def build_path(pattern, anchor, suffix)
|
41
|
+
PatternCache[[build_path_from_pattern(pattern, anchor), suffix]]
|
42
42
|
end
|
43
43
|
|
44
|
-
def extract_capture(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
def extract_capture(format, version, requirements)
|
45
|
+
capture = {}.tap do |h|
|
46
|
+
h[:format] = map_str(format) if format.present?
|
47
|
+
h[:version] = map_str(version) if version.present?
|
48
|
+
end
|
49
|
+
|
50
|
+
return capture if requirements.blank?
|
50
51
|
|
51
|
-
|
52
|
+
requirements.merge(capture)
|
52
53
|
end
|
53
54
|
|
54
|
-
def build_path_from_pattern(pattern, anchor
|
55
|
+
def build_path_from_pattern(pattern, anchor)
|
55
56
|
if pattern.end_with?('*path')
|
56
57
|
pattern.dup.insert(pattern.rindex('/') + 1, '?')
|
57
58
|
elsif anchor
|
@@ -63,6 +64,10 @@ module Grape
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
67
|
+
def map_str(value)
|
68
|
+
Array.wrap(value).map(&:to_s)
|
69
|
+
end
|
70
|
+
|
66
71
|
class PatternCache < Grape::Util::Cache
|
67
72
|
def initialize
|
68
73
|
super
|
data/lib/grape/router/route.rb
CHANGED
@@ -5,14 +5,22 @@ module Grape
|
|
5
5
|
class Route < BaseRoute
|
6
6
|
extend Forwardable
|
7
7
|
|
8
|
+
FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
|
9
|
+
NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
|
10
|
+
|
8
11
|
attr_reader :app, :request_method
|
9
12
|
|
10
13
|
def_delegators :pattern, :path, :origin
|
11
14
|
|
12
|
-
def initialize(method,
|
15
|
+
def initialize(method, origin, path, options)
|
13
16
|
@request_method = upcase_method(method)
|
14
|
-
@pattern = Grape::Router::Pattern.new(
|
15
|
-
|
17
|
+
@pattern = Grape::Router::Pattern.new(origin, path, options)
|
18
|
+
@match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD
|
19
|
+
super(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def convert_to_head_request!
|
23
|
+
@request_method = Rack::HEAD
|
16
24
|
end
|
17
25
|
|
18
26
|
def exec(env)
|
@@ -27,7 +35,7 @@ module Grape
|
|
27
35
|
def match?(input)
|
28
36
|
return false if input.blank?
|
29
37
|
|
30
|
-
|
38
|
+
@match_function.call(input, pattern)
|
31
39
|
end
|
32
40
|
|
33
41
|
def params(input = nil)
|
@@ -42,12 +50,12 @@ module Grape
|
|
42
50
|
private
|
43
51
|
|
44
52
|
def params_without_input
|
45
|
-
pattern.captures_default.merge(attributes.params)
|
53
|
+
@params_without_input ||= pattern.captures_default.merge(attributes.params)
|
46
54
|
end
|
47
55
|
|
48
56
|
def upcase_method(method)
|
49
57
|
method_s = method.to_s
|
50
|
-
Grape::
|
58
|
+
Grape::HTTP_SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
|
51
59
|
end
|
52
60
|
end
|
53
61
|
end
|
data/lib/grape/router.rb
CHANGED
@@ -4,12 +4,30 @@ module Grape
|
|
4
4
|
class Router
|
5
5
|
attr_reader :map, :compiled
|
6
6
|
|
7
|
+
# Taken from Rails
|
8
|
+
# normalize_path("/foo") # => "/foo"
|
9
|
+
# normalize_path("/foo/") # => "/foo"
|
10
|
+
# normalize_path("foo") # => "/foo"
|
11
|
+
# normalize_path("") # => "/"
|
12
|
+
# normalize_path("/%ab") # => "/%AB"
|
13
|
+
# https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
|
7
14
|
def self.normalize_path(path)
|
15
|
+
return +'/' unless path
|
16
|
+
|
17
|
+
# Fast path for the overwhelming majority of paths that don't need to be normalized
|
18
|
+
return path.dup if path == '/' || (path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//})))
|
19
|
+
|
20
|
+
# Slow path
|
21
|
+
encoding = path.encoding
|
8
22
|
path = +"/#{path}"
|
9
23
|
path.squeeze!('/')
|
10
|
-
|
11
|
-
|
12
|
-
|
24
|
+
|
25
|
+
unless path == '/'
|
26
|
+
path.delete_suffix!('/')
|
27
|
+
path.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }
|
28
|
+
end
|
29
|
+
|
30
|
+
path.force_encoding(encoding)
|
13
31
|
end
|
14
32
|
|
15
33
|
def initialize
|
@@ -24,7 +42,7 @@ module Grape
|
|
24
42
|
|
25
43
|
@union = Regexp.union(@neutral_regexes)
|
26
44
|
@neutral_regexes = nil
|
27
|
-
(Grape::
|
45
|
+
(Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
|
28
46
|
next unless map.key?(method)
|
29
47
|
|
30
48
|
routes = map[method]
|
@@ -38,8 +56,8 @@ module Grape
|
|
38
56
|
map[route.request_method] << route
|
39
57
|
end
|
40
58
|
|
41
|
-
def associate_routes(pattern,
|
42
|
-
Grape::Router::GreedyRoute.new(pattern
|
59
|
+
def associate_routes(pattern, options)
|
60
|
+
Grape::Router::GreedyRoute.new(pattern, options).then do |greedy_route|
|
43
61
|
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
|
44
62
|
@neutral_map << greedy_route
|
45
63
|
end
|
@@ -93,7 +111,7 @@ module Grape
|
|
93
111
|
return response unless cascade
|
94
112
|
|
95
113
|
# we need to close the body if possible before dismissing
|
96
|
-
response[2].
|
114
|
+
response[2].try(:close)
|
97
115
|
end
|
98
116
|
end
|
99
117
|
end
|
@@ -107,7 +125,7 @@ module Grape
|
|
107
125
|
|
108
126
|
route = match?(input, '*')
|
109
127
|
|
110
|
-
return last_neighbor_route.endpoint.call(env) if last_neighbor_route && last_response_cascade && route
|
128
|
+
return last_neighbor_route.options[:endpoint].call(env) if last_neighbor_route && last_response_cascade && route
|
111
129
|
|
112
130
|
last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
|
113
131
|
|
@@ -138,7 +156,7 @@ module Grape
|
|
138
156
|
end
|
139
157
|
|
140
158
|
def default_response
|
141
|
-
headers = Grape::Util::Header.new.merge(
|
159
|
+
headers = Grape::Util::Header.new.merge('X-Cascade' => 'pass')
|
142
160
|
[404, headers, ['404 Not Found']]
|
143
161
|
end
|
144
162
|
|
@@ -152,8 +170,8 @@ module Grape
|
|
152
170
|
|
153
171
|
def call_with_allow_headers(env, route)
|
154
172
|
prepare_env_from_route(env, route)
|
155
|
-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header
|
156
|
-
route.endpoint.call(env)
|
173
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
|
174
|
+
route.options[:endpoint].call(env)
|
157
175
|
end
|
158
176
|
|
159
177
|
def prepare_env_from_route(env, route)
|
@@ -162,7 +180,7 @@ module Grape
|
|
162
180
|
end
|
163
181
|
|
164
182
|
def cascade?(response)
|
165
|
-
response && response[1][
|
183
|
+
response && response[1]['X-Cascade'] == 'pass'
|
166
184
|
end
|
167
185
|
|
168
186
|
def string_for(input)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
module Registry
|
6
|
+
def register(klass)
|
7
|
+
short_name = build_short_name(klass)
|
8
|
+
return if short_name.nil?
|
9
|
+
|
10
|
+
warn "#{short_name} is already registered with class #{klass}" if registry.key?(short_name)
|
11
|
+
registry[short_name] = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def build_short_name(klass)
|
17
|
+
return if klass.name.blank?
|
18
|
+
|
19
|
+
klass.name.demodulize.underscore
|
20
|
+
end
|
21
|
+
|
22
|
+
def registry
|
23
|
+
@registry ||= {}.with_indifferent_access
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -23,49 +23,12 @@ module Grape
|
|
23
23
|
api.namespace_stackable(:contract_key_map, key_map)
|
24
24
|
|
25
25
|
validator_options = {
|
26
|
-
validator_class:
|
27
|
-
opts: { schema: contract }
|
26
|
+
validator_class: Grape::Validations.require_validator(:contract_scope),
|
27
|
+
opts: { schema: contract, fail_fast: false }
|
28
28
|
}
|
29
29
|
|
30
30
|
api.namespace_stackable(:validations, validator_options)
|
31
31
|
end
|
32
|
-
|
33
|
-
class Validator
|
34
|
-
attr_reader :schema
|
35
|
-
|
36
|
-
def initialize(*_args, schema:)
|
37
|
-
@schema = schema
|
38
|
-
end
|
39
|
-
|
40
|
-
# Validates a given request.
|
41
|
-
# @param request [Grape::Request] the request currently being handled
|
42
|
-
# @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
|
43
|
-
# @return [void]
|
44
|
-
def validate(request)
|
45
|
-
res = schema.call(request.params)
|
46
|
-
|
47
|
-
if res.success?
|
48
|
-
request.params.deep_merge!(res.to_h)
|
49
|
-
return
|
50
|
-
end
|
51
|
-
|
52
|
-
errors = []
|
53
|
-
|
54
|
-
res.errors.messages.each do |message|
|
55
|
-
full_name = message.path.first.to_s
|
56
|
-
|
57
|
-
full_name += "[#{message.path[1..].join('][')}]" if message.path.size > 1
|
58
|
-
|
59
|
-
errors << Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
|
60
|
-
end
|
61
|
-
|
62
|
-
raise Grape::Exceptions::ValidationArrayErrors.new(errors)
|
63
|
-
end
|
64
|
-
|
65
|
-
def fail_fast?
|
66
|
-
false
|
67
|
-
end
|
68
|
-
end
|
69
32
|
end
|
70
33
|
end
|
71
34
|
end
|