grape 0.1.1 → 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.
Files changed (197) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1167 -0
  3. data/CONTRIBUTING.md +156 -0
  4. data/LICENSE +1 -1
  5. data/README.md +4192 -0
  6. data/UPGRADING.md +1697 -0
  7. data/grape.gemspec +28 -105
  8. data/grape.png +0 -0
  9. data/lib/grape/api/helpers.rb +9 -0
  10. data/lib/grape/api/instance.rb +247 -0
  11. data/lib/grape/api.rb +158 -194
  12. data/lib/grape/content_types.rb +31 -0
  13. data/lib/grape/cookies.rb +50 -0
  14. data/lib/grape/dry_types.rb +10 -0
  15. data/lib/grape/dsl/api.rb +17 -0
  16. data/lib/grape/dsl/callbacks.rb +69 -0
  17. data/lib/grape/dsl/configuration.rb +15 -0
  18. data/lib/grape/dsl/desc.rb +124 -0
  19. data/lib/grape/dsl/headers.rb +21 -0
  20. data/lib/grape/dsl/helpers.rb +108 -0
  21. data/lib/grape/dsl/inside_route.rb +454 -0
  22. data/lib/grape/dsl/logger.rb +22 -0
  23. data/lib/grape/dsl/middleware.rb +54 -0
  24. data/lib/grape/dsl/parameters.rb +266 -0
  25. data/lib/grape/dsl/request_response.rb +171 -0
  26. data/lib/grape/dsl/routing.rb +248 -0
  27. data/lib/grape/dsl/settings.rb +179 -0
  28. data/lib/grape/dsl/validations.rb +57 -0
  29. data/lib/grape/endpoint.rb +398 -68
  30. data/lib/grape/env.rb +20 -0
  31. data/lib/grape/error_formatter/base.rb +68 -0
  32. data/lib/grape/error_formatter/json.rb +28 -0
  33. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  34. data/lib/grape/error_formatter/txt.rb +21 -0
  35. data/lib/grape/error_formatter/xml.rb +11 -0
  36. data/lib/grape/error_formatter.rb +15 -0
  37. data/lib/grape/exceptions/base.rb +76 -0
  38. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  39. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  40. data/lib/grape/exceptions/incompatible_option_values.rb +11 -0
  41. data/lib/grape/exceptions/invalid_accept_header.rb +11 -0
  42. data/lib/grape/exceptions/invalid_formatter.rb +11 -0
  43. data/lib/grape/exceptions/invalid_message_body.rb +11 -0
  44. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  45. data/lib/grape/exceptions/invalid_response.rb +11 -0
  46. data/lib/grape/exceptions/invalid_version_header.rb +11 -0
  47. data/lib/grape/exceptions/invalid_versioner_option.rb +11 -0
  48. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +11 -0
  49. data/lib/grape/exceptions/method_not_allowed.rb +11 -0
  50. data/lib/grape/exceptions/missing_group_type.rb +13 -0
  51. data/lib/grape/exceptions/missing_mime_type.rb +11 -0
  52. data/lib/grape/exceptions/missing_option.rb +11 -0
  53. data/lib/grape/exceptions/missing_vendor_option.rb +11 -0
  54. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  55. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  56. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  57. data/lib/grape/exceptions/unknown_options.rb +11 -0
  58. data/lib/grape/exceptions/unknown_parameter.rb +11 -0
  59. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  60. data/lib/grape/exceptions/unknown_validator.rb +11 -0
  61. data/lib/grape/exceptions/unsupported_group_type.rb +13 -0
  62. data/lib/grape/exceptions/validation.rb +25 -0
  63. data/lib/grape/exceptions/validation_array_errors.rb +14 -0
  64. data/lib/grape/exceptions/validation_errors.rb +53 -0
  65. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +24 -0
  66. data/lib/grape/extensions/hash.rb +27 -0
  67. data/lib/grape/extensions/hashie/mash.rb +24 -0
  68. data/lib/grape/formatter/base.rb +16 -0
  69. data/lib/grape/formatter/json.rb +13 -0
  70. data/lib/grape/formatter/serializable_hash.rb +39 -0
  71. data/lib/grape/formatter/txt.rb +11 -0
  72. data/lib/grape/formatter/xml.rb +13 -0
  73. data/lib/grape/formatter.rb +17 -0
  74. data/lib/grape/json.rb +10 -0
  75. data/lib/grape/locale/en.yml +59 -0
  76. data/lib/grape/middleware/auth/base.rb +23 -0
  77. data/lib/grape/middleware/auth/dsl.rb +39 -0
  78. data/lib/grape/middleware/auth/strategies.rb +25 -0
  79. data/lib/grape/middleware/auth/strategy_info.rb +15 -0
  80. data/lib/grape/middleware/base.rb +89 -18
  81. data/lib/grape/middleware/error.rb +129 -10
  82. data/lib/grape/middleware/filter.rb +19 -0
  83. data/lib/grape/middleware/formatter.rb +124 -82
  84. data/lib/grape/middleware/globals.rb +14 -0
  85. data/lib/grape/middleware/stack.rb +109 -0
  86. data/lib/grape/middleware/versioner/accept_version_header.rb +38 -0
  87. data/lib/grape/middleware/versioner/base.rb +74 -0
  88. data/lib/grape/middleware/versioner/header.rb +127 -0
  89. data/lib/grape/middleware/versioner/param.rb +32 -0
  90. data/lib/grape/middleware/versioner/path.rb +40 -0
  91. data/lib/grape/middleware/versioner.rb +21 -21
  92. data/lib/grape/namespace.rb +46 -0
  93. data/lib/grape/params_builder/base.rb +18 -0
  94. data/lib/grape/params_builder/hash.rb +11 -0
  95. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  96. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  97. data/lib/grape/params_builder.rb +32 -0
  98. data/lib/grape/parser/base.rb +16 -0
  99. data/lib/grape/parser/json.rb +14 -0
  100. data/lib/grape/parser/xml.rb +14 -0
  101. data/lib/grape/parser.rb +15 -0
  102. data/lib/grape/path.rb +76 -0
  103. data/lib/grape/presenters/presenter.rb +11 -0
  104. data/lib/grape/railtie.rb +9 -0
  105. data/lib/grape/request.rb +190 -0
  106. data/lib/grape/router/base_route.rb +39 -0
  107. data/lib/grape/router/greedy_route.rb +20 -0
  108. data/lib/grape/router/pattern.rb +81 -0
  109. data/lib/grape/router/route.rb +62 -0
  110. data/lib/grape/router.rb +190 -0
  111. data/lib/grape/serve_stream/file_body.rb +36 -0
  112. data/lib/grape/serve_stream/sendfile_response.rb +21 -0
  113. data/lib/grape/serve_stream/stream_response.rb +23 -0
  114. data/lib/grape/types/invalid_value.rb +8 -0
  115. data/lib/grape/util/base_inheritable.rb +43 -0
  116. data/lib/grape/util/cache.rb +17 -0
  117. data/lib/grape/util/endpoint_configuration.rb +8 -0
  118. data/lib/grape/util/header.rb +13 -0
  119. data/lib/grape/util/inheritable_setting.rb +100 -0
  120. data/lib/grape/util/inheritable_values.rb +29 -0
  121. data/lib/grape/util/lazy/block.rb +29 -0
  122. data/lib/grape/util/lazy/value.rb +38 -0
  123. data/lib/grape/util/lazy/value_array.rb +21 -0
  124. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  125. data/lib/grape/util/lazy/value_hash.rb +21 -0
  126. data/lib/grape/util/media_type.rb +70 -0
  127. data/lib/grape/util/registry.rb +27 -0
  128. data/lib/grape/util/reverse_stackable_values.rb +15 -0
  129. data/lib/grape/util/stackable_values.rb +36 -0
  130. data/lib/grape/util/strict_hash_configuration.rb +108 -0
  131. data/lib/grape/validations/attributes_doc.rb +60 -0
  132. data/lib/grape/validations/attributes_iterator.rb +62 -0
  133. data/lib/grape/validations/contract_scope.rb +34 -0
  134. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  135. data/lib/grape/validations/params_scope.rb +538 -0
  136. data/lib/grape/validations/single_attribute_iterator.rb +26 -0
  137. data/lib/grape/validations/types/array_coercer.rb +61 -0
  138. data/lib/grape/validations/types/custom_type_coercer.rb +164 -0
  139. data/lib/grape/validations/types/custom_type_collection_coercer.rb +56 -0
  140. data/lib/grape/validations/types/dry_type_coercer.rb +66 -0
  141. data/lib/grape/validations/types/file.rb +31 -0
  142. data/lib/grape/validations/types/invalid_value.rb +17 -0
  143. data/lib/grape/validations/types/json.rb +71 -0
  144. data/lib/grape/validations/types/multiple_type_coercer.rb +57 -0
  145. data/lib/grape/validations/types/primitive_coercer.rb +73 -0
  146. data/lib/grape/validations/types/set_coercer.rb +35 -0
  147. data/lib/grape/validations/types/variant_collection_coercer.rb +51 -0
  148. data/lib/grape/validations/types.rb +213 -0
  149. data/lib/grape/validations/validator_factory.rb +15 -0
  150. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  151. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  152. data/lib/grape/validations/validators/as_validator.rb +14 -0
  153. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  154. data/lib/grape/validations/validators/base.rb +88 -0
  155. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  156. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  157. data/lib/grape/validations/validators/default_validator.rb +37 -0
  158. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  159. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  160. data/lib/grape/validations/validators/length_validator.rb +49 -0
  161. data/lib/grape/validations/validators/multiple_params_base.rb +34 -0
  162. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  163. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  164. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  165. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  166. data/lib/grape/validations/validators/values_validator.rb +60 -0
  167. data/lib/grape/validations.rb +21 -0
  168. data/lib/grape/version.rb +6 -0
  169. data/lib/grape/xml.rb +10 -0
  170. data/lib/grape.rb +81 -17
  171. metadata +250 -192
  172. data/.document +0 -5
  173. data/.gitignore +0 -23
  174. data/.rspec +0 -2
  175. data/.rvmrc +0 -1
  176. data/Gemfile +0 -21
  177. data/Gemfile.lock +0 -58
  178. data/README.markdown +0 -75
  179. data/Rakefile +0 -70
  180. data/VERSION +0 -1
  181. data/autotest/discover.rb +0 -1
  182. data/lib/grape/middleware/auth/basic.rb +0 -30
  183. data/lib/grape/middleware/auth/oauth2.rb +0 -55
  184. data/lib/grape/middleware/prefixer.rb +0 -21
  185. data/lib/grape/middleware_stack.rb +0 -35
  186. data/spec/grape/api_spec.rb +0 -343
  187. data/spec/grape/endpoint_spec.rb +0 -104
  188. data/spec/grape/middleware/auth/basic_spec.rb +0 -31
  189. data/spec/grape/middleware/auth/oauth2_spec.rb +0 -88
  190. data/spec/grape/middleware/base_spec.rb +0 -63
  191. data/spec/grape/middleware/error_spec.rb +0 -49
  192. data/spec/grape/middleware/formatter_spec.rb +0 -87
  193. data/spec/grape/middleware/prefixer_spec.rb +0 -30
  194. data/spec/grape/middleware/versioner_spec.rb +0 -40
  195. data/spec/grape/middleware_stack_spec.rb +0 -47
  196. data/spec/grape_spec.rb +0 -1
  197. data/spec/spec_helper.rb +0 -20
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ class Router
5
+ class Route < BaseRoute
6
+ extend Forwardable
7
+
8
+ FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
9
+ NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
10
+
11
+ attr_reader :app, :request_method
12
+
13
+ def_delegators :pattern, :path, :origin
14
+
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)
20
+ end
21
+
22
+ def convert_to_head_request!
23
+ @request_method = Rack::HEAD
24
+ end
25
+
26
+ def exec(env)
27
+ @app.call(env)
28
+ end
29
+
30
+ def apply(app)
31
+ @app = app
32
+ self
33
+ end
34
+
35
+ def match?(input)
36
+ return false if input.blank?
37
+
38
+ @match_function.call(input, pattern)
39
+ end
40
+
41
+ def params(input = nil)
42
+ return params_without_input if input.blank?
43
+
44
+ parsed = pattern.params(input)
45
+ return {} unless parsed
46
+
47
+ parsed.compact.symbolize_keys
48
+ end
49
+
50
+ private
51
+
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
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ class Router
5
+ attr_reader :map, :compiled
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
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
22
+ path = +"/#{path}"
23
+ path.squeeze!('/')
24
+
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)
31
+ end
32
+
33
+ def initialize
34
+ @neutral_map = []
35
+ @neutral_regexes = []
36
+ @map = Hash.new { |hash, key| hash[key] = [] }
37
+ @optimized_map = Hash.new { |hash, key| hash[key] = // }
38
+ end
39
+
40
+ def compile!
41
+ return if compiled
42
+
43
+ @union = Regexp.union(@neutral_regexes)
44
+ @neutral_regexes = nil
45
+ (Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
46
+ next unless map.key?(method)
47
+
48
+ routes = map[method]
49
+ optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
50
+ @optimized_map[method] = Regexp.union(optimized_map)
51
+ end
52
+ @compiled = true
53
+ end
54
+
55
+ def append(route)
56
+ map[route.request_method] << route
57
+ end
58
+
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
64
+ end
65
+
66
+ def call(env)
67
+ with_optimization do
68
+ response, route = identity(env)
69
+ response || rotation(env, route)
70
+ end
71
+ end
72
+
73
+ def recognize_path(input)
74
+ any = with_optimization { greedy_match?(input) }
75
+ return if any == default_response
76
+
77
+ any.endpoint
78
+ end
79
+
80
+ private
81
+
82
+ def identity(env)
83
+ route = nil
84
+ response = transaction(env) do |input, method|
85
+ route = match?(input, method)
86
+ process_route(route, env) if route
87
+ end
88
+ [response, route]
89
+ end
90
+
91
+ def rotation(env, exact_route = nil)
92
+ response = nil
93
+ input, method = *extract_input_and_method(env)
94
+ map[method].each do |route|
95
+ next if exact_route == route
96
+ next unless route.match?(input)
97
+
98
+ response = process_route(route, env)
99
+ break unless cascade?(response)
100
+ end
101
+ response
102
+ end
103
+
104
+ def transaction(env)
105
+ input, method = *extract_input_and_method(env)
106
+
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
118
+
119
+ last_response_cascade = cascade_or_return_response.call(yield(input, method))
120
+ last_neighbor_route = greedy_match?(input)
121
+
122
+ # If last_neighbor_route exists and request method is OPTIONS,
123
+ # return response by using #call_with_allow_headers.
124
+ return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Rack::OPTIONS && !last_response_cascade
125
+
126
+ route = match?(input, '*')
127
+
128
+ return last_neighbor_route.options[:endpoint].call(env) if last_neighbor_route && last_response_cascade && route
129
+
130
+ last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
131
+
132
+ return call_with_allow_headers(env, last_neighbor_route) if !last_response_cascade && last_neighbor_route
133
+
134
+ nil
135
+ end
136
+
137
+ def process_route(route, env)
138
+ prepare_env_from_route(env, route)
139
+ route.exec(env)
140
+ end
141
+
142
+ def make_routing_args(default_args, route, input)
143
+ args = default_args || { route_info: route }
144
+ args.merge(route.params(input))
145
+ end
146
+
147
+ def extract_input_and_method(env)
148
+ input = string_for(env[Rack::PATH_INFO])
149
+ method = env[Rack::REQUEST_METHOD]
150
+ [input, method]
151
+ end
152
+
153
+ def with_optimization
154
+ compile! unless compiled
155
+ yield || default_response
156
+ end
157
+
158
+ def default_response
159
+ headers = Grape::Util::Header.new.merge('X-Cascade' => 'pass')
160
+ [404, headers, ['404 Not Found']]
161
+ end
162
+
163
+ def match?(input, method)
164
+ @optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
165
+ end
166
+
167
+ def greedy_match?(input)
168
+ @union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
169
+ end
170
+
171
+ def call_with_allow_headers(env, route)
172
+ prepare_env_from_route(env, route)
173
+ env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
174
+ route.options[:endpoint].call(env)
175
+ end
176
+
177
+ def prepare_env_from_route(env, route)
178
+ input, = *extract_input_and_method(env)
179
+ env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
180
+ end
181
+
182
+ def cascade?(response)
183
+ response && response[1]['X-Cascade'] == 'pass'
184
+ end
185
+
186
+ def string_for(input)
187
+ self.class.normalize_path(input)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ServeStream
5
+ CHUNK_SIZE = 16_384
6
+
7
+ # Class helps send file through API
8
+ class FileBody
9
+ attr_reader :path
10
+
11
+ # @param path [String]
12
+ def initialize(path)
13
+ @path = path
14
+ end
15
+
16
+ # Need for Rack::Sendfile middleware
17
+ #
18
+ # @return [String]
19
+ def to_path
20
+ path
21
+ end
22
+
23
+ def each
24
+ File.open(path, 'rb') do |file|
25
+ while (chunk = file.read(CHUNK_SIZE))
26
+ yield chunk
27
+ end
28
+ end
29
+ end
30
+
31
+ def ==(other)
32
+ path == other.path
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ServeStream
5
+ # Response should respond to to_path method
6
+ # for using Rack::SendFile middleware
7
+ class SendfileResponse < Rack::Response
8
+ def respond_to?(method_name, include_all = false)
9
+ if method_name == :to_path
10
+ @body.respond_to?(:to_path, include_all)
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def to_path
17
+ @body.to_path
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ServeStream
5
+ # A simple class used to identify responses which represent streams (or files) and do not
6
+ # need to be formatted or pre-read by Rack::Response
7
+ class StreamResponse
8
+ attr_reader :stream
9
+
10
+ # @param stream [Object]
11
+ def initialize(stream)
12
+ @stream = stream
13
+ end
14
+
15
+ # Equality provided mostly for tests.
16
+ #
17
+ # @return [Boolean]
18
+ def ==(other)
19
+ stream == other.stream
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # only exists to make it shorter for external use
4
+ module Grape
5
+ module Types
6
+ InvalidValue = Class.new(Grape::Validations::Types::InvalidValue)
7
+ end
8
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ # Base for classes which need to operate with own values kept
6
+ # in the hash and inherited values kept in a Hash-like object.
7
+ class BaseInheritable
8
+ attr_accessor :inherited_values, :new_values
9
+
10
+ # @param inherited_values [Object] An object implementing an interface
11
+ # of the Hash class.
12
+ def initialize(inherited_values = nil)
13
+ @inherited_values = inherited_values || {}
14
+ @new_values = {}
15
+ end
16
+
17
+ def delete(key)
18
+ new_values.delete key
19
+ end
20
+
21
+ def initialize_copy(other)
22
+ super
23
+ self.inherited_values = other.inherited_values
24
+ self.new_values = other.new_values.dup
25
+ end
26
+
27
+ def keys
28
+ if new_values.any?
29
+ inherited_values.keys.tap do |combined|
30
+ combined.concat(new_values.keys)
31
+ combined.uniq!
32
+ end
33
+ else
34
+ inherited_values.keys
35
+ end
36
+ end
37
+
38
+ def key?(name)
39
+ inherited_values.key?(name) || new_values.key?(name)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ class Cache
6
+ include Singleton
7
+
8
+ attr_reader :cache
9
+
10
+ class << self
11
+ extend Forwardable
12
+ def_delegators :cache, :[]
13
+ def_delegators :instance, :cache
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ class EndpointConfiguration < Lazy::ValueHash
6
+ end
7
+ end
8
+ end
@@ -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,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ # A branchable, inheritable settings object which can store both stackable
6
+ # and inheritable values (see InheritableValues and StackableValues).
7
+ class InheritableSetting
8
+ attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent, :point_in_time_copies
9
+
10
+ # Retrieve global settings.
11
+ def self.global
12
+ @global ||= {}
13
+ end
14
+
15
+ # Clear all global settings.
16
+ # @api private
17
+ # @note only for testing
18
+ def self.reset_global!
19
+ @global = {}
20
+ end
21
+
22
+ # Instantiate a new settings instance, with blank values. The fresh
23
+ # instance can then be set to inherit from an existing instance (see
24
+ # #inherit_from).
25
+ def initialize
26
+ self.route = {}
27
+ self.api_class = {}
28
+ self.namespace = InheritableValues.new # only inheritable from a parent when
29
+ # used with a mount, or should every API::Class be a separate namespace by default?
30
+ self.namespace_inheritable = InheritableValues.new
31
+ self.namespace_stackable = StackableValues.new
32
+ self.namespace_reverse_stackable = ReverseStackableValues.new
33
+
34
+ self.point_in_time_copies = []
35
+
36
+ self.parent = nil
37
+ end
38
+
39
+ # Return the class-level global properties.
40
+ def global
41
+ self.class.global
42
+ end
43
+
44
+ # Set our inherited values to the given parent's current values. Also,
45
+ # update the inherited values on any settings instances which were forked
46
+ # from us.
47
+ # @param parent [InheritableSetting]
48
+ def inherit_from(parent)
49
+ return if parent.nil?
50
+
51
+ self.parent = parent
52
+
53
+ namespace_inheritable.inherited_values = parent.namespace_inheritable
54
+ namespace_stackable.inherited_values = parent.namespace_stackable
55
+ namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable
56
+ self.route = parent.route.merge(route)
57
+
58
+ point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent }
59
+ end
60
+
61
+ # Create a point-in-time copy of this settings instance, with clones of
62
+ # all our values. Note that, should this instance's parent be set or
63
+ # changed via #inherit_from, it will copy that inheritence to any copies
64
+ # which were made.
65
+ def point_in_time_copy
66
+ self.class.new.tap do |new_setting|
67
+ point_in_time_copies << new_setting
68
+ new_setting.point_in_time_copies = []
69
+
70
+ new_setting.namespace = namespace.clone
71
+ new_setting.namespace_inheritable = namespace_inheritable.clone
72
+ new_setting.namespace_stackable = namespace_stackable.clone
73
+ new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone
74
+ new_setting.route = route.clone
75
+ new_setting.api_class = api_class
76
+
77
+ new_setting.inherit_from(parent)
78
+ end
79
+ end
80
+
81
+ # Resets the instance store of per-route settings.
82
+ # @api private
83
+ def route_end
84
+ @route = {}
85
+ end
86
+
87
+ # Return a serializable hash of our values.
88
+ def to_hash
89
+ {
90
+ global: global.clone,
91
+ route: route.clone,
92
+ namespace: namespace.to_hash,
93
+ namespace_inheritable: namespace_inheritable.to_hash,
94
+ namespace_stackable: namespace_stackable.to_hash,
95
+ namespace_reverse_stackable: namespace_reverse_stackable.to_hash
96
+ }
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ class InheritableValues < BaseInheritable
6
+ def [](name)
7
+ values[name]
8
+ end
9
+
10
+ def []=(name, value)
11
+ new_values[name] = value
12
+ end
13
+
14
+ def merge(new_hash)
15
+ values.merge!(new_hash)
16
+ end
17
+
18
+ def to_hash
19
+ values
20
+ end
21
+
22
+ protected
23
+
24
+ def values
25
+ @inherited_values.merge(@new_values)
26
+ end
27
+ end
28
+ end
29
+ 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