grape 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +2 -2
- data/CHANGELOG.md +237 -215
- data/CONTRIBUTING.md +4 -4
- data/README.md +133 -10
- data/RELEASING.md +14 -6
- data/Rakefile +1 -1
- data/UPGRADING.md +23 -23
- data/grape.gemspec +1 -3
- data/lib/grape/api.rb +24 -4
- data/lib/grape/dsl/callbacks.rb +20 -0
- data/lib/grape/dsl/configuration.rb +54 -0
- data/lib/grape/dsl/inside_route.rb +33 -1
- data/lib/grape/dsl/parameters.rb +80 -0
- data/lib/grape/dsl/routing.rb +14 -0
- data/lib/grape/dsl/settings.rb +36 -1
- data/lib/grape/dsl/validations.rb +7 -5
- data/lib/grape/endpoint.rb +42 -32
- data/lib/grape/exceptions/unknown_parameter.rb +10 -0
- data/lib/grape/exceptions/validation_errors.rb +4 -3
- data/lib/grape/http/headers.rb +0 -1
- data/lib/grape/http/request.rb +12 -4
- data/lib/grape/locale/en.yml +1 -0
- data/lib/grape/middleware/base.rb +1 -0
- data/lib/grape/middleware/formatter.rb +39 -23
- data/lib/grape/namespace.rb +13 -2
- data/lib/grape/path.rb +1 -0
- data/lib/grape/route.rb +5 -0
- data/lib/grape/util/file_response.rb +21 -0
- data/lib/grape/util/inheritable_setting.rb +23 -2
- data/lib/grape/util/inheritable_values.rb +1 -1
- data/lib/grape/util/parameter_types.rb +58 -0
- data/lib/grape/util/stackable_values.rb +5 -2
- data/lib/grape/validations/params_scope.rb +83 -9
- data/lib/grape/validations/validators/coerce.rb +11 -2
- data/lib/grape/validations.rb +5 -0
- data/lib/grape/version.rb +2 -1
- data/lib/grape.rb +7 -8
- data/spec/grape/api_spec.rb +63 -0
- data/spec/grape/dsl/inside_route_spec.rb +37 -2
- data/spec/grape/dsl/validations_spec.rb +18 -0
- data/spec/grape/endpoint_spec.rb +83 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
- data/spec/grape/middleware/base_spec.rb +33 -11
- data/spec/grape/middleware/formatter_spec.rb +0 -5
- data/spec/grape/util/inheritable_values_spec.rb +14 -0
- data/spec/grape/util/parameter_types_spec.rb +54 -0
- data/spec/grape/util/stackable_values_spec.rb +10 -0
- data/spec/grape/validations/params_scope_spec.rb +84 -0
- data/spec/grape/validations/validators/coerce_spec.rb +29 -8
- data/spec/grape/validations/validators/values_spec.rb +12 -0
- metadata +9 -6
- data/lib/backports/active_support/deep_dup.rb +0 -49
- data/lib/backports/active_support/duplicable.rb +0 -88
@@ -10,6 +10,9 @@ module Grape
|
|
10
10
|
|
11
11
|
include Grape::DSL::Settings
|
12
12
|
|
13
|
+
# Set or retrive the configured logger. If none was configured, this
|
14
|
+
# method will create a new one, logging to stdout.
|
15
|
+
# @param logger [Object] the new logger to use
|
13
16
|
def logger(logger = nil)
|
14
17
|
if logger
|
15
18
|
global_setting(:logger, logger)
|
@@ -19,6 +22,39 @@ module Grape
|
|
19
22
|
end
|
20
23
|
|
21
24
|
# Add a description to the next namespace or function.
|
25
|
+
# @param description [String] descriptive string for this endpoint
|
26
|
+
# or namespace
|
27
|
+
# @param options [Hash] other properties you can set to describe the
|
28
|
+
# endpoint or namespace. Optional.
|
29
|
+
# @option options :detail [String] additional detail about this endpoint
|
30
|
+
# @option options :params [Hash] param types and info. normally, you set
|
31
|
+
# these via the `params` dsl method.
|
32
|
+
# @option options :entity [Grape::Entity] the entity returned upon a
|
33
|
+
# successful call to this action
|
34
|
+
# @option options :http_codes [Array[Array]] possible HTTP codes this
|
35
|
+
# endpoint may return, with their meanings, in a 2d array
|
36
|
+
# @option options :named [String] a specific name to help find this route
|
37
|
+
# @option options :headers [Hash] HTTP headers this method can accept
|
38
|
+
# @yield a block yielding an instance context with methods mapping to
|
39
|
+
# each of the above, except that :entity is also aliased as #success
|
40
|
+
# and :http_codes is aliased as #failure.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
#
|
44
|
+
# desc 'create a user'
|
45
|
+
# post '/users' do
|
46
|
+
# # ...
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# desc 'find a user' do
|
50
|
+
# detail 'locates the user from the given user ID'
|
51
|
+
# failure [ [404, 'Couldn\'t find the given user' ] ]
|
52
|
+
# success User::Entity
|
53
|
+
# end
|
54
|
+
# get '/user/:id' do
|
55
|
+
# # ...
|
56
|
+
# end
|
57
|
+
#
|
22
58
|
def desc(description, options = {}, &config_block)
|
23
59
|
if block_given?
|
24
60
|
config_class = Grape::DSL::Configuration.desc_container
|
@@ -36,15 +72,33 @@ module Grape
|
|
36
72
|
namespace_setting :description, options
|
37
73
|
route_setting :description, options
|
38
74
|
end
|
75
|
+
|
76
|
+
def description_field(field, value = nil)
|
77
|
+
if value
|
78
|
+
description = route_setting(:description)
|
79
|
+
description ||= route_setting(:description, {})
|
80
|
+
description[field] = value
|
81
|
+
else
|
82
|
+
description = route_setting(:description)
|
83
|
+
description[field] if description
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def unset_description_field(field)
|
88
|
+
description = route_setting(:description)
|
89
|
+
description.delete(field) if description
|
90
|
+
end
|
39
91
|
end
|
40
92
|
|
41
93
|
module_function
|
42
94
|
|
95
|
+
# Merge multiple layers of settings into one hash.
|
43
96
|
def stacked_hash_to_hash(settings)
|
44
97
|
return nil if settings.nil? || settings.blank?
|
45
98
|
settings.each_with_object(ActiveSupport::OrderedHash.new) { |value, result| result.deep_merge!(value) }
|
46
99
|
end
|
47
100
|
|
101
|
+
# Returns an object which configures itself via an instance-context DSL.
|
48
102
|
def desc_container
|
49
103
|
Module.new do
|
50
104
|
include Grape::Util::StrictHashConfiguration.module(
|
@@ -177,12 +177,34 @@ module Grape
|
|
177
177
|
# GET /file # => "contents of file"
|
178
178
|
def file(value = nil)
|
179
179
|
if value
|
180
|
-
@file = value
|
180
|
+
@file = Grape::Util::FileResponse.new(value)
|
181
181
|
else
|
182
182
|
@file
|
183
183
|
end
|
184
184
|
end
|
185
185
|
|
186
|
+
# Allows you to define the response as a streamable object.
|
187
|
+
#
|
188
|
+
# If Content-Length and Transfer-Encoding are blank (among other conditions),
|
189
|
+
# Rack assumes this response can be streamed in chunks.
|
190
|
+
#
|
191
|
+
# @example
|
192
|
+
# get '/stream' do
|
193
|
+
# stream FileStreamer.new(...)
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# GET /stream # => "chunked contents of file"
|
197
|
+
#
|
198
|
+
# See:
|
199
|
+
# * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
|
200
|
+
# * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
|
201
|
+
def stream(value = nil)
|
202
|
+
header 'Content-Length', nil
|
203
|
+
header 'Transfer-Encoding', nil
|
204
|
+
header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
|
205
|
+
file(value)
|
206
|
+
end
|
207
|
+
|
186
208
|
# Allows you to make use of Grape Entities by setting
|
187
209
|
# the response body to the serializable hash of the
|
188
210
|
# entity provided in the `:with` option. This has the
|
@@ -238,6 +260,14 @@ module Grape
|
|
238
260
|
env['rack.routing_args'][:route_info]
|
239
261
|
end
|
240
262
|
|
263
|
+
# Attempt to locate the Entity class for a given object, if not given
|
264
|
+
# explicitly. This is done by looking for the presence of Klass::Entity,
|
265
|
+
# where Klass is the class of the `object` parameter, or one of its
|
266
|
+
# ancestors.
|
267
|
+
# @param object [Object] the object to locate the Entity class for
|
268
|
+
# @param options [Hash]
|
269
|
+
# @option options :with [Class] the explicit entity class to use
|
270
|
+
# @return [Class] the located Entity class, or nil if none is found
|
241
271
|
def entity_class_for_obj(object, options)
|
242
272
|
entity_class = options.delete(:with)
|
243
273
|
|
@@ -259,6 +289,8 @@ module Grape
|
|
259
289
|
entity_class
|
260
290
|
end
|
261
291
|
|
292
|
+
# @return the representation of the given object as done through
|
293
|
+
# the given entity_class.
|
262
294
|
def entity_representation_for(entity_class, object, options)
|
263
295
|
embeds = { env: env }
|
264
296
|
embeds[:version] = env['api.version'] if env['api.version']
|
data/lib/grape/dsl/parameters.rb
CHANGED
@@ -2,6 +2,9 @@ require 'active_support/concern'
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module DSL
|
5
|
+
# Defines DSL methods, meant to be applied to a ParamsScope, which define
|
6
|
+
# and describe the parameters accepted by an endpoint, or all endpoints
|
7
|
+
# within a namespace.
|
5
8
|
module Parameters
|
6
9
|
extend ActiveSupport::Concern
|
7
10
|
|
@@ -39,6 +42,47 @@ module Grape
|
|
39
42
|
alias_method :use_scope, :use
|
40
43
|
alias_method :includes, :use
|
41
44
|
|
45
|
+
# Require one or more parameters for the current endpoint.
|
46
|
+
#
|
47
|
+
# @param attrs list of parameter names, or, if :using is
|
48
|
+
# passed as an option, which keys to include (:all or :none) from
|
49
|
+
# the :using hash. The last key can be a hash, which specifies
|
50
|
+
# options for the parameters
|
51
|
+
# @option attrs :type [Class] the type to coerce this parameter to before
|
52
|
+
# passing it to the endpoint. See Grape::ParameterTypes for supported
|
53
|
+
# types, or use a class that defines `::parse` as a custom type
|
54
|
+
# @option attrs :desc [String] description to document this parameter
|
55
|
+
# @option attrs :default [Object] default value, if parameter is optional
|
56
|
+
# @option attrs :values [Array] permissable values for this field. If any
|
57
|
+
# other value is given, it will be handled as a validation error
|
58
|
+
# @option attrs :using [Hash[Symbol => Hash]] a hash defining keys and
|
59
|
+
# options, like that returned by Grape::Entity#documentation. The value
|
60
|
+
# of each key is an options hash accepting the same parameters
|
61
|
+
# @option attrs :except [Array[Symbol]] a list of keys to exclude from
|
62
|
+
# the :using Hash. The meaning of this depends on if :all or :none was
|
63
|
+
# passed; :all + :except will make the :except fields optional, whereas
|
64
|
+
# :none + :except will make the :except fields required
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
#
|
68
|
+
# params do
|
69
|
+
# # Basic usage: require a parameter of a certain type
|
70
|
+
# requires :user_id, type: Integer
|
71
|
+
#
|
72
|
+
# # You don't need to specify type; String is default
|
73
|
+
# requires :foo
|
74
|
+
#
|
75
|
+
# # Multiple params can be specified at once if they share
|
76
|
+
# # the same options.
|
77
|
+
# requires :x, :y, :z, type: Date
|
78
|
+
#
|
79
|
+
# # Nested parameters can be handled as hashes. You must
|
80
|
+
# # pass in a block, within which you can use any of the
|
81
|
+
# # parameters DSL methods.
|
82
|
+
# requires :user, type: Hash do
|
83
|
+
# requires :name, type: String
|
84
|
+
# end
|
85
|
+
# end
|
42
86
|
def requires(*attrs, &block)
|
43
87
|
orig_attrs = attrs.clone
|
44
88
|
|
@@ -55,6 +99,10 @@ module Grape
|
|
55
99
|
end
|
56
100
|
end
|
57
101
|
|
102
|
+
# Allow, but don't require, one or more parameters for the current
|
103
|
+
# endpoint.
|
104
|
+
# @param (see #requires)
|
105
|
+
# @option (see #requires)
|
58
106
|
def optional(*attrs, &block)
|
59
107
|
orig_attrs = attrs.clone
|
60
108
|
|
@@ -77,24 +125,56 @@ module Grape
|
|
77
125
|
end
|
78
126
|
end
|
79
127
|
|
128
|
+
# Disallow the given parameters to be present in the same request.
|
129
|
+
# @param attrs [*Symbol] parameters to validate
|
80
130
|
def mutually_exclusive(*attrs)
|
81
131
|
validates(attrs, mutual_exclusion: true)
|
82
132
|
end
|
83
133
|
|
134
|
+
# Require exactly one of the given parameters to be present.
|
135
|
+
# @param (see #mutually_exclusive)
|
84
136
|
def exactly_one_of(*attrs)
|
85
137
|
validates(attrs, exactly_one_of: true)
|
86
138
|
end
|
87
139
|
|
140
|
+
# Require at least one of the given parameters to be present.
|
141
|
+
# @param (see #mutually_exclusive)
|
88
142
|
def at_least_one_of(*attrs)
|
89
143
|
validates(attrs, at_least_one_of: true)
|
90
144
|
end
|
91
145
|
|
146
|
+
# Require that either all given params are present, or none are.
|
147
|
+
# @param (see #mutually_exclusive)
|
92
148
|
def all_or_none_of(*attrs)
|
93
149
|
validates(attrs, all_or_none_of: true)
|
94
150
|
end
|
95
151
|
|
152
|
+
# Define a block of validations which should be applied if and only if
|
153
|
+
# the given parameter is present. The parameters are not nested.
|
154
|
+
# @param attr [Symbol] the parameter which, if present, triggers the
|
155
|
+
# validations
|
156
|
+
# @throws Grape::Exceptions::UnknownParameter if `attr` has not been
|
157
|
+
# defined in this scope yet
|
158
|
+
# @yield a parameter definition DSL
|
159
|
+
def given(attr, &block)
|
160
|
+
fail Grape::Exceptions::UnknownParameter.new(attr) unless declared_param?(attr)
|
161
|
+
new_lateral_scope(dependent_on: attr, &block)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Test for whether a certain parameter has been defined in this params
|
165
|
+
# block yet.
|
166
|
+
# @returns [Boolean] whether the parameter has been defined
|
167
|
+
def declared_param?(param)
|
168
|
+
# @declared_params also includes hashes of options and such, but those
|
169
|
+
# won't be flattened out.
|
170
|
+
@declared_params.flatten.include?(param)
|
171
|
+
end
|
172
|
+
|
96
173
|
alias_method :group, :requires
|
97
174
|
|
175
|
+
# @param params [Hash] initial hash of parameters
|
176
|
+
# @return hash of parameters relevant for the current scope
|
177
|
+
# @api private
|
98
178
|
def params(params)
|
99
179
|
params = @parent.params(params) if @parent
|
100
180
|
if @element
|
data/lib/grape/dsl/routing.rb
CHANGED
@@ -135,6 +135,18 @@ module Grape
|
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
+
# Declare a "namespace", which prefixes all subordinate routes with its
|
139
|
+
# name. Any endpoints within a namespace, or group, resource, segment,
|
140
|
+
# etc., will share their parent context as well as any configuration
|
141
|
+
# done in the namespace context.
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
#
|
145
|
+
# namespace :foo do
|
146
|
+
# get 'bar' do
|
147
|
+
# # defines the endpoint: GET /foo/bar
|
148
|
+
# end
|
149
|
+
# end
|
138
150
|
def namespace(space = nil, options = {}, &block)
|
139
151
|
if space || block_given?
|
140
152
|
within_namespace do
|
@@ -162,6 +174,7 @@ module Grape
|
|
162
174
|
@routes ||= prepare_routes
|
163
175
|
end
|
164
176
|
|
177
|
+
# Remove all defined routes.
|
165
178
|
def reset_routes!
|
166
179
|
@routes = nil
|
167
180
|
end
|
@@ -177,6 +190,7 @@ module Grape
|
|
177
190
|
namespace(":#{param}", options, &block)
|
178
191
|
end
|
179
192
|
|
193
|
+
# @return array of defined versions
|
180
194
|
def versions
|
181
195
|
@versions ||= []
|
182
196
|
end
|
data/lib/grape/dsl/settings.rb
CHANGED
@@ -2,24 +2,37 @@ require 'active_support/concern'
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module DSL
|
5
|
+
# Keeps track of settings (impemented as key-value pairs, grouped by
|
6
|
+
# types), in two contexts: top-level settings which apply globally no
|
7
|
+
# matter where they're defined, and inheritable settings which apply only
|
8
|
+
# in the current scope and scopes nested under it.
|
5
9
|
module Settings
|
6
10
|
extend ActiveSupport::Concern
|
7
11
|
|
8
12
|
attr_accessor :inheritable_setting, :top_level_setting
|
9
13
|
|
14
|
+
# Fetch our top-level settings, which apply to all endpoints in the API.
|
10
15
|
def top_level_setting
|
11
16
|
@top_level_setting ||= Grape::Util::InheritableSetting.new
|
12
17
|
end
|
13
18
|
|
19
|
+
# Fetch our current inheritable settings, which are inherited by
|
20
|
+
# nested scopes but not shared across siblings.
|
14
21
|
def inheritable_setting
|
15
22
|
@inheritable_setting ||= Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from top_level_setting }
|
16
23
|
end
|
17
24
|
|
25
|
+
# @param type [Symbol]
|
26
|
+
# @param key [Symbol]
|
18
27
|
def unset(type, key)
|
19
28
|
setting = inheritable_setting.send(type)
|
20
29
|
setting.delete key
|
21
30
|
end
|
22
31
|
|
32
|
+
# @param type [Symbol]
|
33
|
+
# @param key [Symbol]
|
34
|
+
# @param value [Object] will be stored if the value is currently empty
|
35
|
+
# @return either the old value, if it wasn't nil, or the given value
|
23
36
|
def get_or_set(type, key, value)
|
24
37
|
setting = inheritable_setting.send(type)
|
25
38
|
if value.nil?
|
@@ -29,72 +42,94 @@ module Grape
|
|
29
42
|
end
|
30
43
|
end
|
31
44
|
|
45
|
+
# @param key [Symbol]
|
46
|
+
# @param value [Object]
|
47
|
+
# @return (see #get_or_set)
|
32
48
|
def global_setting(key, value = nil)
|
33
49
|
get_or_set :global, key, value
|
34
50
|
end
|
35
51
|
|
52
|
+
# @param key [Symbol]
|
36
53
|
def unset_global_setting(key)
|
37
54
|
unset :global, key
|
38
55
|
end
|
39
56
|
|
57
|
+
# (see #global_setting)
|
40
58
|
def route_setting(key, value = nil)
|
41
59
|
get_or_set :route, key, value
|
42
60
|
end
|
43
61
|
|
62
|
+
# (see #unset_global_setting)
|
44
63
|
def unset_route_setting(key)
|
45
64
|
unset :route, key
|
46
65
|
end
|
47
66
|
|
67
|
+
# (see #global_setting)
|
48
68
|
def namespace_setting(key, value = nil)
|
49
69
|
get_or_set :namespace, key, value
|
50
70
|
end
|
51
71
|
|
72
|
+
# (see #unset_global_setting)
|
52
73
|
def unset_namespace_setting(key)
|
53
74
|
unset :namespace_setting, key
|
54
75
|
end
|
55
76
|
|
77
|
+
# (see #global_setting)
|
56
78
|
def namespace_inheritable(key, value = nil)
|
57
79
|
get_or_set :namespace_inheritable, key, value
|
58
80
|
end
|
59
81
|
|
82
|
+
# (see #unset_global_setting)
|
60
83
|
def unset_namespace_inheritable(key)
|
61
84
|
unset :namespace_inheritable, key
|
62
85
|
end
|
63
86
|
|
87
|
+
# @param key [Symbol]
|
64
88
|
def namespace_inheritable_to_nil(key)
|
65
89
|
inheritable_setting.namespace_inheritable[key] = nil
|
66
90
|
end
|
67
91
|
|
92
|
+
# (see #global_setting)
|
68
93
|
def namespace_stackable(key, value = nil)
|
69
94
|
get_or_set :namespace_stackable, key, value
|
70
95
|
end
|
71
96
|
|
97
|
+
# (see #unset_global_setting)
|
72
98
|
def unset_namespace_stackable(key)
|
73
99
|
unset :namespace_stackable, key
|
74
100
|
end
|
75
101
|
|
102
|
+
# (see #global_setting)
|
76
103
|
def api_class_setting(key, value = nil)
|
77
104
|
get_or_set :api_class, key, value
|
78
105
|
end
|
79
106
|
|
107
|
+
# (see #unset_global_setting)
|
80
108
|
def unset_api_class_setting(key)
|
81
109
|
unset :api_class_setting, key
|
82
110
|
end
|
83
111
|
|
112
|
+
# Fork our inheritable settings to a new instance, copied from our
|
113
|
+
# parent's, but separate so we won't modify it. Every call to this
|
114
|
+
# method should have an answering call to #namespace_end.
|
84
115
|
def namespace_start
|
85
116
|
@inheritable_setting = Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from inheritable_setting }
|
86
117
|
end
|
87
118
|
|
119
|
+
# Set the inheritable settings pointer back up by one level.
|
88
120
|
def namespace_end
|
89
121
|
route_end
|
90
122
|
@inheritable_setting = inheritable_setting.parent
|
91
123
|
end
|
92
124
|
|
125
|
+
# Stop defining settings for the current route and clear them for the
|
126
|
+
# next, within a namespace.
|
93
127
|
def route_end
|
94
128
|
inheritable_setting.route_end
|
95
|
-
reset_validations!
|
96
129
|
end
|
97
130
|
|
131
|
+
# Execute the block within a context where our inheritable settings are forked
|
132
|
+
# to a new copy (see #namespace_start).
|
98
133
|
def within_namespace(&_block)
|
99
134
|
namespace_start
|
100
135
|
|
@@ -8,22 +8,24 @@ module Grape
|
|
8
8
|
include Grape::DSL::Configuration
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
+
# Clears all defined parameters and validations.
|
11
12
|
def reset_validations!
|
12
13
|
unset_namespace_stackable :declared_params
|
13
14
|
unset_namespace_stackable :validations
|
14
15
|
unset_namespace_stackable :params
|
16
|
+
unset_description_field :params
|
15
17
|
end
|
16
18
|
|
19
|
+
# Opens a root-level ParamsScope, defining parameter coercions and
|
20
|
+
# validations for the endpoint.
|
21
|
+
# @yield instance context of the new scope
|
17
22
|
def params(&block)
|
18
23
|
Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
|
19
24
|
end
|
20
25
|
|
21
26
|
def document_attribute(names, opts)
|
22
|
-
|
23
|
-
|
24
|
-
route_setting(:description)[:params] ||= {}
|
25
|
-
|
26
|
-
setting = route_setting(:description)[:params]
|
27
|
+
setting = description_field(:params)
|
28
|
+
setting ||= description_field(:params, {})
|
27
29
|
Array(names).each do |name|
|
28
30
|
setting[name[:full_name].to_s] ||= {}
|
29
31
|
setting[name[:full_name].to_s].merge!(opts)
|
data/lib/grape/endpoint.rb
CHANGED
@@ -41,10 +41,16 @@ module Grape
|
|
41
41
|
if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
|
42
42
|
fail NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
|
43
43
|
end
|
44
|
+
|
44
45
|
define_method(method_name, &block)
|
45
46
|
method = instance_method(method_name)
|
46
47
|
remove_method(method_name)
|
47
|
-
|
48
|
+
|
49
|
+
proc do |endpoint_instance|
|
50
|
+
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
|
51
|
+
method.bind(endpoint_instance).call
|
52
|
+
end
|
53
|
+
end
|
48
54
|
end
|
49
55
|
end
|
50
56
|
|
@@ -210,47 +216,49 @@ module Grape
|
|
210
216
|
protected
|
211
217
|
|
212
218
|
def run(env)
|
213
|
-
|
214
|
-
|
219
|
+
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
|
220
|
+
@env = env
|
221
|
+
@header = {}
|
215
222
|
|
216
|
-
|
217
|
-
|
218
|
-
|
223
|
+
@request = Grape::Request.new(env)
|
224
|
+
@params = @request.params
|
225
|
+
@headers = @request.headers
|
219
226
|
|
220
|
-
|
227
|
+
cookies.read(@request)
|
221
228
|
|
222
|
-
|
229
|
+
self.class.before_each.call(self) if self.class.before_each
|
223
230
|
|
224
|
-
|
231
|
+
run_filters befores, :before
|
225
232
|
|
226
|
-
|
233
|
+
run_filters before_validations, :before_validation
|
227
234
|
|
228
|
-
|
229
|
-
|
235
|
+
# Retrieve validations from this namespace and all parent namespaces.
|
236
|
+
validation_errors = []
|
230
237
|
|
231
|
-
|
238
|
+
# require 'pry-byebug'; binding.pry
|
232
239
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
240
|
+
route_setting(:saved_validations).each do |validator|
|
241
|
+
begin
|
242
|
+
validator.validate!(params)
|
243
|
+
rescue Grape::Exceptions::Validation => e
|
244
|
+
validation_errors << e
|
245
|
+
end
|
238
246
|
end
|
239
|
-
end
|
240
247
|
|
241
|
-
|
242
|
-
|
243
|
-
|
248
|
+
if validation_errors.any?
|
249
|
+
fail Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header
|
250
|
+
end
|
244
251
|
|
245
|
-
|
252
|
+
run_filters after_validations, :after_validation
|
246
253
|
|
247
|
-
|
248
|
-
|
249
|
-
|
254
|
+
response_object = @block ? @block.call(self) : nil
|
255
|
+
run_filters afters, :after
|
256
|
+
cookies.write(header)
|
250
257
|
|
251
|
-
|
252
|
-
|
253
|
-
|
258
|
+
# The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
|
259
|
+
response_object = file || [body || response_object]
|
260
|
+
[status, header, response_object]
|
261
|
+
end
|
254
262
|
end
|
255
263
|
|
256
264
|
def build_middleware
|
@@ -305,9 +313,11 @@ module Grape
|
|
305
313
|
mod
|
306
314
|
end
|
307
315
|
|
308
|
-
def run_filters(filters)
|
309
|
-
(filters
|
310
|
-
|
316
|
+
def run_filters(filters, type = :other)
|
317
|
+
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
|
318
|
+
(filters || []).each do |filter|
|
319
|
+
instance_eval(&filter)
|
320
|
+
end
|
311
321
|
end
|
312
322
|
end
|
313
323
|
|
@@ -13,7 +13,8 @@ module Grape
|
|
13
13
|
@errors[validation_error.params] ||= []
|
14
14
|
@errors[validation_error.params] << validation_error
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
|
+
super message: full_messages.join(', '), status: 400, headers: args[:headers]
|
17
18
|
end
|
18
19
|
|
19
20
|
def each
|
@@ -37,12 +38,12 @@ module Grape
|
|
37
38
|
as_json.to_json
|
38
39
|
end
|
39
40
|
|
40
|
-
private
|
41
|
-
|
42
41
|
def full_messages
|
43
42
|
map { |attributes, error| full_message(attributes, error) }.uniq
|
44
43
|
end
|
45
44
|
|
45
|
+
private
|
46
|
+
|
46
47
|
def full_message(attributes, error)
|
47
48
|
I18n.t(
|
48
49
|
'grape.errors.format'.to_sym,
|
data/lib/grape/http/headers.rb
CHANGED
data/lib/grape/http/request.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
module Grape
|
2
2
|
class Request < Rack::Request
|
3
|
+
ROUTING_ARGS = 'rack.routing_args'
|
4
|
+
HTTP_PREFIX = 'HTTP_'
|
5
|
+
UNDERSCORE = '_'
|
6
|
+
MINUS = '-'
|
7
|
+
|
3
8
|
def params
|
4
9
|
@params ||= begin
|
5
10
|
params = Hashie::Mash.new(super)
|
6
|
-
if env[
|
7
|
-
args = env[
|
11
|
+
if env[ROUTING_ARGS]
|
12
|
+
args = env[ROUTING_ARGS].dup
|
8
13
|
# preserve version from query string parameters
|
9
14
|
args.delete(:version)
|
10
15
|
args.delete(:route_info)
|
@@ -16,8 +21,11 @@ module Grape
|
|
16
21
|
|
17
22
|
def headers
|
18
23
|
@headers ||= env.dup.inject({}) do |h, (k, v)|
|
19
|
-
if k.to_s.start_with?
|
20
|
-
k = k[5..-1]
|
24
|
+
if k.to_s.start_with? HTTP_PREFIX
|
25
|
+
k = k[5..-1]
|
26
|
+
k.tr!(UNDERSCORE, MINUS)
|
27
|
+
k.downcase!
|
28
|
+
k.gsub!(/^.|[-_\s]./, &:upcase!)
|
21
29
|
h[k] = v
|
22
30
|
end
|
23
31
|
h
|
data/lib/grape/locale/en.yml
CHANGED
@@ -28,6 +28,7 @@ en:
|
|
28
28
|
resolution: 'available strategy for :using is :path, :header, :param'
|
29
29
|
unknown_validator: 'unknown validator: %{validator_type}'
|
30
30
|
unknown_options: 'unknown options: %{options}'
|
31
|
+
unknown_parameter: 'unknown parameter: %{param}'
|
31
32
|
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
|
32
33
|
mutual_exclusion: 'are mutually exclusive'
|
33
34
|
at_least_one: 'are missing, at least one parameter must be provided'
|