grape 1.1.0 → 1.2.5

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -43
  3. data/LICENSE +1 -1
  4. data/README.md +394 -47
  5. data/UPGRADING.md +111 -0
  6. data/grape.gemspec +3 -1
  7. data/lib/grape.rb +98 -66
  8. data/lib/grape/api.rb +136 -175
  9. data/lib/grape/api/instance.rb +280 -0
  10. data/lib/grape/config.rb +32 -0
  11. data/lib/grape/dsl/callbacks.rb +20 -0
  12. data/lib/grape/dsl/desc.rb +39 -7
  13. data/lib/grape/dsl/inside_route.rb +12 -6
  14. data/lib/grape/dsl/middleware.rb +7 -0
  15. data/lib/grape/dsl/parameters.rb +9 -4
  16. data/lib/grape/dsl/routing.rb +5 -1
  17. data/lib/grape/dsl/validations.rb +4 -3
  18. data/lib/grape/eager_load.rb +18 -0
  19. data/lib/grape/endpoint.rb +42 -26
  20. data/lib/grape/error_formatter.rb +1 -1
  21. data/lib/grape/exceptions/base.rb +9 -1
  22. data/lib/grape/exceptions/invalid_response.rb +9 -0
  23. data/lib/grape/exceptions/validation_errors.rb +4 -2
  24. data/lib/grape/formatter.rb +1 -1
  25. data/lib/grape/locale/en.yml +2 -0
  26. data/lib/grape/middleware/auth/base.rb +2 -4
  27. data/lib/grape/middleware/base.rb +2 -0
  28. data/lib/grape/middleware/error.rb +9 -4
  29. data/lib/grape/middleware/helpers.rb +10 -0
  30. data/lib/grape/middleware/stack.rb +1 -1
  31. data/lib/grape/middleware/versioner/header.rb +4 -4
  32. data/lib/grape/parser.rb +1 -1
  33. data/lib/grape/request.rb +1 -1
  34. data/lib/grape/router/attribute_translator.rb +2 -0
  35. data/lib/grape/router/route.rb +2 -2
  36. data/lib/grape/util/base_inheritable.rb +34 -0
  37. data/lib/grape/util/endpoint_configuration.rb +6 -0
  38. data/lib/grape/util/inheritable_values.rb +5 -25
  39. data/lib/grape/util/lazy_block.rb +25 -0
  40. data/lib/grape/util/lazy_value.rb +95 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +7 -36
  42. data/lib/grape/util/stackable_values.rb +19 -22
  43. data/lib/grape/validations/attributes_iterator.rb +5 -3
  44. data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
  45. data/lib/grape/validations/params_scope.rb +20 -14
  46. data/lib/grape/validations/single_attribute_iterator.rb +13 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  48. data/lib/grape/validations/types/file.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +6 -11
  50. data/lib/grape/validations/validators/all_or_none.rb +6 -13
  51. data/lib/grape/validations/validators/as.rb +2 -3
  52. data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
  53. data/lib/grape/validations/validators/base.rb +11 -10
  54. data/lib/grape/validations/validators/coerce.rb +4 -0
  55. data/lib/grape/validations/validators/default.rb +1 -1
  56. data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
  57. data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
  58. data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
  59. data/lib/grape/validations/validators/same_as.rb +23 -0
  60. data/lib/grape/version.rb +1 -1
  61. data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
  62. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  63. data/spec/grape/api_remount_spec.rb +466 -0
  64. data/spec/grape/api_spec.rb +379 -1
  65. data/spec/grape/config_spec.rb +17 -0
  66. data/spec/grape/dsl/desc_spec.rb +40 -16
  67. data/spec/grape/dsl/middleware_spec.rb +8 -0
  68. data/spec/grape/dsl/routing_spec.rb +10 -0
  69. data/spec/grape/endpoint_spec.rb +40 -4
  70. data/spec/grape/exceptions/base_spec.rb +65 -0
  71. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  72. data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
  73. data/spec/grape/integration/rack_spec.rb +22 -6
  74. data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
  75. data/spec/grape/middleware/base_spec.rb +8 -0
  76. data/spec/grape/middleware/exception_spec.rb +1 -1
  77. data/spec/grape/middleware/formatter_spec.rb +15 -5
  78. data/spec/grape/middleware/versioner/header_spec.rb +6 -0
  79. data/spec/grape/named_api_spec.rb +19 -0
  80. data/spec/grape/request_spec.rb +24 -0
  81. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
  82. data/spec/grape/validations/params_scope_spec.rb +184 -8
  83. data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
  84. data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
  85. data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
  86. data/spec/grape/validations/validators/coerce_spec.rb +10 -2
  87. data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
  88. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
  89. data/spec/grape/validations/validators/same_as_spec.rb +63 -0
  90. data/spec/grape/validations_spec.rb +33 -21
  91. data/spec/spec_helper.rb +4 -1
  92. metadata +35 -23
  93. data/Appraisals +0 -32
  94. data/Dangerfile +0 -2
  95. data/Gemfile +0 -33
  96. data/Gemfile.lock +0 -231
  97. data/Guardfile +0 -10
  98. data/RELEASING.md +0 -111
  99. data/Rakefile +0 -25
  100. data/benchmark/simple.rb +0 -27
  101. data/benchmark/simple_with_type_coercer.rb +0 -22
  102. data/gemfiles/multi_json.gemfile +0 -35
  103. data/gemfiles/multi_xml.gemfile +0 -35
  104. data/gemfiles/rack_1.5.2.gemfile +0 -35
  105. data/gemfiles/rack_edge.gemfile +0 -35
  106. data/gemfiles/rails_3.gemfile +0 -36
  107. data/gemfiles/rails_4.gemfile +0 -35
  108. data/gemfiles/rails_5.gemfile +0 -35
  109. data/gemfiles/rails_edge.gemfile +0 -35
  110. data/pkg/grape-0.17.0.gem +0 -0
  111. data/pkg/grape-0.19.0.gem +0 -0
@@ -21,6 +21,13 @@ module Grape
21
21
  namespace_stackable(:middleware, arr)
22
22
  end
23
23
 
24
+ def insert(*args, &block)
25
+ arr = [:insert, *args]
26
+ arr << block if block_given?
27
+
28
+ namespace_stackable(:middleware, arr)
29
+ end
30
+
24
31
  def insert_before(*args, &block)
25
32
  arr = [:insert_before, *args]
26
33
  arr << block if block_given?
@@ -211,10 +211,15 @@ module Grape
211
211
  # block yet.
212
212
  # @return [Boolean] whether the parameter has been defined
213
213
  def declared_param?(param)
214
- # @declared_params also includes hashes of options and such, but those
215
- # won't be flattened out.
216
- @declared_params.flatten.any? do |declared_param|
217
- first_hash_key_or_param(declared_param) == param
214
+ if lateral?
215
+ # Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent.
216
+ @parent.declared_param?(param)
217
+ else
218
+ # @declared_params also includes hashes of options and such, but those
219
+ # won't be flattened out.
220
+ @declared_params.flatten.any? do |declared_param|
221
+ first_hash_key_or_param(declared_param) == param
222
+ end
218
223
  end
219
224
  end
220
225
 
@@ -77,9 +77,13 @@ module Grape
77
77
  namespace_inheritable(:do_not_route_options, true)
78
78
  end
79
79
 
80
- def mount(mounts)
80
+ def mount(mounts, opts = {})
81
81
  mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
82
82
  mounts.each_pair do |app, path|
83
+ if app.respond_to?(:mount_instance)
84
+ mount(app.mount_instance(configuration: opts[:with] || {}) => path)
85
+ next
86
+ end
83
87
  in_setting = inheritable_setting
84
88
 
85
89
  if app.respond_to?(:inheritable_setting, true)
@@ -27,10 +27,11 @@ module Grape
27
27
  setting = description_field(:params)
28
28
  setting ||= description_field(:params, {})
29
29
  Array(names).each do |name|
30
- setting[name[:full_name].to_s] ||= {}
31
- setting[name[:full_name].to_s].merge!(opts)
30
+ full_name = name[:full_name].to_s
31
+ setting[full_name] ||= {}
32
+ setting[full_name].merge!(opts)
32
33
 
33
- namespace_stackable(:params, name[:full_name].to_s => opts)
34
+ namespace_stackable(:params, full_name => opts)
34
35
  end
35
36
  end
36
37
  end
@@ -0,0 +1,18 @@
1
+ Grape.eager_load!
2
+ Grape::Http.eager_load!
3
+ Grape::Exceptions.eager_load!
4
+ Grape::Extensions.eager_load!
5
+ Grape::Extensions::ActiveSupport.eager_load!
6
+ Grape::Extensions::Hashie.eager_load!
7
+ Grape::Middleware.eager_load!
8
+ Grape::Middleware::Auth.eager_load!
9
+ Grape::Middleware::Versioner.eager_load!
10
+ Grape::Util.eager_load!
11
+ Grape::ErrorFormatter.eager_load!
12
+ Grape::Formatter.eager_load!
13
+ Grape::Parser.eager_load!
14
+ Grape::DSL.eager_load!
15
+ Grape::API.eager_load!
16
+ Grape::Presenters.eager_load!
17
+ Grape::ServeFile.eager_load!
18
+ Rack::Head # AutoLoads the Rack::Head
@@ -200,7 +200,7 @@ module Grape
200
200
  end
201
201
 
202
202
  def merge_route_options(**default)
203
- options[:route_options].clone.reverse_merge(**default)
203
+ options[:route_options].clone.merge!(**default)
204
204
  end
205
205
 
206
206
  def map_routes
@@ -245,32 +245,37 @@ module Grape
245
245
  @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
246
246
  @params = @request.params
247
247
  @headers = @request.headers
248
+ begin
249
+ cookies.read(@request)
250
+ self.class.run_before_each(self)
251
+ run_filters befores, :before
252
+
253
+ if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
254
+ raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options?
255
+ header 'Allow', allowed_methods
256
+ response_object = ''
257
+ status 204
258
+ else
259
+ run_filters before_validations, :before_validation
260
+ run_validators validations, request
261
+ remove_renamed_params
262
+ run_filters after_validations, :after_validation
263
+ response_object = @block ? @block.call(self) : nil
264
+ end
248
265
 
249
- cookies.read(@request)
250
- self.class.run_before_each(self)
251
- run_filters befores, :before
266
+ run_filters afters, :after
267
+ cookies.write(header)
252
268
 
253
- if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
254
- raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options?
255
- header 'Allow', allowed_methods
256
- response_object = ''
257
- status 204
258
- else
259
- run_filters before_validations, :before_validation
260
- run_validators validations, request
261
- run_filters after_validations, :after_validation
262
- response_object = @block ? @block.call(self) : nil
263
- end
269
+ # status verifies body presence when DELETE
270
+ @body ||= response_object
264
271
 
265
- run_filters afters, :after
266
- cookies.write(header)
272
+ # The body commonly is an Array of Strings, the application instance itself, or a File-like object
273
+ response_object = file || [body]
267
274
 
268
- # status verifies body presence when DELETE
269
- @body ||= response_object
270
-
271
- # The Body commonly is an Array of Strings, the application instance itself, or a File-like object
272
- response_object = file || [body]
273
- [status, header, response_object]
275
+ [status, header, response_object]
276
+ ensure
277
+ run_filters finallies, :finally
278
+ end
274
279
  end
275
280
  end
276
281
 
@@ -319,7 +324,14 @@ module Grape
319
324
  Module.new { helpers.each { |mod_to_include| include mod_to_include } }
320
325
  end
321
326
 
322
- private :build_stack, :build_helpers
327
+ def remove_renamed_params
328
+ return unless route_setting(:renamed_params)
329
+ route_setting(:renamed_params).flat_map(&:keys).each do |renamed_param|
330
+ @params.delete(renamed_param)
331
+ end
332
+ end
333
+
334
+ private :build_stack, :build_helpers, :remove_renamed_params
323
335
 
324
336
  def helpers
325
337
  lazy_initialize! && @helpers
@@ -341,7 +353,7 @@ module Grape
341
353
  def run_validators(validator_factories, request)
342
354
  validation_errors = []
343
355
 
344
- validators = validator_factories.map(&:create_validator)
356
+ validators = validator_factories.map { |options| Grape::Validations::ValidatorFactory.create_validator(options) }
345
357
 
346
358
  ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
347
359
  validators.each do |validator|
@@ -351,7 +363,7 @@ module Grape
351
363
  validation_errors << e
352
364
  break if validator.fail_fast?
353
365
  rescue Grape::Exceptions::ValidationArrayErrors => e
354
- validation_errors += e.errors
366
+ validation_errors.concat e.errors
355
367
  break if validator.fail_fast?
356
368
  end
357
369
  end
@@ -384,6 +396,10 @@ module Grape
384
396
  namespace_stackable(:afters) || []
385
397
  end
386
398
 
399
+ def finallies
400
+ namespace_stackable(:finallies) || []
401
+ end
402
+
387
403
  def validations
388
404
  route_setting(:saved_validations) || []
389
405
  end
@@ -14,7 +14,7 @@ module Grape
14
14
  end
15
15
 
16
16
  def formatters(options)
17
- builtin_formatters.merge(default_elements).merge(options[:error_formatters] || {})
17
+ builtin_formatters.merge(default_elements).merge!(options[:error_formatters] || {})
18
18
  end
19
19
 
20
20
  def formatter_for(api_format, **options)
@@ -74,7 +74,15 @@ module Grape
74
74
  options = options.dup
75
75
  options[:default] &&= options[:default].to_s
76
76
  message = ::I18n.translate(key, **options)
77
- message.present? ? message : ::I18n.translate(key, locale: FALLBACK_LOCALE, **options)
77
+ message.present? ? message : fallback_message(key, **options)
78
+ end
79
+
80
+ def fallback_message(key, **options)
81
+ if ::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)
82
+ key
83
+ else
84
+ ::I18n.translate(key, locale: FALLBACK_LOCALE, **options)
85
+ end
78
86
  end
79
87
  end
80
88
  end
@@ -0,0 +1,9 @@
1
+ module Grape
2
+ module Exceptions
3
+ class InvalidResponse < Base
4
+ def initialize
5
+ super(message: compose_message(:invalid_response))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -39,14 +39,16 @@ module Grape
39
39
  end
40
40
 
41
41
  def full_messages
42
- map { |attributes, error| full_message(attributes, error) }.uniq
42
+ messages = map { |attributes, error| full_message(attributes, error) }
43
+ messages.uniq!
44
+ messages
43
45
  end
44
46
 
45
47
  private
46
48
 
47
49
  def full_message(attributes, error)
48
50
  I18n.t(
49
- 'grape.errors.format'.to_sym,
51
+ 'grape.errors.format',
50
52
  default: '%{attributes} %{message}',
51
53
  attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes),
52
54
  message: error.message
@@ -14,7 +14,7 @@ module Grape
14
14
  end
15
15
 
16
16
  def formatters(options)
17
- builtin_formmaters.merge(default_elements).merge(options[:formatters] || {})
17
+ builtin_formmaters.merge(default_elements).merge!(options[:formatters] || {})
18
18
  end
19
19
 
20
20
  def formatter_for(api_format, **options)
@@ -9,6 +9,7 @@ en:
9
9
  blank: 'is empty'
10
10
  values: 'does not have a valid value'
11
11
  except_values: 'has a value not allowed'
12
+ same_as: 'is not the same as %{parameter}'
12
13
  missing_vendor_option:
13
14
  problem: 'missing :vendor option.'
14
15
  summary: 'when version using header, you must specify :vendor option. '
@@ -49,4 +50,5 @@ en:
49
50
  invalid_version_header:
50
51
  problem: 'Invalid version header'
51
52
  resolution: '%{message}'
53
+ invalid_response: 'Invalid response'
52
54
 
@@ -4,6 +4,8 @@ module Grape
4
4
  module Middleware
5
5
  module Auth
6
6
  class Base
7
+ include Helpers
8
+
7
9
  attr_accessor :options, :app, :env
8
10
 
9
11
  def initialize(app, **options)
@@ -11,10 +13,6 @@ module Grape
11
13
  @options = options
12
14
  end
13
15
 
14
- def context
15
- env[Grape::Env::API_ENDPOINT]
16
- end
17
-
18
16
  def call(env)
19
17
  dup._call(env)
20
18
  end
@@ -3,6 +3,8 @@ require 'grape/dsl/headers'
3
3
  module Grape
4
4
  module Middleware
5
5
  class Base
6
+ include Helpers
7
+
6
8
  attr_reader :app, :env, :options
7
9
  TEXT_HTML = 'text/html'.freeze
8
10
 
@@ -21,7 +21,7 @@ module Grape
21
21
  },
22
22
  rescue_handlers: {}, # rescue handler blocks
23
23
  base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
24
- all_rescue_handler: nil # rescue handler block to rescue from all exceptions
24
+ all_rescue_handler: nil, # rescue handler block to rescue from all exceptions
25
25
  }
26
26
  end
27
27
 
@@ -32,7 +32,6 @@ module Grape
32
32
 
33
33
  def call!(env)
34
34
  @env = env
35
-
36
35
  begin
37
36
  error_response(catch(:error) do
38
37
  return @app.call(@env)
@@ -73,7 +72,7 @@ module Grape
73
72
  if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
74
73
  message = ERB::Util.html_escape(message)
75
74
  end
76
- Rack::Response.new([message], status, headers).finish
75
+ Rack::Response.new([message], status, headers)
77
76
  end
78
77
 
79
78
  def format_message(message, backtrace, original_exception = nil)
@@ -127,7 +126,13 @@ module Grape
127
126
  handler = public_method(handler)
128
127
  end
129
128
 
130
- handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
129
+ response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
130
+
131
+ if response.is_a?(Rack::Response)
132
+ response
133
+ else
134
+ run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
135
+ end
131
136
  end
132
137
  end
133
138
  end
@@ -0,0 +1,10 @@
1
+ module Grape
2
+ module Middleware
3
+ # Common methods for all types of Grape middleware
4
+ module Helpers
5
+ def context
6
+ env[Grape::Env::API_ENDPOINT]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -96,7 +96,7 @@ module Grape
96
96
  # @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
97
97
  def concat(other_specs)
98
98
  @others << Array(other_specs).reject { |o| o.first == :use }
99
- merge_with Array(other_specs).select { |o| o.first == :use }
99
+ merge_with(Array(other_specs).select { |o| o.first == :use })
100
100
  end
101
101
 
102
102
  protected
@@ -99,7 +99,7 @@ module Grape
99
99
  def available_media_types
100
100
  available_media_types = []
101
101
 
102
- content_types.each do |extension, _media_type|
102
+ content_types.each_key do |extension|
103
103
  versions.reverse_each do |version|
104
104
  available_media_types += [
105
105
  "application/vnd.#{vendor}-#{version}+#{extension}",
@@ -111,7 +111,7 @@ module Grape
111
111
 
112
112
  available_media_types << "application/vnd.#{vendor}"
113
113
 
114
- content_types.each do |_, media_type|
114
+ content_types.each_value do |media_type|
115
115
  available_media_types << media_type
116
116
  end
117
117
 
@@ -173,7 +173,7 @@ module Grape
173
173
  # @return [Boolean] whether the content type sets a vendor
174
174
  def vendor?(media_type)
175
175
  _, subtype = Rack::Accept::Header.parse_media_type(media_type)
176
- subtype[HAS_VENDOR_REGEX]
176
+ subtype.present? && subtype[HAS_VENDOR_REGEX]
177
177
  end
178
178
 
179
179
  def request_vendor(media_type)
@@ -190,7 +190,7 @@ module Grape
190
190
  # @return [Boolean] whether the content type sets an API version
191
191
  def version?(media_type)
192
192
  _, subtype = Rack::Accept::Header.parse_media_type(media_type)
193
- subtype[HAS_VERSION_REGEX]
193
+ subtype.present? && subtype[HAS_VERSION_REGEX]
194
194
  end
195
195
  end
196
196
  end
@@ -12,7 +12,7 @@ module Grape
12
12
  end
13
13
 
14
14
  def parsers(options)
15
- builtin_parsers.merge(default_elements).merge(options[:parsers] || {})
15
+ builtin_parsers.merge(default_elements).merge!(options[:parsers] || {})
16
16
  end
17
17
 
18
18
  def parser_for(api_format, **options)
@@ -5,7 +5,7 @@ module Grape
5
5
  alias rack_params params
6
6
 
7
7
  def initialize(env, options = {})
8
- extend options[:build_params_with] || Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
8
+ extend options[:build_params_with] || Grape.config.param_builder
9
9
  super(env)
10
10
  end
11
11
 
@@ -15,6 +15,8 @@ module Grape
15
15
  @attributes[m[0..-1]] = *args
16
16
  elsif m[-1] != '='
17
17
  @attributes[m]
18
+ else
19
+ super
18
20
  end
19
21
  end
20
22
 
@@ -98,9 +98,9 @@ module Grape
98
98
  path, line = *location.scan(SOURCE_LOCATION_REGEXP).first
99
99
  path = File.realpath(path) if Pathname.new(path).relative?
100
100
  expected ||= name
101
- warn <<-EOS
101
+ warn <<-WARNING
102
102
  #{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.
103
- EOS
103
+ WARNING
104
104
  end
105
105
  end
106
106
  end