grape 2.3.0 → 3.0.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 +69 -0
- data/CONTRIBUTING.md +2 -10
- data/README.md +106 -43
- data/UPGRADING.md +90 -1
- data/grape.gemspec +4 -4
- data/lib/grape/api/instance.rb +51 -73
- data/lib/grape/api.rb +56 -89
- data/lib/grape/cookies.rb +31 -25
- data/lib/grape/dry_types.rb +48 -4
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/desc.rb +8 -67
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/helpers.rb +60 -65
- data/lib/grape/dsl/inside_route.rb +26 -61
- data/lib/grape/dsl/logger.rb +3 -6
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +10 -19
- data/lib/grape/dsl/request_response.rb +136 -139
- data/lib/grape/dsl/routing.rb +230 -194
- data/lib/grape/dsl/settings.rb +22 -134
- data/lib/grape/dsl/validations.rb +37 -45
- data/lib/grape/endpoint.rb +91 -126
- data/lib/grape/error_formatter/base.rb +2 -0
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/exceptions/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- 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/unsupported_group_type.rb +0 -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/locale/en.yml +44 -44
- data/lib/grape/middleware/auth/base.rb +11 -32
- data/lib/grape/middleware/auth/dsl.rb +22 -29
- data/lib/grape/middleware/base.rb +30 -11
- data/lib/grape/middleware/error.rb +14 -32
- data/lib/grape/middleware/formatter.rb +40 -72
- data/lib/grape/middleware/stack.rb +28 -38
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -4
- data/lib/grape/middleware/versioner/base.rb +30 -56
- data/lib/grape/middleware/versioner/header.rb +2 -2
- data/lib/grape/middleware/versioner/param.rb +2 -3
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +11 -0
- data/lib/grape/params_builder/base.rb +20 -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/request.rb +161 -22
- data/lib/grape/router/route.rb +1 -1
- data/lib/grape/router.rb +27 -8
- data/lib/grape/util/api_description.rb +56 -0
- data/lib/grape/util/base_inheritable.rb +5 -2
- data/lib/grape/util/inheritable_setting.rb +7 -0
- data/lib/grape/util/media_type.rb +1 -1
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/validations/contract_scope.rb +2 -2
- data/lib/grape/validations/params_documentation.rb +50 -0
- data/lib/grape/validations/params_scope.rb +46 -56
- data/lib/grape/validations/types/array_coercer.rb +2 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
- data/lib/grape/validations/types/primitive_coercer.rb +1 -28
- data/lib/grape/validations/types.rb +10 -25
- data/lib/grape/validations/validators/base.rb +2 -9
- data/lib/grape/validations/validators/except_values_validator.rb +1 -1
- data/lib/grape/validations/validators/presence_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +18 -9
- metadata +35 -20
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -19
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/error_formatter/jsonapi.rb +0 -7
- data/lib/grape/http/headers.rb +0 -56
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/parser/jsonapi.rb +0 -7
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/lazy/object.rb +0 -45
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
|
@@ -4,77 +4,51 @@ module Grape
|
|
|
4
4
|
module Middleware
|
|
5
5
|
module Versioner
|
|
6
6
|
class Base < Grape::Middleware::Base
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
DEFAULT_OPTIONS = {
|
|
8
|
+
pattern: /.*/i,
|
|
9
|
+
prefix: nil,
|
|
10
|
+
mount_path: nil,
|
|
11
|
+
version_options: {
|
|
12
|
+
strict: false,
|
|
13
|
+
cascade: true,
|
|
14
|
+
parameter: 'apiver',
|
|
15
|
+
vendor: nil
|
|
16
|
+
}.freeze
|
|
17
|
+
}.freeze
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
super
|
|
12
|
-
Versioner.register(klass)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def default_options
|
|
16
|
-
{
|
|
17
|
-
versions: nil,
|
|
18
|
-
prefix: nil,
|
|
19
|
-
mount_path: nil,
|
|
20
|
-
pattern: DEFAULT_PATTERN,
|
|
21
|
-
version_options: {
|
|
22
|
-
strict: false,
|
|
23
|
-
cascade: true,
|
|
24
|
-
parameter: DEFAULT_PARAMETER
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
end
|
|
19
|
+
CASCADE_PASS_HEADER = { 'X-Cascade' => 'pass' }.freeze
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
DEFAULT_OPTIONS.each_key do |key|
|
|
22
|
+
define_method key do
|
|
23
|
+
options[key]
|
|
24
|
+
end
|
|
31
25
|
end
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
DEFAULT_OPTIONS[:version_options].each_key do |key|
|
|
28
|
+
define_method key do
|
|
29
|
+
options[:version_options][key]
|
|
30
|
+
end
|
|
35
31
|
end
|
|
36
32
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def pattern
|
|
42
|
-
options[:pattern]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def version_options
|
|
46
|
-
options[:version_options]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def strict?
|
|
50
|
-
version_options[:strict]
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
|
54
|
-
# of routes (see Grape::Router) for more information). To prevent
|
|
55
|
-
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
|
56
|
-
def cascade?
|
|
57
|
-
version_options[:cascade]
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def parameter_key
|
|
61
|
-
version_options[:parameter]
|
|
33
|
+
def self.inherited(klass)
|
|
34
|
+
super
|
|
35
|
+
Versioner.register(klass)
|
|
62
36
|
end
|
|
63
37
|
|
|
64
|
-
|
|
65
|
-
version_options[:vendor]
|
|
66
|
-
end
|
|
38
|
+
attr_reader :error_headers, :versions
|
|
67
39
|
|
|
68
|
-
def
|
|
69
|
-
|
|
40
|
+
def initialize(app, **options)
|
|
41
|
+
super
|
|
42
|
+
@error_headers = cascade ? CASCADE_PASS_HEADER : {}
|
|
43
|
+
@versions = options[:versions]&.map(&:to_s) # making sure versions are strings to ease potential match
|
|
70
44
|
end
|
|
71
45
|
|
|
72
46
|
def potential_version_match?(potential_version)
|
|
73
|
-
versions.blank? || versions.
|
|
47
|
+
versions.blank? || versions.include?(potential_version)
|
|
74
48
|
end
|
|
75
49
|
|
|
76
50
|
def version_not_found!
|
|
77
|
-
throw :error, status: 404, message: '404 API Version Not Found', headers:
|
|
51
|
+
throw :error, status: 404, message: '404 API Version Not Found', headers: CASCADE_PASS_HEADER
|
|
78
52
|
end
|
|
79
53
|
end
|
|
80
54
|
end
|
|
@@ -49,11 +49,11 @@ module Grape
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def accept_header
|
|
52
|
-
env[
|
|
52
|
+
env['HTTP_ACCEPT']
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def strict_header_checks!
|
|
56
|
-
return unless strict
|
|
56
|
+
return unless strict
|
|
57
57
|
|
|
58
58
|
accept_header_check!
|
|
59
59
|
version_and_vendor_check!
|
|
@@ -20,12 +20,11 @@ module Grape
|
|
|
20
20
|
# env['api.version'] => 'v1'
|
|
21
21
|
class Param < Base
|
|
22
22
|
def before
|
|
23
|
-
potential_version =
|
|
23
|
+
potential_version = query_params[parameter]
|
|
24
24
|
return if potential_version.blank?
|
|
25
25
|
|
|
26
26
|
version_not_found! unless potential_version_match?(potential_version)
|
|
27
|
-
env[Grape::Env::API_VERSION] =
|
|
28
|
-
env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
|
|
27
|
+
env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter)
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
end
|
|
@@ -28,7 +28,7 @@ module Grape
|
|
|
28
28
|
slash_position = path_info.index('/', 1) # omit the first one
|
|
29
29
|
return unless slash_position
|
|
30
30
|
|
|
31
|
-
potential_version = path_info[1..slash_position - 1]
|
|
31
|
+
potential_version = path_info[1..(slash_position - 1)]
|
|
32
32
|
return unless potential_version.match?(pattern)
|
|
33
33
|
|
|
34
34
|
version_not_found! unless potential_version_match?(potential_version)
|
data/lib/grape/namespace.rb
CHANGED
|
@@ -28,6 +28,17 @@ module Grape
|
|
|
28
28
|
settings&.map(&:space)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def eql?(other)
|
|
32
|
+
other.class == self.class &&
|
|
33
|
+
other.space == space &&
|
|
34
|
+
other.options == options
|
|
35
|
+
end
|
|
36
|
+
alias == eql?
|
|
37
|
+
|
|
38
|
+
def hash
|
|
39
|
+
[self.class, space, options].hash
|
|
40
|
+
end
|
|
41
|
+
|
|
31
42
|
# Join the namespaces from a list of settings to create a path prefix.
|
|
32
43
|
# @param settings [Array] list of Grape::Util::InheritableSettings.
|
|
33
44
|
def self.joined_space_path(settings)
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
private
|
|
12
|
+
|
|
13
|
+
def inherited(klass)
|
|
14
|
+
super
|
|
15
|
+
ParamsBuilder.register(klass)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
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
|
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
142
|
def initialize(env, build_params_with: nil)
|
|
10
|
-
extend build_params_with || Grape.config.param_builder
|
|
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
|
data/lib/grape/router/route.rb
CHANGED
|
@@ -55,7 +55,7 @@ module Grape
|
|
|
55
55
|
|
|
56
56
|
def upcase_method(method)
|
|
57
57
|
method_s = method.to_s
|
|
58
|
-
Grape::
|
|
58
|
+
Grape::HTTP_SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
data/lib/grape/router.rb
CHANGED
|
@@ -4,12 +4,31 @@ 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)
|
|
8
|
-
|
|
15
|
+
return '/' unless path
|
|
16
|
+
return path if path == '/'
|
|
17
|
+
|
|
18
|
+
# Fast path for the overwhelming majority of paths that don't need to be normalized
|
|
19
|
+
return path.dup if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))
|
|
20
|
+
|
|
21
|
+
# Slow path
|
|
22
|
+
encoding = path.encoding
|
|
23
|
+
path = "/#{path}"
|
|
9
24
|
path.squeeze!('/')
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
|
|
26
|
+
unless path == '/'
|
|
27
|
+
path.delete_suffix!('/')
|
|
28
|
+
path.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
path.force_encoding(encoding)
|
|
13
32
|
end
|
|
14
33
|
|
|
15
34
|
def initialize
|
|
@@ -24,7 +43,7 @@ module Grape
|
|
|
24
43
|
|
|
25
44
|
@union = Regexp.union(@neutral_regexes)
|
|
26
45
|
@neutral_regexes = nil
|
|
27
|
-
(Grape::
|
|
46
|
+
(Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
|
|
28
47
|
next unless map.key?(method)
|
|
29
48
|
|
|
30
49
|
routes = map[method]
|
|
@@ -93,7 +112,7 @@ module Grape
|
|
|
93
112
|
return response unless cascade
|
|
94
113
|
|
|
95
114
|
# we need to close the body if possible before dismissing
|
|
96
|
-
response[2].
|
|
115
|
+
response[2].try(:close)
|
|
97
116
|
end
|
|
98
117
|
end
|
|
99
118
|
end
|
|
@@ -138,7 +157,7 @@ module Grape
|
|
|
138
157
|
end
|
|
139
158
|
|
|
140
159
|
def default_response
|
|
141
|
-
headers = Grape::Util::Header.new.merge(
|
|
160
|
+
headers = Grape::Util::Header.new.merge('X-Cascade' => 'pass')
|
|
142
161
|
[404, headers, ['404 Not Found']]
|
|
143
162
|
end
|
|
144
163
|
|
|
@@ -162,7 +181,7 @@ module Grape
|
|
|
162
181
|
end
|
|
163
182
|
|
|
164
183
|
def cascade?(response)
|
|
165
|
-
response && response[1][
|
|
184
|
+
response && response[1]['X-Cascade'] == 'pass'
|
|
166
185
|
end
|
|
167
186
|
|
|
168
187
|
def string_for(input)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
class ApiDescription
|
|
6
|
+
def initialize(description, endpoint_configuration, &block)
|
|
7
|
+
@endpoint_configuration = endpoint_configuration
|
|
8
|
+
@attributes = { description: description }
|
|
9
|
+
instance_eval(&block)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
%i[
|
|
13
|
+
body_name
|
|
14
|
+
consumes
|
|
15
|
+
default
|
|
16
|
+
deprecated
|
|
17
|
+
detail
|
|
18
|
+
entity
|
|
19
|
+
headers
|
|
20
|
+
hidden
|
|
21
|
+
http_codes
|
|
22
|
+
is_array
|
|
23
|
+
named
|
|
24
|
+
nickname
|
|
25
|
+
params
|
|
26
|
+
produces
|
|
27
|
+
security
|
|
28
|
+
summary
|
|
29
|
+
tags
|
|
30
|
+
].each do |attribute|
|
|
31
|
+
define_method attribute do |value|
|
|
32
|
+
@attributes[attribute] = value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
alias success entity
|
|
37
|
+
alias failure http_codes
|
|
38
|
+
|
|
39
|
+
def configuration
|
|
40
|
+
@configuration ||= eval_endpoint_config(@endpoint_configuration)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def settings
|
|
44
|
+
@attributes
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def eval_endpoint_config(configuration)
|
|
50
|
+
return configuration if configuration.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
configuration.evaluate
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -14,8 +14,11 @@ module Grape
|
|
|
14
14
|
@new_values = {}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def delete(
|
|
18
|
-
|
|
17
|
+
def delete(*keys)
|
|
18
|
+
keys.map do |key|
|
|
19
|
+
# since delete returns the deleted value, seems natural to `map` the result
|
|
20
|
+
new_values.delete key
|
|
21
|
+
end
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
def initialize_copy(other)
|
|
@@ -95,6 +95,13 @@ module Grape
|
|
|
95
95
|
namespace_reverse_stackable: namespace_reverse_stackable.to_hash
|
|
96
96
|
}
|
|
97
97
|
end
|
|
98
|
+
|
|
99
|
+
def namespace_stackable_with_hash(key)
|
|
100
|
+
data = namespace_stackable[key]
|
|
101
|
+
return if data.blank?
|
|
102
|
+
|
|
103
|
+
data.each_with_object({}) { |value, result| result.deep_merge!(value) }
|
|
104
|
+
end
|
|
98
105
|
end
|
|
99
106
|
end
|
|
100
107
|
end
|
|
@@ -7,7 +7,7 @@ module Grape
|
|
|
7
7
|
|
|
8
8
|
# based on the HTTP Accept header with the pattern:
|
|
9
9
|
# application/vnd.:vendor-:version+:format
|
|
10
|
-
VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z
|
|
10
|
+
VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z/
|
|
11
11
|
|
|
12
12
|
def initialize(type:, subtype:)
|
|
13
13
|
@type = type
|
data/lib/grape/util/registry.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Grape
|
|
|
7
7
|
short_name = build_short_name(klass)
|
|
8
8
|
return if short_name.nil?
|
|
9
9
|
|
|
10
|
-
warn "#{short_name} is already registered with class #{klass}" if registry.key?(short_name)
|
|
10
|
+
warn "#{short_name} is already registered with class #{registry[short_name]}. It will be overridden globally with the following: #{klass.name}" if registry.key?(short_name)
|
|
11
11
|
registry[short_name] = klass
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -20,14 +20,14 @@ module Grape
|
|
|
20
20
|
key_map = contract.key_map
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
api.namespace_stackable
|
|
23
|
+
api.inheritable_setting.namespace_stackable[:contract_key_map] = key_map
|
|
24
24
|
|
|
25
25
|
validator_options = {
|
|
26
26
|
validator_class: Grape::Validations.require_validator(:contract_scope),
|
|
27
27
|
opts: { schema: contract, fail_fast: false }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
api.namespace_stackable
|
|
30
|
+
api.inheritable_setting.namespace_stackable[:validations] = validator_options
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|