grape 2.0.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|