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
data/lib/grape/router/route.rb
CHANGED
@@ -1,57 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/router/pattern'
|
4
|
-
require 'grape/router/attribute_translator'
|
5
|
-
require 'forwardable'
|
6
|
-
require 'pathname'
|
7
|
-
|
8
3
|
module Grape
|
9
4
|
class Router
|
10
|
-
class Route
|
11
|
-
|
12
|
-
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
|
13
|
-
FIXED_NAMED_CAPTURES = %w[format version].freeze
|
5
|
+
class Route < BaseRoute
|
6
|
+
extend Forwardable
|
14
7
|
|
15
|
-
|
8
|
+
FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
|
9
|
+
NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
|
16
10
|
|
17
|
-
|
11
|
+
attr_reader :app, :request_method
|
18
12
|
|
19
|
-
extend Forwardable
|
20
13
|
def_delegators :pattern, :path, :origin
|
21
|
-
delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
|
22
|
-
|
23
|
-
def method_missing(method_id, *arguments)
|
24
|
-
match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
|
25
|
-
if match
|
26
|
-
method_name = match.captures.last.to_sym
|
27
|
-
warn_route_methods(method_name, caller(1).shift)
|
28
|
-
@options[method_name]
|
29
|
-
else
|
30
|
-
super
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def respond_to_missing?(method_id, _)
|
35
|
-
ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
|
36
|
-
end
|
37
|
-
|
38
|
-
def route_method
|
39
|
-
warn_route_methods(:method, caller(1).shift, :request_method)
|
40
|
-
request_method
|
41
|
-
end
|
42
14
|
|
43
|
-
def
|
44
|
-
|
45
|
-
pattern.path
|
15
|
+
def initialize(method, origin, path, options)
|
16
|
+
@request_method = upcase_method(method)
|
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)
|
46
20
|
end
|
47
21
|
|
48
|
-
def
|
49
|
-
|
50
|
-
method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase
|
51
|
-
|
52
|
-
@options = options.merge(method: method_upcase)
|
53
|
-
@pattern = Pattern.new(pattern, **options)
|
54
|
-
@translator = AttributeTranslator.new(**options, request_method: method_upcase)
|
22
|
+
def convert_to_head_request!
|
23
|
+
@request_method = Rack::HEAD
|
55
24
|
end
|
56
25
|
|
57
26
|
def exec(env)
|
@@ -64,27 +33,29 @@ module Grape
|
|
64
33
|
end
|
65
34
|
|
66
35
|
def match?(input)
|
67
|
-
|
36
|
+
return false if input.blank?
|
37
|
+
|
38
|
+
@match_function.call(input, pattern)
|
68
39
|
end
|
69
40
|
|
70
41
|
def params(input = nil)
|
71
|
-
if input.
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
parsed ? parsed.delete_if { |_, value| value.nil? }.symbolize_keys : {}
|
78
|
-
end
|
42
|
+
return params_without_input if input.blank?
|
43
|
+
|
44
|
+
parsed = pattern.params(input)
|
45
|
+
return {} unless parsed
|
46
|
+
|
47
|
+
parsed.compact.symbolize_keys
|
79
48
|
end
|
80
49
|
|
81
50
|
private
|
82
51
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
52
|
+
def params_without_input
|
53
|
+
@params_without_input ||= pattern.captures_default.merge(attributes.params)
|
54
|
+
end
|
55
|
+
|
56
|
+
def upcase_method(method)
|
57
|
+
method_s = method.to_s
|
58
|
+
Grape::HTTP_SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
|
88
59
|
end
|
89
60
|
end
|
90
61
|
end
|
data/lib/grape/router.rb
CHANGED
@@ -1,22 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/router/route'
|
4
|
-
require 'grape/util/cache'
|
5
|
-
|
6
3
|
module Grape
|
7
4
|
class Router
|
8
5
|
attr_reader :map, :compiled
|
9
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
|
10
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
|
11
22
|
path = +"/#{path}"
|
12
23
|
path.squeeze!('/')
|
13
|
-
path.sub!(%r{/+\Z}, '')
|
14
|
-
path = '/' if path == ''
|
15
|
-
path
|
16
|
-
end
|
17
24
|
|
18
|
-
|
19
|
-
|
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)
|
20
31
|
end
|
21
32
|
|
22
33
|
def initialize
|
@@ -31,13 +42,12 @@ module Grape
|
|
31
42
|
|
32
43
|
@union = Regexp.union(@neutral_regexes)
|
33
44
|
@neutral_regexes = nil
|
34
|
-
|
45
|
+
(Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
|
46
|
+
next unless map.key?(method)
|
47
|
+
|
35
48
|
routes = map[method]
|
36
|
-
|
37
|
-
|
38
|
-
Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
|
39
|
-
end
|
40
|
-
@optimized_map[method] = Regexp.union(@optimized_map[method])
|
49
|
+
optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
|
50
|
+
@optimized_map[method] = Regexp.union(optimized_map)
|
41
51
|
end
|
42
52
|
@compiled = true
|
43
53
|
end
|
@@ -46,9 +56,11 @@ module Grape
|
|
46
56
|
map[route.request_method] << route
|
47
57
|
end
|
48
58
|
|
49
|
-
def associate_routes(pattern,
|
50
|
-
|
51
|
-
|
59
|
+
def associate_routes(pattern, options)
|
60
|
+
Grape::Router::GreedyRoute.new(pattern, options).then do |greedy_route|
|
61
|
+
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
|
62
|
+
@neutral_map << greedy_route
|
63
|
+
end
|
52
64
|
end
|
53
65
|
|
54
66
|
def call(env)
|
@@ -91,26 +103,33 @@ module Grape
|
|
91
103
|
|
92
104
|
def transaction(env)
|
93
105
|
input, method = *extract_input_and_method(env)
|
94
|
-
response = yield(input, method)
|
95
106
|
|
96
|
-
|
107
|
+
# using a Proc is important since `return` will exit the enclosing function
|
108
|
+
cascade_or_return_response = proc do |response|
|
109
|
+
if response
|
110
|
+
cascade?(response).tap do |cascade|
|
111
|
+
return response unless cascade
|
112
|
+
|
113
|
+
# we need to close the body if possible before dismissing
|
114
|
+
response[2].try(:close)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
97
118
|
|
119
|
+
last_response_cascade = cascade_or_return_response.call(yield(input, method))
|
98
120
|
last_neighbor_route = greedy_match?(input)
|
99
121
|
|
100
122
|
# If last_neighbor_route exists and request method is OPTIONS,
|
101
123
|
# return response by using #call_with_allow_headers.
|
102
|
-
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method ==
|
124
|
+
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Rack::OPTIONS && !last_response_cascade
|
103
125
|
|
104
126
|
route = match?(input, '*')
|
105
127
|
|
106
|
-
return last_neighbor_route.endpoint.call(env) if last_neighbor_route &&
|
128
|
+
return last_neighbor_route.options[:endpoint].call(env) if last_neighbor_route && last_response_cascade && route
|
107
129
|
|
108
|
-
if route
|
109
|
-
response = process_route(route, env)
|
110
|
-
return response if response && !(cascade = cascade?(response))
|
111
|
-
end
|
130
|
+
last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
|
112
131
|
|
113
|
-
return call_with_allow_headers(env, last_neighbor_route) if !
|
132
|
+
return call_with_allow_headers(env, last_neighbor_route) if !last_response_cascade && last_neighbor_route
|
114
133
|
|
115
134
|
nil
|
116
135
|
end
|
@@ -122,12 +141,12 @@ module Grape
|
|
122
141
|
|
123
142
|
def make_routing_args(default_args, route, input)
|
124
143
|
args = default_args || { route_info: route }
|
125
|
-
args.merge(route.params(input)
|
144
|
+
args.merge(route.params(input))
|
126
145
|
end
|
127
146
|
|
128
147
|
def extract_input_and_method(env)
|
129
|
-
input = string_for(env[
|
130
|
-
method = env[
|
148
|
+
input = string_for(env[Rack::PATH_INFO])
|
149
|
+
method = env[Rack::REQUEST_METHOD]
|
131
150
|
[input, method]
|
132
151
|
end
|
133
152
|
|
@@ -137,28 +156,22 @@ module Grape
|
|
137
156
|
end
|
138
157
|
|
139
158
|
def default_response
|
140
|
-
|
159
|
+
headers = Grape::Util::Header.new.merge('X-Cascade' => 'pass')
|
160
|
+
[404, headers, ['404 Not Found']]
|
141
161
|
end
|
142
162
|
|
143
163
|
def match?(input, method)
|
144
|
-
|
145
|
-
return unless current_regexp.match(input)
|
146
|
-
|
147
|
-
last_match = Regexp.last_match
|
148
|
-
@map[method].detect { |route| last_match["_#{route.index}"] }
|
164
|
+
@optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
|
149
165
|
end
|
150
166
|
|
151
167
|
def greedy_match?(input)
|
152
|
-
|
153
|
-
|
154
|
-
last_match = Regexp.last_match
|
155
|
-
@neutral_map.detect { |route| last_match["_#{route.index}"] }
|
168
|
+
@union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
|
156
169
|
end
|
157
170
|
|
158
171
|
def call_with_allow_headers(env, route)
|
159
172
|
prepare_env_from_route(env, route)
|
160
|
-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header
|
161
|
-
route.endpoint.call(env)
|
173
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
|
174
|
+
route.options[:endpoint].call(env)
|
162
175
|
end
|
163
176
|
|
164
177
|
def prepare_env_from_route(env, route)
|
@@ -167,7 +180,7 @@ module Grape
|
|
167
180
|
end
|
168
181
|
|
169
182
|
def cascade?(response)
|
170
|
-
response && response[1][
|
183
|
+
response && response[1]['X-Cascade'] == 'pass'
|
171
184
|
end
|
172
185
|
|
173
186
|
def string_for(input)
|
@@ -26,10 +26,10 @@ module Grape
|
|
26
26
|
|
27
27
|
def keys
|
28
28
|
if new_values.any?
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
inherited_values.keys.tap do |combined|
|
30
|
+
combined.concat(new_values.keys)
|
31
|
+
combined.uniq!
|
32
|
+
end
|
33
33
|
else
|
34
34
|
inherited_values.keys
|
35
35
|
end
|
data/lib/grape/util/cache.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
if Gem::Version.new(Rack.release) >= Gem::Version.new('3')
|
6
|
+
require 'rack/headers'
|
7
|
+
Header = Rack::Headers
|
8
|
+
else
|
9
|
+
require 'rack/utils'
|
10
|
+
Header = Rack::Utils::HeaderHash
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
module Lazy
|
6
|
+
class Block
|
7
|
+
def initialize(&new_block)
|
8
|
+
@block = new_block
|
9
|
+
end
|
10
|
+
|
11
|
+
def evaluate_from(configuration)
|
12
|
+
@block.call(configuration)
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate
|
16
|
+
@block.call({})
|
17
|
+
end
|
18
|
+
|
19
|
+
def lazy?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
evaluate.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
module Lazy
|
6
|
+
class Value
|
7
|
+
attr_reader :access_keys
|
8
|
+
|
9
|
+
def initialize(value, access_keys = [])
|
10
|
+
@value = value
|
11
|
+
@access_keys = access_keys
|
12
|
+
end
|
13
|
+
|
14
|
+
def evaluate_from(configuration)
|
15
|
+
matching_lazy_value = configuration.fetch(@access_keys)
|
16
|
+
matching_lazy_value.evaluate
|
17
|
+
end
|
18
|
+
|
19
|
+
def evaluate
|
20
|
+
@value
|
21
|
+
end
|
22
|
+
|
23
|
+
def lazy?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def reached_by(parent_access_keys, access_key)
|
28
|
+
@access_keys = parent_access_keys + [access_key]
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
evaluate.to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
module Lazy
|
6
|
+
class ValueArray < ValueEnumerable
|
7
|
+
def initialize(array)
|
8
|
+
super
|
9
|
+
@value_hash = []
|
10
|
+
array.each_with_index do |value, index|
|
11
|
+
self[index] = value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate
|
16
|
+
@value_hash.map(&:evaluate)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
module Lazy
|
6
|
+
class ValueEnumerable < Value
|
7
|
+
def [](key)
|
8
|
+
if @value_hash[key].nil?
|
9
|
+
Value.new(nil).reached_by(access_keys, key)
|
10
|
+
else
|
11
|
+
@value_hash[key].reached_by(access_keys, key)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(access_keys)
|
16
|
+
fetched_keys = access_keys.dup
|
17
|
+
value = self[fetched_keys.shift]
|
18
|
+
fetched_keys.any? ? value.fetch(fetched_keys) : value
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, value)
|
22
|
+
@value_hash[key] = case value
|
23
|
+
when Hash
|
24
|
+
ValueHash.new(value)
|
25
|
+
when Array
|
26
|
+
ValueArray.new(value)
|
27
|
+
else
|
28
|
+
Value.new(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
module Lazy
|
6
|
+
class ValueHash < ValueEnumerable
|
7
|
+
def initialize(hash)
|
8
|
+
super
|
9
|
+
@value_hash = ActiveSupport::HashWithIndifferentAccess.new
|
10
|
+
hash.each do |key, value|
|
11
|
+
self[key] = value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate
|
16
|
+
@value_hash.transform_values(&:evaluate)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
class MediaType
|
6
|
+
attr_reader :type, :subtype, :vendor, :version, :format
|
7
|
+
|
8
|
+
# based on the HTTP Accept header with the pattern:
|
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/.freeze
|
11
|
+
|
12
|
+
def initialize(type:, subtype:)
|
13
|
+
@type = type
|
14
|
+
@subtype = subtype
|
15
|
+
VENDOR_VERSION_HEADER_REGEX.match(subtype) do |m|
|
16
|
+
@vendor = m[:vendor]
|
17
|
+
@version = m[:version]
|
18
|
+
@format = m[:format]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
eql?(other)
|
24
|
+
end
|
25
|
+
|
26
|
+
def eql?(other)
|
27
|
+
self.class == other.class &&
|
28
|
+
other.type == type &&
|
29
|
+
other.subtype == subtype &&
|
30
|
+
other.vendor == vendor &&
|
31
|
+
other.version == version &&
|
32
|
+
other.format == format
|
33
|
+
end
|
34
|
+
|
35
|
+
def hash
|
36
|
+
[self.class, type, subtype, vendor, version, format].hash
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def best_quality(header, available_media_types)
|
41
|
+
parse(best_quality_media_type(header, available_media_types))
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse(media_type)
|
45
|
+
return if media_type.blank?
|
46
|
+
|
47
|
+
type, subtype = media_type.split('/', 2)
|
48
|
+
return if type.blank? || subtype.blank?
|
49
|
+
|
50
|
+
new(type: type, subtype: subtype)
|
51
|
+
end
|
52
|
+
|
53
|
+
def match?(media_type)
|
54
|
+
return false if media_type.blank?
|
55
|
+
|
56
|
+
subtype = media_type.split('/', 2).last
|
57
|
+
return false if subtype.blank?
|
58
|
+
|
59
|
+
VENDOR_VERSION_HEADER_REGEX.match?(subtype)
|
60
|
+
end
|
61
|
+
|
62
|
+
def best_quality_media_type(header, available_media_types)
|
63
|
+
header.blank? ? available_media_types.first : Rack::Utils.best_q_match(header, available_media_types)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private_class_method :best_quality_media_type
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -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
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'stackable_values'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Util
|
7
5
|
class ReverseStackableValues < StackableValues
|
@@ -10,10 +8,7 @@ module Grape
|
|
10
8
|
def concat_values(inherited_value, new_value)
|
11
9
|
return inherited_value unless new_value
|
12
10
|
|
13
|
-
|
14
|
-
value.concat(new_value)
|
15
|
-
value.concat(inherited_value)
|
16
|
-
end
|
11
|
+
new_value + inherited_value
|
17
12
|
end
|
18
13
|
end
|
19
14
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'base_inheritable'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Util
|
7
5
|
class StackableValues < BaseInheritable
|
@@ -31,10 +29,7 @@ module Grape
|
|
31
29
|
def concat_values(inherited_value, new_value)
|
32
30
|
return inherited_value unless new_value
|
33
31
|
|
34
|
-
|
35
|
-
value.concat(inherited_value)
|
36
|
-
value.concat(new_value)
|
37
|
-
end
|
32
|
+
inherited_value + new_value
|
38
33
|
end
|
39
34
|
end
|
40
35
|
end
|
@@ -56,19 +56,19 @@ module Grape
|
|
56
56
|
def self.nested_settings_methods(setting_name, new_config_class)
|
57
57
|
new_config_class.class_eval do
|
58
58
|
setting_name.each_pair do |key, value|
|
59
|
-
define_method "#{key}_context" do
|
59
|
+
define_method :"#{key}_context" do
|
60
60
|
@contexts[key] ||= Grape::Util::StrictHashConfiguration.config_class(*value).new
|
61
61
|
end
|
62
62
|
|
63
63
|
define_method key do |&block|
|
64
|
-
send("#{key}_context").instance_exec(&block)
|
64
|
+
send(:"#{key}_context").instance_exec(&block)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
define_method :to_hash do
|
69
69
|
@settings.to_hash.merge(
|
70
70
|
setting_name.each_key.with_object({}) do |k, merge_hash|
|
71
|
-
merge_hash[k] = send("#{k}_context").to_hash
|
71
|
+
merge_hash[k] = send(:"#{k}_context").to_hash
|
72
72
|
end
|
73
73
|
)
|
74
74
|
end
|