grape 1.1.0 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
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