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
@@ -1,87 +1,417 @@
1
- require 'rack'
2
- require 'grape'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Grape
5
4
  # An Endpoint is the proxy scope in which all routing
6
5
  # blocks are executed. In other words, any methods
7
6
  # on the instance level of this class may be called
8
- # from inside a `get`, `post`, etc. block.
7
+ # from inside a `get`, `post`, etc.
9
8
  class Endpoint
10
- def self.generate(&block)
11
- c = Class.new(Grape::Endpoint)
12
- c.class_eval do
13
- @block = block
9
+ extend Forwardable
10
+ include Grape::DSL::Settings
11
+ include Grape::DSL::InsideRoute
12
+
13
+ attr_accessor :block, :source, :options
14
+ attr_reader :env, :request
15
+
16
+ def_delegators :request, :params, :headers, :cookies
17
+ def_delegator :cookies, :response_cookies
18
+
19
+ class << self
20
+ def new(...)
21
+ self == Endpoint ? Class.new(Endpoint).new(...) : super
22
+ end
23
+
24
+ def before_each(new_setup = false, &block)
25
+ @before_each ||= []
26
+ if new_setup == false
27
+ return @before_each unless block
28
+
29
+ @before_each << block
30
+ else
31
+ @before_each = [new_setup]
32
+ end
33
+ end
34
+
35
+ def run_before_each(endpoint)
36
+ superclass.run_before_each(endpoint) unless self == Endpoint
37
+ before_each.each { |blk| blk.try(:call, endpoint) }
38
+ end
39
+
40
+ # @api private
41
+ #
42
+ # Create an UnboundMethod that is appropriate for executing an endpoint
43
+ # route.
44
+ #
45
+ # The unbound method allows explicit calls to +return+ without raising a
46
+ # +LocalJumpError+. The method will be removed, but a +Proc+ reference to
47
+ # it will be returned. The returned +Proc+ expects a single argument: the
48
+ # instance of +Endpoint+ to bind to the method during the call.
49
+ #
50
+ # @param [String, Symbol] method_name
51
+ # @return [Proc]
52
+ # @raise [NameError] an instance method with the same name already exists
53
+ def generate_api_method(method_name, &block)
54
+ raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") if method_defined?(method_name)
55
+
56
+ define_method(method_name, &block)
57
+ method = instance_method(method_name)
58
+ remove_method(method_name)
59
+
60
+ proc do |endpoint_instance|
61
+ ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
62
+ method.bind_call(endpoint_instance)
63
+ end
64
+ end
14
65
  end
15
- c
16
66
  end
17
-
18
- class << self
19
- attr_accessor :block
67
+
68
+ # Create a new endpoint.
69
+ # @param new_settings [InheritableSetting] settings to determine the params,
70
+ # validations, and other properties from.
71
+ # @param options [Hash] attributes of this endpoint
72
+ # @option options path [String or Array] the path to this endpoint, within
73
+ # the current scope.
74
+ # @option options method [String or Array] which HTTP method(s) can be used
75
+ # to reach this endpoint.
76
+ # @option options route_options [Hash]
77
+ # @note This happens at the time of API definition, so in this context the
78
+ # endpoint does not know if it will be mounted under a different endpoint.
79
+ # @yield a block defining what your API should do when this endpoint is hit
80
+ def initialize(new_settings, options = {}, &block)
81
+ require_option(options, :path)
82
+ require_option(options, :method)
83
+
84
+ self.inheritable_setting = new_settings.point_in_time_copy
85
+
86
+ # now +namespace_stackable(:declared_params)+ contains all params defined for
87
+ # this endpoint and its parents, but later it will be cleaned up,
88
+ # see +reset_validations!+ in lib/grape/dsl/validations.rb
89
+ route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
90
+ route_setting(:saved_validations, namespace_stackable(:validations))
91
+
92
+ namespace_stackable(:representations, []) unless namespace_stackable(:representations)
93
+ namespace_inheritable(:default_error_status, 500) unless namespace_inheritable(:default_error_status)
94
+
95
+ @options = options
96
+
97
+ @options[:path] = Array(options[:path])
98
+ @options[:path] << '/' if options[:path].empty?
99
+
100
+ @options[:method] = Array(options[:method])
101
+ @options[:route_options] ||= {}
102
+
103
+ @lazy_initialize_lock = Mutex.new
104
+ @lazy_initialized = nil
105
+ @block = nil
106
+
107
+ @status = nil
108
+ @stream = nil
109
+ @body = nil
110
+ @proc = nil
111
+
112
+ return unless block
113
+
114
+ @source = block
115
+ @block = self.class.generate_api_method(method_name, &block)
20
116
  end
21
-
22
- def self.call(env)
23
- new.call(env)
117
+
118
+ # Update our settings from a given set of stackable parameters. Used when
119
+ # the endpoint's API is mounted under another one.
120
+ def inherit_settings(namespace_stackable)
121
+ parent_validations = namespace_stackable[:validations]
122
+ inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?
123
+ parent_declared_params = namespace_stackable[:declared_params]
124
+ inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?
125
+
126
+ endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
24
127
  end
25
-
26
- attr_reader :env, :request
27
-
28
- # The parameters passed into the request as
29
- # well as parsed from URL segments.
30
- def params
31
- @params ||= request.params.merge(env['rack.routing_args'] || {}).inject({}) do |h,(k,v)|
32
- h[k.to_s] = v
33
- h[k.to_sym] = v
34
- h
35
- end
128
+
129
+ def require_option(options, key)
130
+ raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
36
131
  end
37
-
38
- # The API version as specified in the URL.
39
- def version; env['api.version'] end
40
-
41
- # End the request and display an error to the
42
- # end user with the specified message.
43
- #
44
- # @param message [String] The message to display.
45
- # @param status [Integer] the HTTP Status Code. Defaults to 403.
46
- def error!(message, status=403)
47
- throw :error, :message => message, :status => status
48
- end
49
-
50
- # Set or retrieve the HTTP status code.
51
- #
52
- # @param status [Integer] The HTTP Status Code to return for this request.
53
- def status(status = nil)
54
- if status
55
- @status = status
56
- else
57
- return @status if @status
58
- case request.request_method.to_s.upcase
59
- when 'POST'
60
- 201
61
- else
62
- 200
132
+
133
+ def method_name
134
+ [options[:method],
135
+ Namespace.joined_space(namespace_stackable(:namespace)),
136
+ (namespace_stackable(:mount_path) || []).join('/'),
137
+ options[:path].join('/')]
138
+ .join(' ')
139
+ end
140
+
141
+ def routes
142
+ @routes ||= endpoints&.collect(&:routes)&.flatten || to_routes
143
+ end
144
+
145
+ def reset_routes!
146
+ endpoints&.each(&:reset_routes!)
147
+ @namespace = nil
148
+ @routes = nil
149
+ end
150
+
151
+ def mount_in(router)
152
+ return endpoints.each { |e| e.mount_in(router) } if endpoints
153
+
154
+ reset_routes!
155
+ routes.each do |route|
156
+ router.append(route.apply(self))
157
+ next unless !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
158
+
159
+ route.dup.then do |head_route|
160
+ head_route.convert_to_head_request!
161
+ router.append(head_route.apply(self))
63
162
  end
64
- end
65
- end
66
-
67
- # Set an individual header or retrieve
68
- # all headers that have been set.
69
- def header(key = nil, val = nil)
70
- if key
71
- val ? @header[key.to_s] = val : @header.delete(key.to_s)
72
- else
73
- @header
74
163
  end
75
164
  end
76
-
165
+
166
+ def to_routes
167
+ default_route_options = prepare_default_route_attributes
168
+
169
+ map_routes do |method, raw_path|
170
+ prepared_path = Path.new(raw_path, namespace, prepare_default_path_settings)
171
+ params = options[:route_options].present? ? options[:route_options].merge(default_route_options) : default_route_options
172
+ route = Grape::Router::Route.new(method, prepared_path.origin, prepared_path.suffix, params)
173
+ route.apply(self)
174
+ end.flatten
175
+ end
176
+
177
+ def prepare_routes_requirements
178
+ {}.merge!(*namespace_stackable(:namespace).map(&:requirements)).tap do |requirements|
179
+ endpoint_requirements = options.dig(:route_options, :requirements)
180
+ requirements.merge!(endpoint_requirements) if endpoint_requirements
181
+ end
182
+ end
183
+
184
+ def prepare_default_route_attributes
185
+ {
186
+ namespace: namespace,
187
+ version: prepare_version,
188
+ requirements: prepare_routes_requirements,
189
+ prefix: namespace_inheritable(:root_prefix),
190
+ anchor: options[:route_options].fetch(:anchor, true),
191
+ settings: inheritable_setting.route.except(:declared_params, :saved_validations),
192
+ forward_match: options[:forward_match]
193
+ }
194
+ end
195
+
196
+ def prepare_version
197
+ version = namespace_inheritable(:version)
198
+ return if version.blank?
199
+
200
+ version.length == 1 ? version.first : version
201
+ end
202
+
203
+ def map_routes
204
+ options[:method].map { |method| options[:path].map { |path| yield method, path } }
205
+ end
206
+
207
+ def prepare_default_path_settings
208
+ namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
209
+ namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
210
+ namespace_stackable_hash.merge!(namespace_inheritable_hash)
211
+ end
212
+
213
+ def namespace
214
+ @namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
215
+ end
216
+
77
217
  def call(env)
218
+ lazy_initialize!
219
+ dup.call!(env)
220
+ end
221
+
222
+ def call!(env)
223
+ env[Grape::Env::API_ENDPOINT] = self
78
224
  @env = env
79
- @header = {}
80
- @request = Rack::Request.new(@env)
81
-
82
- response_text = instance_eval &self.class.block
83
-
84
- [status, header, [response_text]]
225
+ @app.call(env)
226
+ end
227
+
228
+ # Return the collection of endpoints within this endpoint.
229
+ # This is the case when an Grape::API mounts another Grape::API.
230
+ def endpoints
231
+ @endpoints ||= options[:app].try(:endpoints)
232
+ end
233
+
234
+ def equals?(endpoint)
235
+ (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
236
+ end
237
+
238
+ # The purpose of this override is solely for stripping internals when an error occurs while calling
239
+ # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
240
+ # Otherwise, it calls super.
241
+ def inspect
242
+ return super unless env
243
+
244
+ "#{self.class} in '#{route.origin}' endpoint"
245
+ end
246
+
247
+ protected
248
+
249
+ def run
250
+ ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
251
+ @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
252
+ begin
253
+ self.class.run_before_each(self)
254
+ run_filters befores, :before
255
+
256
+ if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)
257
+ header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
258
+ raise Grape::Exceptions::MethodNotAllowed.new(header) unless options?
259
+
260
+ header 'Allow', header['Allow']
261
+ response_object = ''
262
+ status 204
263
+ else
264
+ run_filters before_validations, :before_validation
265
+ run_validators validations, request
266
+ run_filters after_validations, :after_validation
267
+ response_object = execute
268
+ end
269
+
270
+ run_filters afters, :after
271
+ build_response_cookies
272
+
273
+ # status verifies body presence when DELETE
274
+ @body ||= response_object
275
+
276
+ # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
277
+ response_object = stream || [body]
278
+
279
+ [status, header, response_object]
280
+ ensure
281
+ run_filters finallies, :finally
282
+ end
283
+ end
284
+ end
285
+
286
+ def execute
287
+ @block&.call(self)
288
+ end
289
+
290
+ def helpers
291
+ lazy_initialize! && @helpers
292
+ end
293
+
294
+ def lazy_initialize!
295
+ return true if @lazy_initialized
296
+
297
+ @lazy_initialize_lock.synchronize do
298
+ return true if @lazy_initialized
299
+
300
+ @helpers = build_helpers&.tap { |mod| self.class.include mod }
301
+ @app = options[:app] || build_stack(@helpers)
302
+
303
+ @lazy_initialized = true
304
+ end
305
+ end
306
+
307
+ def run_validators(validators, request)
308
+ validation_errors = []
309
+
310
+ ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
311
+ validators.each do |validator|
312
+ validator.validate(request)
313
+ rescue Grape::Exceptions::Validation => e
314
+ validation_errors << e
315
+ break if validator.fail_fast?
316
+ rescue Grape::Exceptions::ValidationArrayErrors => e
317
+ validation_errors.concat e.errors
318
+ break if validator.fail_fast?
319
+ end
320
+ end
321
+
322
+ validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header))
323
+ end
324
+
325
+ def run_filters(filters, type = :other)
326
+ ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
327
+ filters&.each { |filter| instance_eval(&filter) }
328
+ end
329
+ post_extension = DSL::InsideRoute.post_filter_methods(type)
330
+ extend post_extension if post_extension
331
+ end
332
+
333
+ %i[befores before_validations after_validations afters finallies].each do |method|
334
+ define_method method do
335
+ namespace_stackable(method)
336
+ end
337
+ end
338
+
339
+ def validations
340
+ return enum_for(:validations) unless block_given?
341
+
342
+ route_setting(:saved_validations)&.each do |saved_validation|
343
+ yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
344
+ end
345
+ end
346
+
347
+ def options?
348
+ options[:options_route_enabled] &&
349
+ env[Rack::REQUEST_METHOD] == Rack::OPTIONS
350
+ end
351
+
352
+ private
353
+
354
+ def build_stack(helpers)
355
+ stack = Grape::Middleware::Stack.new
356
+
357
+ content_types = namespace_stackable_with_hash(:content_types)
358
+ format = namespace_inheritable(:format)
359
+
360
+ stack.use Rack::Head
361
+ stack.use Rack::Lint if lint?
362
+ stack.use Class.new(Grape::Middleware::Error),
363
+ helpers: helpers,
364
+ format: format,
365
+ content_types: content_types,
366
+ default_status: namespace_inheritable(:default_error_status),
367
+ rescue_all: namespace_inheritable(:rescue_all),
368
+ rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
369
+ default_error_formatter: namespace_inheritable(:default_error_formatter),
370
+ error_formatters: namespace_stackable_with_hash(:error_formatters),
371
+ rescue_options: namespace_stackable_with_hash(:rescue_options),
372
+ rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
373
+ base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
374
+ all_rescue_handler: namespace_inheritable(:all_rescue_handler),
375
+ grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
376
+
377
+ stack.concat namespace_stackable(:middleware)
378
+
379
+ if namespace_inheritable(:version).present?
380
+ stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
381
+ versions: namespace_inheritable(:version).flatten,
382
+ version_options: namespace_inheritable(:version_options),
383
+ prefix: namespace_inheritable(:root_prefix),
384
+ mount_path: namespace_stackable(:mount_path).first
385
+ end
386
+
387
+ stack.use Grape::Middleware::Formatter,
388
+ format: format,
389
+ default_format: namespace_inheritable(:default_format) || :txt,
390
+ content_types: content_types,
391
+ formatters: namespace_stackable_with_hash(:formatters),
392
+ parsers: namespace_stackable_with_hash(:parsers)
393
+
394
+ builder = stack.build
395
+ builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
396
+ builder.to_app
397
+ end
398
+
399
+ def build_helpers
400
+ helpers = namespace_stackable(:helpers)
401
+ return if helpers.empty?
402
+
403
+ Module.new { helpers.each { |mod_to_include| include mod_to_include } }
404
+ end
405
+
406
+ def build_response_cookies
407
+ response_cookies do |name, value|
408
+ cookie_value = value.is_a?(Hash) ? value : { value: value }
409
+ Rack::Utils.set_cookie_header! header, name, cookie_value
410
+ end
411
+ end
412
+
413
+ def lint?
414
+ namespace_inheritable(:lint) || Grape.config.lint
85
415
  end
86
416
  end
87
417
  end
data/lib/grape/env.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Env
5
+ API_VERSION = 'api.version'
6
+ API_ENDPOINT = 'api.endpoint'
7
+ API_REQUEST_INPUT = 'api.request.input'
8
+ API_REQUEST_BODY = 'api.request.body'
9
+ API_TYPE = 'api.type'
10
+ API_SUBTYPE = 'api.subtype'
11
+ API_VENDOR = 'api.vendor'
12
+ API_FORMAT = 'api.format'
13
+
14
+ GRAPE_REQUEST = 'grape.request'
15
+ GRAPE_REQUEST_HEADERS = 'grape.request.headers'
16
+ GRAPE_REQUEST_PARAMS = 'grape.request.params'
17
+ GRAPE_ROUTING_ARGS = 'grape.routing_args'
18
+ GRAPE_ALLOWED_METHODS = 'grape.allowed_methods'
19
+ end
20
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class Base
6
+ class << self
7
+ def call(message, backtrace, options = {}, env = nil, original_exception = nil)
8
+ merge_backtrace = backtrace.present? && options.dig(:rescue_options, :backtrace)
9
+ merge_original_exception = original_exception && options.dig(:rescue_options, :original_exception)
10
+
11
+ wrapped_message = wrap_message(present(message, env))
12
+ if wrapped_message.is_a?(Hash)
13
+ wrapped_message[:backtrace] = backtrace if merge_backtrace
14
+ wrapped_message[:original_exception] = original_exception.inspect if merge_original_exception
15
+ end
16
+
17
+ format_structured_message(wrapped_message)
18
+ end
19
+
20
+ def present(message, env)
21
+ present_options = {}
22
+ presented_message = message
23
+ if presented_message.is_a?(Hash)
24
+ presented_message = presented_message.dup
25
+ present_options[:with] = presented_message.delete(:with)
26
+ end
27
+
28
+ presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options)
29
+
30
+ unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?
31
+ # env['api.endpoint'].route does not work when the error occurs within a middleware
32
+ # the Endpoint does not have a valid env at this moment
33
+ http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []
34
+
35
+ found_code = http_codes.find do |http_code|
36
+ (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
37
+ end if env[Grape::Env::API_ENDPOINT].request
38
+
39
+ presenter = found_code[2] if found_code
40
+ end
41
+
42
+ if presenter
43
+ embeds = { env: env }
44
+ embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
45
+ presented_message = presenter.represent(presented_message, embeds).serializable_hash
46
+ end
47
+
48
+ presented_message
49
+ end
50
+
51
+ def wrap_message(message)
52
+ return message if message.is_a?(Hash)
53
+
54
+ { message: message }
55
+ end
56
+
57
+ def format_structured_message(_structured_message)
58
+ raise NotImplementedError
59
+ end
60
+
61
+ def inherited(klass)
62
+ super
63
+ ErrorFormatter.register(klass)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class Json < Base
6
+ class << self
7
+ def format_structured_message(structured_message)
8
+ ::Grape::Json.dump(structured_message)
9
+ end
10
+
11
+ private
12
+
13
+ def wrap_message(message)
14
+ return message if message.is_a?(Hash)
15
+ return message.as_json if message.is_a?(Exceptions::ValidationErrors)
16
+
17
+ { error: ensure_utf8(message) }
18
+ end
19
+
20
+ def ensure_utf8(message)
21
+ return message unless message.respond_to? :encode
22
+
23
+ message.encode('UTF-8', invalid: :replace, undef: :replace)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class SerializableHash < Json; end
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class Txt < Base
6
+ def self.format_structured_message(structured_message)
7
+ message = structured_message[:message] || Grape::Json.dump(structured_message)
8
+ Array.wrap(message).tap do |final_message|
9
+ if structured_message.key?(:backtrace)
10
+ final_message << 'backtrace:'
11
+ final_message.concat(structured_message[:backtrace])
12
+ end
13
+ if structured_message.key?(:original_exception)
14
+ final_message << 'original exception:'
15
+ final_message << structured_message[:original_exception]
16
+ end
17
+ end.join("\r\n ")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class Xml < Base
6
+ def self.format_structured_message(structured_message)
7
+ structured_message.respond_to?(:to_xml) ? structured_message.to_xml(root: :error) : structured_message.to_s
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ extend Grape::Util::Registry
6
+
7
+ module_function
8
+
9
+ def formatter_for(format, error_formatters = nil, default_error_formatter = nil)
10
+ return error_formatters[format] if error_formatters&.key?(format)
11
+
12
+ registry[format] || default_error_formatter || Grape::ErrorFormatter::Txt
13
+ end
14
+ end
15
+ end