grape 2.0.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -1
- data/README.md +362 -316
- data/UPGRADING.md +197 -7
- data/grape.gemspec +5 -6
- data/lib/grape/api/instance.rb +13 -10
- data/lib/grape/api.rb +17 -8
- data/lib/grape/content_types.rb +0 -2
- data/lib/grape/cookies.rb +2 -1
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/desc.rb +22 -20
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +42 -13
- data/lib/grape/dsl/parameters.rb +4 -3
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +12 -15
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/exceptions/base.rb +3 -3
- data/lib/grape/exceptions/validation.rb +0 -2
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +1 -3
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +3 -0
- data/lib/grape/middleware/auth/base.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +0 -2
- data/lib/grape/middleware/base.rb +0 -2
- data/lib/grape/middleware/error.rb +55 -50
- data/lib/grape/middleware/formatter.rb +16 -13
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +2 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
- data/lib/grape/middleware/versioner/header.rb +17 -163
- data/lib/grape/middleware/versioner/param.rb +2 -4
- data/lib/grape/middleware/versioner/path.rb +1 -3
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/path.rb +24 -29
- data/lib/grape/request.rb +4 -12
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +39 -30
- data/lib/grape/router/route.rb +22 -59
- data/lib/grape/router.rb +32 -37
- data/lib/grape/util/accept/header.rb +19 -0
- data/lib/grape/util/accept_header_handler.rb +105 -0
- data/lib/grape/util/base_inheritable.rb +4 -4
- data/lib/grape/util/cache.rb +0 -3
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_values.rb +0 -2
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/object.rb +45 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +1 -6
- data/lib/grape/util/strict_hash_configuration.rb +3 -3
- data/lib/grape/validations/attributes_doc.rb +38 -36
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +10 -9
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/build_coercer.rb +69 -71
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +0 -2
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +0 -3
- data/lib/grape/validations/validators/base.rb +1 -0
- data/lib/grape/validations/validators/default_validator.rb +5 -1
- data/lib/grape/validations/validators/length_validator.rb +42 -0
- data/lib/grape/validations/validators/values_validator.rb +6 -1
- data/lib/grape/validations.rb +3 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +30 -274
- metadata +31 -37
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
data/lib/grape/router.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
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
|
@@ -15,10 +12,6 @@ module Grape
|
|
15
12
|
path
|
16
13
|
end
|
17
14
|
|
18
|
-
def self.supported_methods
|
19
|
-
@supported_methods ||= Grape::Http::Headers::SUPPORTED_METHODS + ['*']
|
20
|
-
end
|
21
|
-
|
22
15
|
def initialize
|
23
16
|
@neutral_map = []
|
24
17
|
@neutral_regexes = []
|
@@ -31,13 +24,12 @@ module Grape
|
|
31
24
|
|
32
25
|
@union = Regexp.union(@neutral_regexes)
|
33
26
|
@neutral_regexes = nil
|
34
|
-
|
27
|
+
(Grape::Http::Headers::SUPPORTED_METHODS + ['*']).each do |method|
|
28
|
+
next unless map.key?(method)
|
29
|
+
|
35
30
|
routes = map[method]
|
36
|
-
|
37
|
-
|
38
|
-
Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
|
39
|
-
end
|
40
|
-
@optimized_map[method] = Regexp.union(@optimized_map[method])
|
31
|
+
optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
|
32
|
+
@optimized_map[method] = Regexp.union(optimized_map)
|
41
33
|
end
|
42
34
|
@compiled = true
|
43
35
|
end
|
@@ -47,8 +39,10 @@ module Grape
|
|
47
39
|
end
|
48
40
|
|
49
41
|
def associate_routes(pattern, **options)
|
50
|
-
|
51
|
-
|
42
|
+
Grape::Router::GreedyRoute.new(pattern: pattern, **options).then do |greedy_route|
|
43
|
+
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
|
44
|
+
@neutral_map << greedy_route
|
45
|
+
end
|
52
46
|
end
|
53
47
|
|
54
48
|
def call(env)
|
@@ -91,26 +85,33 @@ module Grape
|
|
91
85
|
|
92
86
|
def transaction(env)
|
93
87
|
input, method = *extract_input_and_method(env)
|
94
|
-
response = yield(input, method)
|
95
88
|
|
96
|
-
|
89
|
+
# using a Proc is important since `return` will exit the enclosing function
|
90
|
+
cascade_or_return_response = proc do |response|
|
91
|
+
if response
|
92
|
+
cascade?(response).tap do |cascade|
|
93
|
+
return response unless cascade
|
97
94
|
|
95
|
+
# we need to close the body if possible before dismissing
|
96
|
+
response[2].close if response[2].respond_to?(:close)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
last_response_cascade = cascade_or_return_response.call(yield(input, method))
|
98
102
|
last_neighbor_route = greedy_match?(input)
|
99
103
|
|
100
104
|
# If last_neighbor_route exists and request method is OPTIONS,
|
101
105
|
# return response by using #call_with_allow_headers.
|
102
|
-
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method ==
|
106
|
+
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Rack::OPTIONS && !last_response_cascade
|
103
107
|
|
104
108
|
route = match?(input, '*')
|
105
109
|
|
106
|
-
return last_neighbor_route.endpoint.call(env) if last_neighbor_route &&
|
110
|
+
return last_neighbor_route.endpoint.call(env) if last_neighbor_route && last_response_cascade && route
|
107
111
|
|
108
|
-
if route
|
109
|
-
response = process_route(route, env)
|
110
|
-
return response if response && !(cascade = cascade?(response))
|
111
|
-
end
|
112
|
+
last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
|
112
113
|
|
113
|
-
return call_with_allow_headers(env, last_neighbor_route) if !
|
114
|
+
return call_with_allow_headers(env, last_neighbor_route) if !last_response_cascade && last_neighbor_route
|
114
115
|
|
115
116
|
nil
|
116
117
|
end
|
@@ -122,12 +123,12 @@ module Grape
|
|
122
123
|
|
123
124
|
def make_routing_args(default_args, route, input)
|
124
125
|
args = default_args || { route_info: route }
|
125
|
-
args.merge(route.params(input)
|
126
|
+
args.merge(route.params(input))
|
126
127
|
end
|
127
128
|
|
128
129
|
def extract_input_and_method(env)
|
129
|
-
input = string_for(env[
|
130
|
-
method = env[
|
130
|
+
input = string_for(env[Rack::PATH_INFO])
|
131
|
+
method = env[Rack::REQUEST_METHOD]
|
131
132
|
[input, method]
|
132
133
|
end
|
133
134
|
|
@@ -137,22 +138,16 @@ module Grape
|
|
137
138
|
end
|
138
139
|
|
139
140
|
def default_response
|
140
|
-
|
141
|
+
headers = Grape::Util::Header.new.merge(Grape::Http::Headers::X_CASCADE => 'pass')
|
142
|
+
[404, headers, ['404 Not Found']]
|
141
143
|
end
|
142
144
|
|
143
145
|
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}"] }
|
146
|
+
@optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
|
149
147
|
end
|
150
148
|
|
151
149
|
def greedy_match?(input)
|
152
|
-
|
153
|
-
|
154
|
-
last_match = Regexp.last_match
|
155
|
-
@neutral_map.detect { |route| last_match["_#{route.index}"] }
|
150
|
+
@union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
|
156
151
|
end
|
157
152
|
|
158
153
|
def call_with_allow_headers(env, route)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
module Accept
|
6
|
+
module Header
|
7
|
+
ALLOWED_CHARACTERS = %r{^([a-z*]+)/([a-z0-9*&\^\-_#{$ERROR_INFO}.+]+)(?:;([a-z0-9=;]+))?$}.freeze
|
8
|
+
class << self
|
9
|
+
# Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
|
10
|
+
def parse_media_type(media_type)
|
11
|
+
# see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names
|
12
|
+
m = media_type&.match(ALLOWED_CHARACTERS)
|
13
|
+
m ? [m[1], m[2], m[3] || ''] : []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
class AcceptHeaderHandler
|
6
|
+
attr_reader :accept_header, :versions, :vendor, :strict, :cascade
|
7
|
+
|
8
|
+
def initialize(accept_header:, versions:, **options)
|
9
|
+
@accept_header = accept_header
|
10
|
+
@versions = versions
|
11
|
+
@vendor = options.fetch(:vendor, nil)
|
12
|
+
@strict = options.fetch(:strict, false)
|
13
|
+
@cascade = options.fetch(:cascade, true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_best_quality_media_type!(content_types: Grape::ContentTypes::CONTENT_TYPES, allowed_methods: nil)
|
17
|
+
return unless vendor
|
18
|
+
|
19
|
+
strict_header_checks!
|
20
|
+
media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types(content_types))
|
21
|
+
if media_type
|
22
|
+
yield media_type
|
23
|
+
else
|
24
|
+
fail!(allowed_methods)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def strict_header_checks!
|
31
|
+
return unless strict
|
32
|
+
|
33
|
+
accept_header_check!
|
34
|
+
version_and_vendor_check!
|
35
|
+
end
|
36
|
+
|
37
|
+
def accept_header_check!
|
38
|
+
return if accept_header.present?
|
39
|
+
|
40
|
+
invalid_accept_header!('Accept header must be set.')
|
41
|
+
end
|
42
|
+
|
43
|
+
def version_and_vendor_check!
|
44
|
+
return if versions.blank? || version_and_vendor?
|
45
|
+
|
46
|
+
invalid_accept_header!('API vendor or version not found.')
|
47
|
+
end
|
48
|
+
|
49
|
+
def q_values_mime_types
|
50
|
+
@q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
|
51
|
+
end
|
52
|
+
|
53
|
+
def version_and_vendor?
|
54
|
+
q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def invalid_accept_header!(message)
|
58
|
+
raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
|
59
|
+
end
|
60
|
+
|
61
|
+
def invalid_version_header!(message)
|
62
|
+
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
63
|
+
end
|
64
|
+
|
65
|
+
def fail!(grape_allowed_methods)
|
66
|
+
return grape_allowed_methods if grape_allowed_methods.present?
|
67
|
+
|
68
|
+
media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
|
69
|
+
vendor_not_found!(media_types) || version_not_found!(media_types)
|
70
|
+
end
|
71
|
+
|
72
|
+
def vendor_not_found!(media_types)
|
73
|
+
return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
|
74
|
+
|
75
|
+
invalid_accept_header!('API vendor not found.')
|
76
|
+
end
|
77
|
+
|
78
|
+
def version_not_found!(media_types)
|
79
|
+
return unless media_types.all? { |media_type| media_type&.version && versions.exclude?(media_type.version) }
|
80
|
+
|
81
|
+
invalid_version_header!('API version not found.')
|
82
|
+
end
|
83
|
+
|
84
|
+
def error_headers
|
85
|
+
cascade ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
86
|
+
end
|
87
|
+
|
88
|
+
def available_media_types(content_types)
|
89
|
+
[].tap do |available_media_types|
|
90
|
+
base_media_type = "application/vnd.#{vendor}"
|
91
|
+
content_types.each_key do |extension|
|
92
|
+
versions&.reverse_each do |version|
|
93
|
+
available_media_types << "#{base_media_type}-#{version}+#{extension}"
|
94
|
+
available_media_types << "#{base_media_type}-#{version}"
|
95
|
+
end
|
96
|
+
available_media_types << "#{base_media_type}+#{extension}"
|
97
|
+
end
|
98
|
+
|
99
|
+
available_media_types << base_media_type
|
100
|
+
available_media_types.concat(content_types.values.flatten)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -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,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Based on https://github.com/HornsAndHooves/lazy_object
|
4
|
+
|
5
|
+
module Grape
|
6
|
+
module Util
|
7
|
+
module Lazy
|
8
|
+
class Object < BasicObject
|
9
|
+
attr_reader :callable
|
10
|
+
|
11
|
+
def initialize(&callable)
|
12
|
+
@callable = callable
|
13
|
+
end
|
14
|
+
|
15
|
+
def __target_object__
|
16
|
+
@__target_object__ ||= callable.call
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
__target_object__ == other
|
21
|
+
end
|
22
|
+
|
23
|
+
def !=(other)
|
24
|
+
__target_object__ != other
|
25
|
+
end
|
26
|
+
|
27
|
+
def !
|
28
|
+
!__target_object__
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(method_name, *args, &block)
|
32
|
+
if __target_object__.respond_to?(method_name)
|
33
|
+
__target_object__.send(method_name, *args, &block)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def respond_to_missing?(method_name, include_priv = false)
|
40
|
+
__target_object__.respond_to?(method_name, include_priv)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
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
|
@@ -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
|