grape 0.16.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +4 -0
  3. data/CHANGELOG.md +54 -27
  4. data/Dangerfile +80 -0
  5. data/Gemfile +23 -0
  6. data/Gemfile.lock +61 -27
  7. data/README.md +135 -7
  8. data/Rakefile +34 -30
  9. data/UPGRADING.md +21 -0
  10. data/gemfiles/rack_1.5.2.gemfile +21 -0
  11. data/gemfiles/rails_3.gemfile +22 -1
  12. data/gemfiles/rails_4.gemfile +21 -0
  13. data/gemfiles/rails_5.gemfile +34 -0
  14. data/grape.gemspec +0 -14
  15. data/lib/grape.rb +2 -0
  16. data/lib/grape/api.rb +9 -2
  17. data/lib/grape/dsl/headers.rb +1 -1
  18. data/lib/grape/dsl/inside_route.rb +15 -17
  19. data/lib/grape/dsl/middleware.rb +15 -1
  20. data/lib/grape/dsl/parameters.rb +16 -14
  21. data/lib/grape/dsl/request_response.rb +24 -20
  22. data/lib/grape/dsl/routing.rb +11 -10
  23. data/lib/grape/dsl/settings.rb +16 -0
  24. data/lib/grape/endpoint.rb +77 -60
  25. data/lib/grape/exceptions/validation.rb +5 -2
  26. data/lib/grape/exceptions/validation_array_errors.rb +11 -0
  27. data/lib/grape/formatter/xml.rb +1 -1
  28. data/lib/grape/middleware/error.rb +34 -25
  29. data/lib/grape/middleware/formatter.rb +9 -9
  30. data/lib/grape/middleware/stack.rb +110 -0
  31. data/lib/grape/middleware/versioner.rb +1 -1
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  33. data/lib/grape/middleware/versioner/header.rb +3 -3
  34. data/lib/grape/path.rb +10 -2
  35. data/lib/grape/request.rb +1 -1
  36. data/lib/grape/router.rb +10 -19
  37. data/lib/grape/router/pattern.rb +2 -2
  38. data/lib/grape/router/route.rb +3 -3
  39. data/lib/grape/util/content_types.rb +1 -1
  40. data/lib/grape/util/inheritable_setting.rb +7 -2
  41. data/lib/grape/util/reverse_stackable_values.rb +45 -0
  42. data/lib/grape/util/stackable_values.rb +10 -11
  43. data/lib/grape/validations/attributes_iterator.rb +32 -7
  44. data/lib/grape/validations/params_scope.rb +33 -21
  45. data/lib/grape/validations/types.rb +4 -4
  46. data/lib/grape/validations/types/build_coercer.rb +9 -1
  47. data/lib/grape/validations/validators/all_or_none.rb +2 -2
  48. data/lib/grape/validations/validators/allow_blank.rb +10 -11
  49. data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
  50. data/lib/grape/validations/validators/base.rb +16 -6
  51. data/lib/grape/validations/validators/coerce.rb +3 -6
  52. data/lib/grape/validations/validators/default.rb +26 -1
  53. data/lib/grape/validations/validators/exactly_one_of.rb +1 -1
  54. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
  55. data/lib/grape/validations/validators/presence.rb +1 -1
  56. data/lib/grape/validations/validators/regexp.rb +1 -1
  57. data/lib/grape/validations/validators/values.rb +1 -1
  58. data/lib/grape/version.rb +1 -1
  59. data/spec/grape/api/custom_validations_spec.rb +3 -3
  60. data/spec/grape/api/parameters_modification_spec.rb +41 -0
  61. data/spec/grape/api_spec.rb +335 -108
  62. data/spec/grape/dsl/logger_spec.rb +1 -1
  63. data/spec/grape/dsl/middleware_spec.rb +25 -5
  64. data/spec/grape/dsl/request_response_spec.rb +20 -6
  65. data/spec/grape/dsl/validations_spec.rb +1 -1
  66. data/spec/grape/endpoint_spec.rb +166 -23
  67. data/spec/grape/entity_spec.rb +0 -2
  68. data/spec/grape/exceptions/body_parse_errors_spec.rb +37 -0
  69. data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
  70. data/spec/grape/exceptions/validation_spec.rb +10 -0
  71. data/spec/grape/integration/global_namespace_function_spec.rb +1 -1
  72. data/spec/grape/integration/rack_spec.rb +1 -1
  73. data/spec/grape/middleware/base_spec.rb +1 -1
  74. data/spec/grape/middleware/exception_spec.rb +2 -2
  75. data/spec/grape/middleware/formatter_spec.rb +4 -4
  76. data/spec/grape/middleware/stack_spec.rb +123 -0
  77. data/spec/grape/middleware/versioner/header_spec.rb +6 -6
  78. data/spec/grape/request_spec.rb +22 -22
  79. data/spec/grape/util/inheritable_setting_spec.rb +23 -0
  80. data/spec/grape/util/reverse_stackable_values_spec.rb +131 -0
  81. data/spec/grape/validations/params_scope_spec.rb +88 -1
  82. data/spec/grape/validations/validators/allow_blank_spec.rb +5 -0
  83. data/spec/grape/validations/validators/coerce_spec.rb +5 -5
  84. data/spec/grape/validations/validators/default_spec.rb +44 -0
  85. data/spec/grape/validations/validators/values_spec.rb +1 -1
  86. data/spec/grape/validations_spec.rb +36 -17
  87. data/spec/spec_helper.rb +1 -8
  88. data/spec/support/versioned_helpers.rb +3 -3
  89. metadata +13 -188
  90. data/gemfiles/rails_3.gemfile.lock +0 -225
  91. data/pkg/grape-0.16.1.gem +0 -0
  92. data/pkg/patch.diff +0 -24
  93. data/tmp/Gemfile.lock +0 -63
@@ -7,9 +7,12 @@ module Grape
7
7
  attr_accessor :message_key
8
8
 
9
9
  def initialize(args = {})
10
- fail 'Params are missing:' unless args.key? :params
10
+ raise 'Params are missing:' unless args.key? :params
11
11
  @params = args[:params]
12
- args[:message] = translate_message(args[:message]) if args.key? :message
12
+ if args.key?(:message)
13
+ @message_key = args[:message] if args[:message].is_a?(Symbol)
14
+ args[:message] = translate_message(args[:message])
15
+ end
13
16
  super
14
17
  end
15
18
 
@@ -0,0 +1,11 @@
1
+ module Grape
2
+ module Exceptions
3
+ class ValidationArrayErrors < Base
4
+ attr_reader :errors
5
+
6
+ def initialize(errors)
7
+ @errors = errors
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,7 +4,7 @@ module Grape
4
4
  class << self
5
5
  def call(object, _env)
6
6
  return object.to_xml if object.respond_to?(:to_xml)
7
- fail Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
7
+ raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
8
8
  end
9
9
  end
10
10
  end
@@ -8,29 +8,34 @@ module Grape
8
8
  default_status: 500, # default status returned on error
9
9
  default_message: '',
10
10
  format: :txt,
11
+ helpers: nil,
11
12
  formatters: {},
12
13
  error_formatters: {},
13
14
  rescue_all: false, # true to rescue all exceptions
15
+ rescue_grape_exceptions: false,
14
16
  rescue_subclasses: true, # rescue subclasses of exceptions listed
15
- rescue_options: { backtrace: false }, # true to display backtrace
17
+ rescue_options: { backtrace: false }, # true to display backtrace, true to let Grape handle Grape::Exceptions
16
18
  rescue_handlers: {}, # rescue handler blocks
17
19
  base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
18
20
  all_rescue_handler: nil # rescue handler block to rescue from all exceptions
19
21
  }
20
22
  end
21
23
 
24
+ def initialize(app, options = {})
25
+ super
26
+ self.class.send(:include, @options[:helpers]) if @options[:helpers]
27
+ end
28
+
22
29
  def call!(env)
23
30
  @env = env
24
31
 
25
- inject_helpers!
26
-
27
32
  begin
28
33
  error_response(catch(:error) do
29
34
  return @app.call(@env)
30
35
  end)
31
36
  rescue StandardError => e
32
37
  is_rescuable = rescuable?(e.class)
33
- if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
38
+ if e.is_a?(Grape::Exceptions::Base) && (!is_rescuable || rescuable_by_grape?(e.class))
34
39
  handler = ->(arg) { error_response(arg) }
35
40
  else
36
41
  raise unless is_rescuable
@@ -45,20 +50,23 @@ module Grape
45
50
  handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
46
51
  handler ||= options[:base_only_rescue_handlers][klass]
47
52
  handler ||= options[:all_rescue_handler]
48
- with_option = options[:rescue_options][:with]
49
- if with_option.instance_of?(Symbol)
50
- if respond_to?(with_option)
51
- handler ||= self.class.instance_method(with_option).bind(self)
52
- else
53
- fail NoMethodError, "undefined method `#{with_option}'"
54
- end
53
+
54
+ if handler.instance_of?(Symbol)
55
+ raise NoMethodError, "undefined method `#{handler}'" unless respond_to?(handler)
56
+ handler = self.class.instance_method(handler).bind(self)
55
57
  end
58
+
56
59
  handler
57
60
  end
58
61
 
59
62
  def rescuable?(klass)
60
63
  return false if klass == Grape::Exceptions::InvalidVersionHeader
61
- options[:rescue_all] || (options[:rescue_handlers] || []).any? { |error, _handler| klass <= error } || (options[:base_only_rescue_handlers] || []).include?(klass)
64
+ rescue_all? || rescue_class_or_its_ancestor?(klass) || rescue_with_base_only_handler?(klass)
65
+ end
66
+
67
+ def rescuable_by_grape?(klass)
68
+ return false if klass == Grape::Exceptions::InvalidVersionHeader
69
+ options[:rescue_grape_exceptions]
62
70
  end
63
71
 
64
72
  def exec_handler(e, &handler)
@@ -69,19 +77,6 @@ module Grape
69
77
  end
70
78
  end
71
79
 
72
- def inject_helpers!
73
- return if helpers_available?
74
- endpoint = @env['api.endpoint']
75
- self.class.instance_eval do
76
- include endpoint.send(:helpers)
77
- end if endpoint.is_a?(Grape::Endpoint)
78
- @helpers = true
79
- end
80
-
81
- def helpers_available?
82
- @helpers
83
- end
84
-
85
80
  def error!(message, status = options[:default_status], headers = {}, backtrace = [])
86
81
  headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
87
82
  rack_response(format_message(message, backtrace), status, headers)
@@ -111,6 +106,20 @@ module Grape
111
106
  throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
112
107
  formatter.call(message, backtrace, options, env)
113
108
  end
109
+
110
+ private
111
+
112
+ def rescue_all?
113
+ options[:rescue_all]
114
+ end
115
+
116
+ def rescue_class_or_its_ancestor?(klass)
117
+ (options[:rescue_handlers] || []).any? { |error, _handler| klass <= error }
118
+ end
119
+
120
+ def rescue_with_base_only_handler?(klass)
121
+ (options[:base_only_rescue_handlers] || []).include?(klass)
122
+ end
114
123
  end
115
124
  end
116
125
  end
@@ -74,7 +74,7 @@ module Grape
74
74
  return unless
75
75
  (request.post? || request.put? || request.patch? || request.delete?) &&
76
76
  (!request.form_data? || !request.media_type) &&
77
- (!request.parseable_data?) &&
77
+ !request.parseable_data? &&
78
78
  (request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
79
79
 
80
80
  return unless (input = env[Grape::Env::RACK_INPUT])
@@ -82,7 +82,7 @@ module Grape
82
82
  input.rewind
83
83
  body = env[Grape::Env::API_REQUEST_INPUT] = input.read
84
84
  begin
85
- read_rack_input(body) if body && body.length > 0
85
+ read_rack_input(body) if body && !body.empty?
86
86
  ensure
87
87
  input.rewind
88
88
  end
@@ -98,11 +98,11 @@ module Grape
98
98
  begin
99
99
  body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
100
100
  if body.is_a?(Hash)
101
- if env[Grape::Env::RACK_REQUEST_FORM_HASH]
102
- env[Grape::Env::RACK_REQUEST_FORM_HASH] = env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body)
103
- else
104
- env[Grape::Env::RACK_REQUEST_FORM_HASH] = body
105
- end
101
+ env[Grape::Env::RACK_REQUEST_FORM_HASH] = if env[Grape::Env::RACK_REQUEST_FORM_HASH]
102
+ env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body)
103
+ else
104
+ body
105
+ end
106
106
  env[Grape::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Env::RACK_INPUT]
107
107
  end
108
108
  rescue Grape::Exceptions::Base => e
@@ -168,8 +168,8 @@ module Grape
168
168
  vendor_prefix_pattern = /vnd\.[^+]+\+/
169
169
 
170
170
  accept.scan(accept_into_mime_and_quality)
171
- .sort_by { |_, quality_preference| -quality_preference.to_f }
172
- .flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
171
+ .sort_by { |_, quality_preference| -quality_preference.to_f }
172
+ .flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
173
173
  end
174
174
  end
175
175
  end
@@ -0,0 +1,110 @@
1
+ module Grape
2
+ module Middleware
3
+ # Class to handle the stack of middlewares based on ActionDispatch::MiddlewareStack
4
+ # It allows to insert and insert after
5
+ class Stack
6
+ class Middleware
7
+ attr_reader :args, :block, :klass
8
+
9
+ def initialize(klass, *args, &block)
10
+ @klass = klass
11
+ @args = args
12
+ @block = block
13
+ end
14
+
15
+ def name
16
+ klass.name
17
+ end
18
+
19
+ def ==(other)
20
+ case other
21
+ when Middleware
22
+ klass == other.klass
23
+ when Class
24
+ klass == other
25
+ end
26
+ end
27
+
28
+ def inspect
29
+ klass.to_s
30
+ end
31
+ end
32
+
33
+ include Enumerable
34
+
35
+ attr_accessor :middlewares, :others
36
+
37
+ def initialize
38
+ @middlewares = []
39
+ @others = []
40
+ end
41
+
42
+ def each
43
+ @middlewares.each { |x| yield x }
44
+ end
45
+
46
+ def size
47
+ middlewares.size
48
+ end
49
+
50
+ def last
51
+ middlewares.last
52
+ end
53
+
54
+ def [](i)
55
+ middlewares[i]
56
+ end
57
+
58
+ def insert(index, *args, &block)
59
+ index = assert_index(index, :before)
60
+ middleware = self.class::Middleware.new(*args, &block)
61
+ middlewares.insert(index, middleware)
62
+ end
63
+
64
+ alias insert_before insert
65
+
66
+ def insert_after(index, *args, &block)
67
+ index = assert_index(index, :after)
68
+ insert(index + 1, *args, &block)
69
+ end
70
+
71
+ def use(*args, &block)
72
+ middleware = self.class::Middleware.new(*args, &block)
73
+ middlewares.push(middleware)
74
+ end
75
+
76
+ def merge_with(middleware_specs)
77
+ middleware_specs.each do |operation, *args|
78
+ if args.last.is_a?(Proc)
79
+ public_send(operation, *args, &args.pop)
80
+ else
81
+ public_send(operation, *args)
82
+ end
83
+ end
84
+ end
85
+
86
+ # @return [Rack::Builder] the builder object with our middlewares applied
87
+ def build(builder = Rack::Builder.new)
88
+ others.shift(others.size).each(&method(:merge_with))
89
+ middlewares.each do |m|
90
+ m.block ? builder.use(m.klass, *m.args, &m.block) : builder.use(m.klass, *m.args)
91
+ end
92
+ builder
93
+ end
94
+
95
+ # @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
96
+ # @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
97
+ def concat(other_specs)
98
+ @others << Array(other_specs).reject { |o| o.first == :use }
99
+ merge_with Array(other_specs).select { |o| o.first == :use }
100
+ end
101
+
102
+ protected
103
+
104
+ def assert_index(index, where)
105
+ i = index.is_a?(Integer) ? index : middlewares.index(index)
106
+ i || raise("No such middleware to insert #{where}: #{index.inspect}")
107
+ end
108
+ end
109
+ end
110
+ end
@@ -24,7 +24,7 @@ module Grape
24
24
  when :accept_version_header
25
25
  AcceptVersionHeader
26
26
  else
27
- fail Grape::Exceptions::InvalidVersionerOption.new(strategy)
27
+ raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
28
28
  end
29
29
  end
30
30
  end
@@ -50,7 +50,7 @@ module Grape
50
50
  # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
51
51
  def cascade?
52
52
  if options[:version_options] && options[:version_options].key?(:cascade)
53
- !!options[:version_options][:cascade]
53
+ options[:version_options][:cascade]
54
54
  else
55
55
  true
56
56
  end
@@ -87,12 +87,12 @@ module Grape
87
87
  end
88
88
 
89
89
  def fail_with_invalid_accept_header!(message)
90
- fail Grape::Exceptions::InvalidAcceptHeader
90
+ raise Grape::Exceptions::InvalidAcceptHeader
91
91
  .new(message, error_headers)
92
92
  end
93
93
 
94
94
  def fail_with_invalid_version_header!(message)
95
- fail Grape::Exceptions::InvalidVersionHeader
95
+ raise Grape::Exceptions::InvalidVersionHeader
96
96
  .new(message, error_headers)
97
97
  end
98
98
 
@@ -159,7 +159,7 @@ module Grape
159
159
  # header, one can set the `:cascade` option to `false`.
160
160
  def cascade?
161
161
  if version_options && version_options.key?(:cascade)
162
- !!version_options[:cascade]
162
+ version_options[:cascade]
163
163
  else
164
164
  true
165
165
  end
@@ -22,11 +22,19 @@ module Grape
22
22
  end
23
23
 
24
24
  def uses_specific_format?
25
- !!(settings[:format] && Array(settings[:content_types]).size == 1)
25
+ if settings.key?(:format) && settings.key?(:content_types)
26
+ (settings[:format] && Array(settings[:content_types]).size == 1)
27
+ else
28
+ false
29
+ end
26
30
  end
27
31
 
28
32
  def uses_path_versioning?
29
- !!(settings[:version] && settings[:version_options][:using] == :path)
33
+ if settings.key?(:version) && settings[:version_options] && settings[:version_options].key?(:using)
34
+ (settings[:version] && settings[:version_options][:using] == :path)
35
+ else
36
+ false
37
+ end
30
38
  end
31
39
 
32
40
  def namespace?
@@ -2,7 +2,7 @@ module Grape
2
2
  class Request < Rack::Request
3
3
  HTTP_PREFIX = 'HTTP_'.freeze
4
4
 
5
- alias_method :rack_params, :params
5
+ alias rack_params params
6
6
 
7
7
  def params
8
8
  @params ||= build_params
@@ -11,6 +11,14 @@ module Grape
11
11
  end
12
12
  end
13
13
 
14
+ def self.normalize_path(path)
15
+ path = "/#{path}"
16
+ path.squeeze!('/')
17
+ path.sub!(%r{/+\Z}, '')
18
+ path = '/' if path == ''
19
+ path
20
+ end
21
+
14
22
  def initialize
15
23
  @neutral_map = []
16
24
  @map = Hash.new { |hash, key| hash[key] = [] }
@@ -125,17 +133,8 @@ module Grape
125
133
  end
126
134
 
127
135
  def method_not_allowed(env, methods, endpoint)
128
- env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED] = true
129
- current = endpoint.dup
130
- current.instance_eval do
131
- @lazy_initialized = false
132
- lazy_initialize!
133
- run_filters befores, :before
134
- @block = proc do
135
- fail Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => methods)
136
- end
137
- end
138
- current.call(env)
136
+ env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED] = methods
137
+ endpoint.call(env)
139
138
  end
140
139
 
141
140
  def cascade?(response)
@@ -149,13 +148,5 @@ module Grape
149
148
  def string_for(input)
150
149
  self.class.normalize_path(input)
151
150
  end
152
-
153
- def self.normalize_path(path)
154
- path = "/#{path}"
155
- path.squeeze!('/')
156
- path.sub!(%r{/+\Z}, '')
157
- path = '/' if path == ''
158
- path
159
- end
160
151
  end
161
152
  end
@@ -12,7 +12,7 @@ module Grape
12
12
  extend Forwardable
13
13
  def_delegators :pattern, :named_captures, :params
14
14
  def_delegators :@regexp, :===
15
- alias_method :match?, :===
15
+ alias match? ===
16
16
 
17
17
  def initialize(pattern, options = {})
18
18
  @origin = pattern
@@ -30,7 +30,7 @@ module Grape
30
30
 
31
31
  def pattern_options
32
32
  options = DEFAULT_PATTERN_OPTIONS.dup
33
- options.merge!(capture: capture) if capture.present?
33
+ options[:capture] = capture if capture.present?
34
34
  options
35
35
  end
36
36