grape 2.0.0 → 2.2.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 +96 -1
- data/README.md +364 -317
- data/UPGRADING.md +205 -7
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +14 -11
- data/lib/grape/api.rb +19 -10
- data/lib/grape/content_types.rb +13 -10
- 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/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +51 -15
- data/lib/grape/dsl/parameters.rb +5 -4
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +43 -35
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/json.rb +13 -4
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/error_formatter.rb +13 -25
- 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 +2 -4
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/formatter.rb +15 -25
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +4 -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 +14 -15
- data/lib/grape/middleware/error.rb +61 -54
- data/lib/grape/middleware/formatter.rb +18 -15
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +4 -5
- data/lib/grape/middleware/versioner/accept_version_header.rb +8 -33
- data/lib/grape/middleware/versioner/header.rb +62 -123
- data/lib/grape/middleware/versioner/param.rb +5 -23
- data/lib/grape/middleware/versioner/path.rb +11 -33
- data/lib/grape/middleware/versioner.rb +5 -14
- data/lib/grape/middleware/versioner_helpers.rb +75 -0
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/parser.rb +8 -24
- 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/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/attributes_iterator.rb +1 -0
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +22 -19
- 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/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +49 -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 -38
- 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/util/registrable.rb +0 -15
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Router
|
5
|
+
class BaseRoute
|
6
|
+
delegate_missing_to :@options
|
7
|
+
|
8
|
+
attr_reader :index, :pattern, :options
|
9
|
+
|
10
|
+
def initialize(**options)
|
11
|
+
@options = ActiveSupport::OrderedOptions.new.update(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
alias attributes options
|
15
|
+
|
16
|
+
def regexp_capture_index
|
17
|
+
CaptureIndexCache[index]
|
18
|
+
end
|
19
|
+
|
20
|
+
def pattern_regexp
|
21
|
+
pattern.to_regexp
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_regexp(index)
|
25
|
+
@index = index
|
26
|
+
Regexp.new("(?<#{regexp_capture_index}>#{pattern_regexp})")
|
27
|
+
end
|
28
|
+
|
29
|
+
class CaptureIndexCache < Grape::Util::Cache
|
30
|
+
def initialize
|
31
|
+
super
|
32
|
+
@cache = Hash.new do |h, index|
|
33
|
+
h[index] = "_#{index}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Act like a Grape::Router::Route but for greedy_match
|
4
|
+
# see @neutral_map
|
5
|
+
|
6
|
+
module Grape
|
7
|
+
class Router
|
8
|
+
class GreedyRoute < BaseRoute
|
9
|
+
def initialize(pattern:, **options)
|
10
|
+
@pattern = pattern
|
11
|
+
super(**options)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Grape::Router:Route defines params as a function
|
15
|
+
def params(_input = nil)
|
16
|
+
options[:params] || {}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/grape/router/pattern.rb
CHANGED
@@ -1,62 +1,71 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'forwardable'
|
4
|
-
require 'mustermann/grape'
|
5
|
-
require 'grape/util/cache'
|
6
|
-
|
7
3
|
module Grape
|
8
4
|
class Router
|
9
5
|
class Pattern
|
10
|
-
|
11
|
-
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
DEFAULT_CAPTURES = %w[format version].freeze
|
12
9
|
|
13
10
|
attr_reader :origin, :path, :pattern, :to_regexp
|
14
11
|
|
15
|
-
extend Forwardable
|
16
12
|
def_delegators :pattern, :named_captures, :params
|
17
13
|
def_delegators :to_regexp, :===
|
18
14
|
alias match? ===
|
19
15
|
|
20
16
|
def initialize(pattern, **options)
|
21
|
-
@origin
|
22
|
-
@path
|
23
|
-
@pattern =
|
17
|
+
@origin = pattern
|
18
|
+
@path = build_path(pattern, anchor: options[:anchor], suffix: options[:suffix])
|
19
|
+
@pattern = build_pattern(@path, options)
|
24
20
|
@to_regexp = @pattern.to_regexp
|
25
21
|
end
|
26
22
|
|
23
|
+
def captures_default
|
24
|
+
to_regexp.names
|
25
|
+
.delete_if { |n| DEFAULT_CAPTURES.include?(n) }
|
26
|
+
.to_h { |k| [k, ''] }
|
27
|
+
end
|
28
|
+
|
27
29
|
private
|
28
30
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
def build_pattern(path, options)
|
32
|
+
Mustermann::Grape.new(
|
33
|
+
path,
|
34
|
+
uri_decode: true,
|
35
|
+
params: options[:params],
|
36
|
+
capture: extract_capture(**options)
|
37
|
+
)
|
34
38
|
end
|
35
39
|
|
36
|
-
def build_path(pattern, anchor: false, suffix: nil
|
37
|
-
|
38
|
-
|
39
|
-
pattern << '/' unless pattern.end_with?('/')
|
40
|
-
pattern << '*path'
|
41
|
-
end
|
40
|
+
def build_path(pattern, anchor: false, suffix: nil)
|
41
|
+
PatternCache[[build_path_from_pattern(pattern, anchor: anchor), suffix]]
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
def extract_capture(**options)
|
45
|
+
sliced_options = options
|
46
|
+
.slice(:format, :version)
|
47
|
+
.delete_if { |_k, v| v.blank? }
|
48
|
+
.transform_values { |v| Array.wrap(v).map(&:to_s) }
|
49
|
+
return sliced_options if options[:requirements].blank?
|
46
50
|
|
47
|
-
|
51
|
+
options[:requirements].merge(sliced_options)
|
48
52
|
end
|
49
53
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
def build_path_from_pattern(pattern, anchor: false)
|
55
|
+
if pattern.end_with?('*path')
|
56
|
+
pattern.dup.insert(pattern.rindex('/') + 1, '?')
|
57
|
+
elsif anchor
|
58
|
+
pattern
|
59
|
+
elsif pattern.end_with?('/')
|
60
|
+
"#{pattern}?*path"
|
61
|
+
else
|
62
|
+
"#{pattern}/?*path"
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
58
66
|
class PatternCache < Grape::Util::Cache
|
59
67
|
def initialize
|
68
|
+
super
|
60
69
|
@cache = Hash.new do |h, (pattern, suffix)|
|
61
70
|
h[[pattern, suffix]] = -"#{pattern}#{suffix}"
|
62
71
|
end
|
data/lib/grape/router/route.rb
CHANGED
@@ -1,57 +1,18 @@
|
|
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
|
-
ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/.freeze
|
12
|
-
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
|
13
|
-
FIXED_NAMED_CAPTURES = %w[format version].freeze
|
14
|
-
|
15
|
-
attr_accessor :pattern, :translator, :app, :index, :options
|
16
|
-
|
17
|
-
alias attributes translator
|
18
|
-
|
5
|
+
class Route < BaseRoute
|
19
6
|
extend Forwardable
|
20
|
-
def_delegators :pattern, :path, :origin
|
21
|
-
delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
|
22
7
|
|
23
|
-
|
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
|
8
|
+
attr_reader :app, :request_method
|
42
9
|
|
43
|
-
|
44
|
-
warn_route_methods(:path, caller(1).shift)
|
45
|
-
pattern.path
|
46
|
-
end
|
10
|
+
def_delegators :pattern, :path, :origin
|
47
11
|
|
48
12
|
def initialize(method, pattern, **options)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
@options = options.merge(method: method_upcase)
|
53
|
-
@pattern = Pattern.new(pattern, **options)
|
54
|
-
@translator = AttributeTranslator.new(**options, request_method: method_upcase)
|
13
|
+
@request_method = upcase_method(method)
|
14
|
+
@pattern = Grape::Router::Pattern.new(pattern, **options)
|
15
|
+
super(**options)
|
55
16
|
end
|
56
17
|
|
57
18
|
def exec(env)
|
@@ -64,27 +25,29 @@ module Grape
|
|
64
25
|
end
|
65
26
|
|
66
27
|
def match?(input)
|
67
|
-
|
28
|
+
return false if input.blank?
|
29
|
+
|
30
|
+
options[:forward_match] ? input.start_with?(pattern.origin) : pattern.match?(input)
|
68
31
|
end
|
69
32
|
|
70
33
|
def params(input = nil)
|
71
|
-
if input.
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
parsed ? parsed.delete_if { |_, value| value.nil? }.symbolize_keys : {}
|
78
|
-
end
|
34
|
+
return params_without_input if input.blank?
|
35
|
+
|
36
|
+
parsed = pattern.params(input)
|
37
|
+
return {} unless parsed
|
38
|
+
|
39
|
+
parsed.compact.symbolize_keys
|
79
40
|
end
|
80
41
|
|
81
42
|
private
|
82
43
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
44
|
+
def params_without_input
|
45
|
+
pattern.captures_default.merge(attributes.params)
|
46
|
+
end
|
47
|
+
|
48
|
+
def upcase_method(method)
|
49
|
+
method_s = method.to_s
|
50
|
+
Grape::Http::Headers::SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
|
88
51
|
end
|
89
52
|
end
|
90
53
|
end
|
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)
|
@@ -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
|