grape 0.12.0 → 0.14.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.

Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +265 -215
  4. data/CONTRIBUTING.md +4 -4
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +166 -0
  7. data/README.md +426 -161
  8. data/RELEASING.md +14 -6
  9. data/Rakefile +30 -33
  10. data/UPGRADING.md +54 -23
  11. data/benchmark/simple.rb +27 -0
  12. data/gemfiles/rack_1.5.2.gemfile +13 -0
  13. data/gemfiles/rails_3.gemfile +2 -2
  14. data/gemfiles/rails_4.gemfile +1 -2
  15. data/grape.gemspec +6 -7
  16. data/lib/grape/api.rb +24 -4
  17. data/lib/grape/dsl/callbacks.rb +20 -0
  18. data/lib/grape/dsl/configuration.rb +59 -2
  19. data/lib/grape/dsl/helpers.rb +8 -3
  20. data/lib/grape/dsl/inside_route.rb +100 -45
  21. data/lib/grape/dsl/parameters.rb +96 -7
  22. data/lib/grape/dsl/request_response.rb +1 -1
  23. data/lib/grape/dsl/routing.rb +17 -4
  24. data/lib/grape/dsl/settings.rb +36 -1
  25. data/lib/grape/dsl/validations.rb +7 -5
  26. data/lib/grape/endpoint.rb +102 -57
  27. data/lib/grape/error_formatter/base.rb +6 -6
  28. data/lib/grape/exceptions/base.rb +5 -5
  29. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  30. data/lib/grape/exceptions/unknown_parameter.rb +10 -0
  31. data/lib/grape/exceptions/validation_errors.rb +4 -3
  32. data/lib/grape/formatter/serializable_hash.rb +3 -2
  33. data/lib/grape/http/headers.rb +0 -1
  34. data/lib/grape/locale/en.yml +5 -1
  35. data/lib/grape/middleware/auth/base.rb +2 -2
  36. data/lib/grape/middleware/auth/dsl.rb +1 -1
  37. data/lib/grape/middleware/auth/strategies.rb +1 -1
  38. data/lib/grape/middleware/base.rb +8 -4
  39. data/lib/grape/middleware/error.rb +3 -2
  40. data/lib/grape/middleware/filter.rb +1 -1
  41. data/lib/grape/middleware/formatter.rb +64 -45
  42. data/lib/grape/middleware/globals.rb +3 -3
  43. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  44. data/lib/grape/middleware/versioner/header.rb +113 -50
  45. data/lib/grape/middleware/versioner/param.rb +5 -8
  46. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  47. data/lib/grape/middleware/versioner/path.rb +3 -6
  48. data/lib/grape/namespace.rb +13 -2
  49. data/lib/grape/path.rb +4 -3
  50. data/lib/grape/request.rb +40 -0
  51. data/lib/grape/route.rb +5 -0
  52. data/lib/grape/util/content_types.rb +9 -9
  53. data/lib/grape/util/env.rb +22 -0
  54. data/lib/grape/util/file_response.rb +21 -0
  55. data/lib/grape/util/inheritable_setting.rb +23 -2
  56. data/lib/grape/util/inheritable_values.rb +1 -1
  57. data/lib/grape/util/stackable_values.rb +5 -2
  58. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  59. data/lib/grape/validations/attributes_iterator.rb +8 -3
  60. data/lib/grape/validations/params_scope.rb +164 -22
  61. data/lib/grape/validations/types/build_coercer.rb +53 -0
  62. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  63. data/lib/grape/validations/types/file.rb +28 -0
  64. data/lib/grape/validations/types/json.rb +65 -0
  65. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  66. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  67. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  68. data/lib/grape/validations/types.rb +144 -0
  69. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  70. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  71. data/lib/grape/validations/validators/base.rb +7 -0
  72. data/lib/grape/validations/validators/coerce.rb +32 -34
  73. data/lib/grape/validations/validators/presence.rb +2 -3
  74. data/lib/grape/validations/validators/regexp.rb +2 -4
  75. data/lib/grape/validations/validators/values.rb +3 -3
  76. data/lib/grape/validations.rb +5 -0
  77. data/lib/grape/version.rb +2 -1
  78. data/lib/grape.rb +15 -12
  79. data/pkg/grape-0.13.0.gem +0 -0
  80. data/spec/grape/api/custom_validations_spec.rb +5 -4
  81. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  82. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  83. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  84. data/spec/grape/api_spec.rb +151 -54
  85. data/spec/grape/dsl/configuration_spec.rb +13 -0
  86. data/spec/grape/dsl/helpers_spec.rb +16 -2
  87. data/spec/grape/dsl/inside_route_spec.rb +40 -4
  88. data/spec/grape/dsl/parameters_spec.rb +0 -6
  89. data/spec/grape/dsl/routing_spec.rb +1 -1
  90. data/spec/grape/dsl/validations_spec.rb +18 -0
  91. data/spec/grape/endpoint_spec.rb +130 -6
  92. data/spec/grape/entity_spec.rb +10 -8
  93. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  94. data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
  95. data/spec/grape/integration/rack_spec.rb +3 -2
  96. data/spec/grape/middleware/base_spec.rb +40 -16
  97. data/spec/grape/middleware/error_spec.rb +16 -15
  98. data/spec/grape/middleware/exception_spec.rb +45 -43
  99. data/spec/grape/middleware/formatter_spec.rb +34 -5
  100. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  101. data/spec/grape/path_spec.rb +10 -10
  102. data/spec/grape/presenters/presenter_spec.rb +2 -2
  103. data/spec/grape/request_spec.rb +100 -0
  104. data/spec/grape/util/inheritable_values_spec.rb +14 -0
  105. data/spec/grape/util/stackable_values_spec.rb +10 -0
  106. data/spec/grape/validations/params_scope_spec.rb +86 -0
  107. data/spec/grape/validations/types_spec.rb +95 -0
  108. data/spec/grape/validations/validators/coerce_spec.rb +364 -10
  109. data/spec/grape/validations/validators/values_spec.rb +27 -15
  110. data/spec/grape/validations_spec.rb +53 -24
  111. data/spec/shared/versioning_examples.rb +2 -2
  112. data/spec/spec_helper.rb +0 -1
  113. data/spec/support/versioned_helpers.rb +2 -2
  114. metadata +55 -14
  115. data/.gitignore +0 -46
  116. data/.rspec +0 -2
  117. data/.rubocop.yml +0 -7
  118. data/.rubocop_todo.yml +0 -84
  119. data/.travis.yml +0 -20
  120. data/.yardopts +0 -2
  121. data/lib/backports/active_support/deep_dup.rb +0 -49
  122. data/lib/backports/active_support/duplicable.rb +0 -88
  123. data/lib/grape/http/request.rb +0 -27
@@ -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
@@ -28,6 +64,9 @@ module Grape
28
64
  end
29
65
 
30
66
  config_class.configure(&config_block)
67
+ unless options.empty?
68
+ warn '[DEPRECATION] Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.'
69
+ end
31
70
  options = config_class.settings
32
71
  else
33
72
  options = options.merge(description: description)
@@ -36,15 +75,33 @@ module Grape
36
75
  namespace_setting :description, options
37
76
  route_setting :description, options
38
77
  end
78
+
79
+ def description_field(field, value = nil)
80
+ if value
81
+ description = route_setting(:description)
82
+ description ||= route_setting(:description, {})
83
+ description[field] = value
84
+ else
85
+ description = route_setting(:description)
86
+ description[field] if description
87
+ end
88
+ end
89
+
90
+ def unset_description_field(field)
91
+ description = route_setting(:description)
92
+ description.delete(field) if description
93
+ end
39
94
  end
40
95
 
41
96
  module_function
42
97
 
98
+ # Merge multiple layers of settings into one hash.
43
99
  def stacked_hash_to_hash(settings)
44
- return nil if settings.nil? || settings.blank?
45
- settings.each_with_object(ActiveSupport::OrderedHash.new) { |value, result| result.deep_merge!(value) }
100
+ return if settings.blank?
101
+ settings.each_with_object({}) { |value, result| result.deep_merge!(value) }
46
102
  end
47
103
 
104
+ # Returns an object which configures itself via an instance-context DSL.
48
105
  def desc_container
49
106
  Module.new do
50
107
  include Grape::Util::StrictHashConfiguration.module(
@@ -29,6 +29,7 @@ module Grape
29
29
  def helpers(new_mod = nil, &block)
30
30
  if block_given? || new_mod
31
31
  mod = new_mod || Module.new
32
+ define_boolean_in_mod(mod)
32
33
  if new_mod
33
34
  inject_api_helpers_to_mod(new_mod) if new_mod.is_a?(BaseHelper)
34
35
  end
@@ -51,6 +52,11 @@ module Grape
51
52
 
52
53
  protected
53
54
 
55
+ def define_boolean_in_mod(mod)
56
+ return if defined? mod::Boolean
57
+ mod.const_set('Boolean', Virtus::Attribute::Boolean)
58
+ end
59
+
54
60
  def inject_api_helpers_to_mod(mod, &_block)
55
61
  mod.extend(BaseHelper)
56
62
  yield if block_given?
@@ -75,9 +81,8 @@ module Grape
75
81
  protected
76
82
 
77
83
  def process_named_params
78
- if @named_params && @named_params.any?
79
- api.namespace_stackable(:named_params, @named_params)
80
- end
84
+ return unless @named_params && @named_params.any?
85
+ api.namespace_stackable(:named_params, @named_params)
81
86
  end
82
87
  end
83
88
  end
@@ -6,57 +6,74 @@ module Grape
6
6
  extend ActiveSupport::Concern
7
7
  include Grape::DSL::Settings
8
8
 
9
- # A filtering method that will return a hash
10
- # consisting only of keys that have been declared by a
11
- # `params` statement against the current/target endpoint or parent
12
- # namespaces.
13
- #
14
- # @param params [Hash] The initial hash to filter. Usually this will just be `params`
15
- # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
16
- # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
17
- # you want only to return params declared against the current/target endpoint.
18
- def declared(params, options = {}, declared_params = nil)
19
- options[:include_missing] = true unless options.key?(:include_missing)
20
- options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
21
-
22
- if declared_params.nil?
23
- declared_params = (!options[:include_parent_namespaces] ? route_setting(:declared_params) :
24
- (route_setting(:saved_declared_params) || [])).flatten(1) || []
25
- end
9
+ # Denotes a situation where a DSL method has been invoked in a
10
+ # filter which it should not yet be available in
11
+ class MethodNotYetAvailable < StandardError; end
26
12
 
27
- unless declared_params
28
- fail ArgumentError, 'Tried to filter for declared parameters but none exist.'
29
- end
13
+ # @param type [Symbol] The type of filter for which evaluation has been
14
+ # completed
15
+ # @return [Module] A module containing method overrides suitable for the
16
+ # position in the filter evaluation sequence denoted by +type+. This
17
+ # defaults to an empty module if no overrides are defined for the given
18
+ # filter +type+.
19
+ def self.post_filter_methods(type)
20
+ @post_filter_modules ||= { before: PostBeforeFilter }
21
+ @post_filter_modules[type]
22
+ end
30
23
 
31
- if params.is_a? Array
32
- params.map do |param|
33
- declared(param || {}, options, declared_params)
34
- end
35
- else
36
- declared_params.inject(Hashie::Mash.new) do |hash, key|
37
- key = { key => nil } unless key.is_a? Hash
24
+ # Methods which should not be available in filters until the before filter
25
+ # has completed
26
+ module PostBeforeFilter
27
+ def declared(params, options = {}, declared_params = nil)
28
+ options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
38
29
 
39
- key.each_pair do |parent, children|
40
- output_key = options[:stringify] ? parent.to_s : parent.to_sym
30
+ declared_params ||= (!options[:include_parent_namespaces] ? route_setting(:declared_params) : (route_setting(:saved_declared_params) || [])).flatten(1) || []
41
31
 
42
- next unless options[:include_missing] || params.key?(parent)
32
+ fail ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
43
33
 
44
- hash[output_key] = if children
45
- children_params = params[parent] || (children.is_a?(Array) ? [] : {})
46
- declared(children_params, options, Array(children))
47
- else
48
- params[parent]
49
- end
34
+ if params.is_a? Array
35
+ params.map do |param|
36
+ declared(param || {}, options, declared_params)
50
37
  end
38
+ else
39
+ declared_params.each_with_object(Hashie::Mash.new) do |key, hash|
40
+ key = { key => nil } unless key.is_a? Hash
41
+
42
+ key.each_pair do |parent, children|
43
+ output_key = options[:stringify] ? parent.to_s : parent.to_sym
44
+
45
+ next unless options[:include_missing] || params.key?(parent)
51
46
 
52
- hash
47
+ hash[output_key] = if children
48
+ children_params = params[parent] || (children.is_a?(Array) ? [] : {})
49
+ declared(children_params, options, Array(children))
50
+ else
51
+ params[parent]
52
+ end
53
+ end
54
+ end
53
55
  end
54
56
  end
55
57
  end
56
58
 
59
+ # A filtering method that will return a hash
60
+ # consisting only of keys that have been declared by a
61
+ # `params` statement against the current/target endpoint or parent
62
+ # namespaces.
63
+ #
64
+ # @see +PostBeforeFilter#declared+
65
+ #
66
+ # @param params [Hash] The initial hash to filter. Usually this will just be `params`
67
+ # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
68
+ # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
69
+ # you want only to return params declared against the current/target endpoint.
70
+ def declared(*)
71
+ fail MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
72
+ end
73
+
57
74
  # The API version as specified in the URL.
58
75
  def version
59
- env['api.version']
76
+ env[Grape::Env::API_VERSION]
60
77
  end
61
78
 
62
79
  # End the request and display an error to the
@@ -74,19 +91,25 @@ module Grape
74
91
  # @param url [String] The url to be redirect.
75
92
  # @param options [Hash] The options used when redirect.
76
93
  # :permanent, default false.
94
+ # :body, default a short message including the URL.
77
95
  def redirect(url, options = {})
78
- merged_options = { permanent: false }.merge(options)
79
- if merged_options[:permanent]
96
+ permanent = options.fetch(:permanent, false)
97
+ body_message = options.fetch(:body, nil)
98
+ if permanent
80
99
  status 301
100
+ body_message ||= "This resource has been moved permanently to #{url}."
81
101
  else
82
102
  if env[Grape::Http::Headers::HTTP_VERSION] == 'HTTP/1.1' && request.request_method.to_s.upcase != Grape::Http::Headers::GET
83
103
  status 303
104
+ body_message ||= "An alternate resource is located at #{url}."
84
105
  else
85
106
  status 302
107
+ body_message ||= "This resource has been moved temporarily to #{url}."
86
108
  end
87
109
  end
88
110
  header 'Location', url
89
- body ''
111
+ content_type 'text/plain'
112
+ body body_message
90
113
  end
91
114
 
92
115
  # Set or retrieve the HTTP status code.
@@ -177,12 +200,34 @@ module Grape
177
200
  # GET /file # => "contents of file"
178
201
  def file(value = nil)
179
202
  if value
180
- @file = value
203
+ @file = Grape::Util::FileResponse.new(value)
181
204
  else
182
205
  @file
183
206
  end
184
207
  end
185
208
 
209
+ # Allows you to define the response as a streamable object.
210
+ #
211
+ # If Content-Length and Transfer-Encoding are blank (among other conditions),
212
+ # Rack assumes this response can be streamed in chunks.
213
+ #
214
+ # @example
215
+ # get '/stream' do
216
+ # stream FileStreamer.new(...)
217
+ # end
218
+ #
219
+ # GET /stream # => "chunked contents of file"
220
+ #
221
+ # See:
222
+ # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
223
+ # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
224
+ def stream(value = nil)
225
+ header 'Content-Length', nil
226
+ header 'Transfer-Encoding', nil
227
+ header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
228
+ file(value)
229
+ end
230
+
186
231
  # Allows you to make use of Grape Entities by setting
187
232
  # the response body to the serializable hash of the
188
233
  # entity provided in the `:with` option. This has the
@@ -219,7 +264,7 @@ module Grape
219
264
  if key
220
265
  representation = (@body || {}).merge(key => representation)
221
266
  elsif entity_class.present? && @body
222
- fail ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?('merge')
267
+ fail ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
223
268
  representation = @body.merge(representation)
224
269
  end
225
270
 
@@ -235,9 +280,17 @@ module Grape
235
280
  # route.route_description
236
281
  # end
237
282
  def route
238
- env['rack.routing_args'][:route_info]
283
+ env[Grape::Env::RACK_ROUTING_ARGS][:route_info]
239
284
  end
240
285
 
286
+ # Attempt to locate the Entity class for a given object, if not given
287
+ # explicitly. This is done by looking for the presence of Klass::Entity,
288
+ # where Klass is the class of the `object` parameter, or one of its
289
+ # ancestors.
290
+ # @param object [Object] the object to locate the Entity class for
291
+ # @param options [Hash]
292
+ # @option options :with [Class] the explicit entity class to use
293
+ # @return [Class] the located Entity class, or nil if none is found
241
294
  def entity_class_for_obj(object, options)
242
295
  entity_class = options.delete(:with)
243
296
 
@@ -259,9 +312,11 @@ module Grape
259
312
  entity_class
260
313
  end
261
314
 
315
+ # @return the representation of the given object as done through
316
+ # the given entity_class.
262
317
  def entity_representation_for(entity_class, object, options)
263
318
  embeds = { env: env }
264
- embeds[:version] = env['api.version'] if env['api.version']
319
+ embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION]
265
320
  entity_class.represent(object, embeds.merge(options))
266
321
  end
267
322
  end
@@ -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
 
@@ -28,7 +31,7 @@ module Grape
28
31
  # end
29
32
  def use(*names)
30
33
  named_params = Grape::DSL::Configuration.stacked_hash_to_hash(@api.namespace_stackable(:named_params)) || {}
31
- options = names.last.is_a?(Hash) ? names.pop : {}
34
+ options = names.extract_options!
32
35
  names.each do |name|
33
36
  params_block = named_params.fetch(name) do
34
37
  fail "Params :#{name} not found!"
@@ -39,10 +42,62 @@ 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::Validations::Types} for a list of
53
+ # types that are supported automatically. Custom classes may be used
54
+ # where they define a class-level `::parse` method, or in conjunction
55
+ # with the `:coerce_with` parameter. `JSON` may be supplied to denote
56
+ # `JSON`-formatted objects or arrays of objects. `Array[JSON]` accepts
57
+ # the same values as `JSON` but will wrap single objects in an `Array`.
58
+ # @option attrs :types [Array<Class>] may be supplied in place of +:type+
59
+ # to declare an attribute that has multiple allowed types. See
60
+ # {Validations::Types::MultipleTypeCoercer} for more details on coercion
61
+ # and validation rules for variant-type parameters.
62
+ # @option attrs :desc [String] description to document this parameter
63
+ # @option attrs :default [Object] default value, if parameter is optional
64
+ # @option attrs :values [Array] permissable values for this field. If any
65
+ # other value is given, it will be handled as a validation error
66
+ # @option attrs :using [Hash[Symbol => Hash]] a hash defining keys and
67
+ # options, like that returned by {Grape::Entity#documentation}. The value
68
+ # of each key is an options hash accepting the same parameters
69
+ # @option attrs :except [Array[Symbol]] a list of keys to exclude from
70
+ # the :using Hash. The meaning of this depends on if :all or :none was
71
+ # passed; :all + :except will make the :except fields optional, whereas
72
+ # :none + :except will make the :except fields required
73
+ # @option attrs :coerce_with [#parse, #call] method to be used when coercing
74
+ # the parameter to the type named by `attrs[:type]`. Any class or object
75
+ # that defines `::parse` or `::call` may be used.
76
+ #
77
+ # @example
78
+ #
79
+ # params do
80
+ # # Basic usage: require a parameter of a certain type
81
+ # requires :user_id, type: Integer
82
+ #
83
+ # # You don't need to specify type; String is default
84
+ # requires :foo
85
+ #
86
+ # # Multiple params can be specified at once if they share
87
+ # # the same options.
88
+ # requires :x, :y, :z, type: Date
89
+ #
90
+ # # Nested parameters can be handled as hashes. You must
91
+ # # pass in a block, within which you can use any of the
92
+ # # parameters DSL methods.
93
+ # requires :user, type: Hash do
94
+ # requires :name, type: String
95
+ # end
96
+ # end
42
97
  def requires(*attrs, &block)
43
98
  orig_attrs = attrs.clone
44
99
 
45
- opts = attrs.last.is_a?(Hash) ? attrs.pop.clone : {}
100
+ opts = attrs.extract_options!.clone
46
101
  opts[:presence] = true
47
102
 
48
103
  if opts[:using]
@@ -50,15 +105,18 @@ module Grape
50
105
  else
51
106
  validate_attributes(attrs, opts, &block)
52
107
 
53
- block_given? ? new_scope(orig_attrs, &block) :
54
- push_declared_params(attrs)
108
+ block_given? ? new_scope(orig_attrs, &block) : push_declared_params(attrs)
55
109
  end
56
110
  end
57
111
 
112
+ # Allow, but don't require, one or more parameters for the current
113
+ # endpoint.
114
+ # @param (see #requires)
115
+ # @option (see #requires)
58
116
  def optional(*attrs, &block)
59
117
  orig_attrs = attrs.clone
60
118
 
61
- opts = attrs.last.is_a?(Hash) ? attrs.pop.clone : {}
119
+ opts = attrs.extract_options!.clone
62
120
  type = opts[:type]
63
121
 
64
122
  # check type for optional parameter group
@@ -72,29 +130,60 @@ module Grape
72
130
  else
73
131
  validate_attributes(attrs, opts, &block)
74
132
 
75
- block_given? ? new_scope(orig_attrs, true, &block) :
76
- push_declared_params(attrs)
133
+ block_given? ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs)
77
134
  end
78
135
  end
79
136
 
137
+ # Disallow the given parameters to be present in the same request.
138
+ # @param attrs [*Symbol] parameters to validate
80
139
  def mutually_exclusive(*attrs)
81
140
  validates(attrs, mutual_exclusion: true)
82
141
  end
83
142
 
143
+ # Require exactly one of the given parameters to be present.
144
+ # @param (see #mutually_exclusive)
84
145
  def exactly_one_of(*attrs)
85
146
  validates(attrs, exactly_one_of: true)
86
147
  end
87
148
 
149
+ # Require at least one of the given parameters to be present.
150
+ # @param (see #mutually_exclusive)
88
151
  def at_least_one_of(*attrs)
89
152
  validates(attrs, at_least_one_of: true)
90
153
  end
91
154
 
155
+ # Require that either all given params are present, or none are.
156
+ # @param (see #mutually_exclusive)
92
157
  def all_or_none_of(*attrs)
93
158
  validates(attrs, all_or_none_of: true)
94
159
  end
95
160
 
161
+ # Define a block of validations which should be applied if and only if
162
+ # the given parameter is present. The parameters are not nested.
163
+ # @param attr [Symbol] the parameter which, if present, triggers the
164
+ # validations
165
+ # @raise Grape::Exceptions::UnknownParameter if `attr` has not been
166
+ # defined in this scope yet
167
+ # @yield a parameter definition DSL
168
+ def given(attr, &block)
169
+ fail Grape::Exceptions::UnknownParameter.new(attr) unless declared_param?(attr)
170
+ new_lateral_scope(dependent_on: attr, &block)
171
+ end
172
+
173
+ # Test for whether a certain parameter has been defined in this params
174
+ # block yet.
175
+ # @return [Boolean] whether the parameter has been defined
176
+ def declared_param?(param)
177
+ # @declared_params also includes hashes of options and such, but those
178
+ # won't be flattened out.
179
+ @declared_params.flatten.include?(param)
180
+ end
181
+
96
182
  alias_method :group, :requires
97
183
 
184
+ # @param params [Hash] initial hash of parameters
185
+ # @return hash of parameters relevant for the current scope
186
+ # @api private
98
187
  def params(params)
99
188
  params = @parent.params(params) if @parent
100
189
  if @element
@@ -104,7 +104,7 @@ module Grape
104
104
  handler = block
105
105
  end
106
106
 
107
- options = args.last.is_a?(Hash) ? args.pop : {}
107
+ options = args.extract_options!
108
108
  handler ||= proc { options[:with] } if options.key?(:with)
109
109
 
110
110
  if args.include?(:all)
@@ -28,9 +28,8 @@ module Grape
28
28
  #
29
29
  def version(*args, &block)
30
30
  if args.any?
31
- options = args.pop if args.last.is_a? Hash
32
- options ||= {}
33
- options = { using: :path }.merge(options)
31
+ options = args.extract_options!
32
+ options = options.reverse_merge(using: :path)
34
33
 
35
34
  fail Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
36
35
 
@@ -76,7 +75,7 @@ module Grape
76
75
 
77
76
  if app.respond_to?(:inheritable_setting, true)
78
77
  mount_path = Rack::Mount::Utils.normalize_path(path)
79
- app.top_level_setting.namespace_stackable[:mount_path] = mount_path
78
+ app.top_level_setting.namespace_stackable[:mount_path] = mount_path
80
79
 
81
80
  app.inherit_settings(inheritable_setting)
82
81
 
@@ -135,6 +134,18 @@ module Grape
135
134
  end
136
135
  end
137
136
 
137
+ # Declare a "namespace", which prefixes all subordinate routes with its
138
+ # name. Any endpoints within a namespace, or group, resource, segment,
139
+ # etc., will share their parent context as well as any configuration
140
+ # done in the namespace context.
141
+ #
142
+ # @example
143
+ #
144
+ # namespace :foo do
145
+ # get 'bar' do
146
+ # # defines the endpoint: GET /foo/bar
147
+ # end
148
+ # end
138
149
  def namespace(space = nil, options = {}, &block)
139
150
  if space || block_given?
140
151
  within_namespace do
@@ -162,6 +173,7 @@ module Grape
162
173
  @routes ||= prepare_routes
163
174
  end
164
175
 
176
+ # Remove all defined routes.
165
177
  def reset_routes!
166
178
  @routes = nil
167
179
  end
@@ -177,6 +189,7 @@ module Grape
177
189
  namespace(":#{param}", options, &block)
178
190
  end
179
191
 
192
+ # @return array of defined versions
180
193
  def versions
181
194
  @versions ||= []
182
195
  end