grape 2.0.0 → 2.1.1
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 +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
|