grape 0.8.0 → 0.9.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -2
  3. data/.rubocop_todo.yml +80 -0
  4. data/.travis.yml +2 -2
  5. data/CHANGELOG.md +21 -2
  6. data/Gemfile +1 -6
  7. data/Guardfile +1 -5
  8. data/README.md +110 -27
  9. data/Rakefile +1 -1
  10. data/UPGRADING.md +35 -0
  11. data/grape.gemspec +5 -2
  12. data/lib/grape.rb +20 -4
  13. data/lib/grape/api.rb +25 -467
  14. data/lib/grape/api/helpers.rb +7 -0
  15. data/lib/grape/dsl/callbacks.rb +27 -0
  16. data/lib/grape/dsl/configuration.rb +27 -0
  17. data/lib/grape/dsl/helpers.rb +86 -0
  18. data/lib/grape/dsl/inside_route.rb +227 -0
  19. data/lib/grape/dsl/middleware.rb +33 -0
  20. data/lib/grape/dsl/parameters.rb +79 -0
  21. data/lib/grape/dsl/request_response.rb +152 -0
  22. data/lib/grape/dsl/routing.rb +172 -0
  23. data/lib/grape/dsl/validations.rb +29 -0
  24. data/lib/grape/endpoint.rb +6 -226
  25. data/lib/grape/error_formatter/base.rb +28 -0
  26. data/lib/grape/error_formatter/json.rb +2 -0
  27. data/lib/grape/error_formatter/txt.rb +2 -0
  28. data/lib/grape/error_formatter/xml.rb +2 -0
  29. data/lib/grape/exceptions/base.rb +6 -0
  30. data/lib/grape/exceptions/validation.rb +3 -3
  31. data/lib/grape/exceptions/validation_errors.rb +19 -6
  32. data/lib/grape/locale/en.yml +5 -3
  33. data/lib/grape/middleware/auth/base.rb +28 -12
  34. data/lib/grape/middleware/auth/dsl.rb +35 -0
  35. data/lib/grape/middleware/auth/strategies.rb +24 -0
  36. data/lib/grape/middleware/auth/strategy_info.rb +15 -0
  37. data/lib/grape/validations.rb +3 -92
  38. data/lib/grape/validations/at_least_one_of.rb +25 -0
  39. data/lib/grape/validations/coerce.rb +2 -2
  40. data/lib/grape/validations/exactly_one_of.rb +2 -2
  41. data/lib/grape/validations/mutual_exclusion.rb +2 -2
  42. data/lib/grape/validations/presence.rb +1 -1
  43. data/lib/grape/validations/regexp.rb +1 -1
  44. data/lib/grape/validations/values.rb +1 -1
  45. data/lib/grape/version.rb +1 -1
  46. data/spec/grape/api/helpers_spec.rb +36 -0
  47. data/spec/grape/api_spec.rb +72 -19
  48. data/spec/grape/dsl/callbacks_spec.rb +44 -0
  49. data/spec/grape/dsl/configuration_spec.rb +37 -0
  50. data/spec/grape/dsl/helpers_spec.rb +54 -0
  51. data/spec/grape/dsl/inside_route_spec.rb +222 -0
  52. data/spec/grape/dsl/middleware_spec.rb +40 -0
  53. data/spec/grape/dsl/parameters_spec.rb +108 -0
  54. data/spec/grape/dsl/request_response_spec.rb +123 -0
  55. data/spec/grape/dsl/routing_spec.rb +132 -0
  56. data/spec/grape/dsl/validations_spec.rb +55 -0
  57. data/spec/grape/endpoint_spec.rb +60 -11
  58. data/spec/grape/entity_spec.rb +9 -4
  59. data/spec/grape/exceptions/validation_errors_spec.rb +31 -1
  60. data/spec/grape/middleware/auth/base_spec.rb +34 -0
  61. data/spec/grape/middleware/auth/dsl_spec.rb +53 -0
  62. data/spec/grape/middleware/auth/strategies_spec.rb +81 -0
  63. data/spec/grape/middleware/error_spec.rb +33 -1
  64. data/spec/grape/middleware/exception_spec.rb +13 -0
  65. data/spec/grape/validations/at_least_one_of_spec.rb +63 -0
  66. data/spec/grape/validations/exactly_one_of_spec.rb +1 -1
  67. data/spec/grape/validations/presence_spec.rb +159 -122
  68. data/spec/grape/validations/zh-CN.yml +1 -1
  69. data/spec/grape/validations_spec.rb +77 -15
  70. data/spec/spec_helper.rb +1 -0
  71. data/spec/support/endpoint_faker.rb +23 -0
  72. metadata +93 -15
  73. data/lib/grape/middleware/auth/basic.rb +0 -13
  74. data/lib/grape/middleware/auth/digest.rb +0 -13
  75. data/lib/grape/middleware/auth/oauth2.rb +0 -83
  76. data/spec/grape/middleware/auth/basic_spec.rb +0 -31
  77. data/spec/grape/middleware/auth/digest_spec.rb +0 -47
  78. data/spec/grape/middleware/auth/oauth2_spec.rb +0 -135
@@ -26,6 +26,34 @@ module Grape
26
26
  end
27
27
  end
28
28
  end
29
+
30
+ module_function
31
+
32
+ def present(message, env)
33
+ present_options = {}
34
+ present_options[:with] = message.delete(:with) if message.is_a?(Hash)
35
+
36
+ presenter = env['api.endpoint'].entity_class_for_obj(message, present_options)
37
+
38
+ unless presenter || env['rack.routing_args'].nil?
39
+ # env['api.endpoint'].route does not work when the error occurs within a middleware
40
+ # the Endpoint does not have a valid env at this moment
41
+ http_codes = env['rack.routing_args'][:route_info].route_http_codes || []
42
+ found_code = http_codes.find do |http_code|
43
+ (http_code[0].to_i == env['api.endpoint'].status) && http_code[2].respond_to?(:represent)
44
+ end
45
+
46
+ presenter = found_code[2] if found_code
47
+ end
48
+
49
+ if presenter
50
+ embeds = { env: env }
51
+ embeds[:version] = env['api.version'] if env['api.version']
52
+ message = presenter.represent(message, embeds).serializable_hash
53
+ end
54
+
55
+ message
56
+ end
29
57
  end
30
58
  end
31
59
  end
@@ -3,6 +3,8 @@ module Grape
3
3
  module Json
4
4
  class << self
5
5
  def call(message, backtrace, options = {}, env = nil)
6
+ message = Grape::ErrorFormatter::Base.present(message, env)
7
+
6
8
  result = message.is_a?(Hash) ? message : { error: message }
7
9
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
8
10
  result = result.merge(backtrace: backtrace)
@@ -3,6 +3,8 @@ module Grape
3
3
  module Txt
4
4
  class << self
5
5
  def call(message, backtrace, options = {}, env = nil)
6
+ message = Grape::ErrorFormatter::Base.present(message, env)
7
+
6
8
  result = message.is_a?(Hash) ? MultiJson.dump(message) : message
7
9
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
8
10
  result += "\r\n "
@@ -3,6 +3,8 @@ module Grape
3
3
  module Xml
4
4
  class << self
5
5
  def call(message, backtrace, options = {}, env = nil)
6
+ message = Grape::ErrorFormatter::Base.present(message, env)
7
+
6
8
  result = message.is_a?(Hash) ? message : { message: message }
7
9
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
8
10
  result = result.merge(backtrace: backtrace)
@@ -49,6 +49,12 @@ module Grape
49
49
  translate_message("#{key}.resolution", attributes)
50
50
  end
51
51
 
52
+ def translate_attributes(keys, options = {})
53
+ keys.map do |key|
54
+ translate("#{BASE_ATTRIBUTES_KEY}.#{key}", { default: key }.merge(options))
55
+ end.join(", ")
56
+ end
57
+
52
58
  def translate_attribute(key, options = {})
53
59
  translate("#{BASE_ATTRIBUTES_KEY}.#{key}", { default: key }.merge(options))
54
60
  end
@@ -3,11 +3,11 @@ require 'grape/exceptions/base'
3
3
  module Grape
4
4
  module Exceptions
5
5
  class Validation < Grape::Exceptions::Base
6
- attr_accessor :param
6
+ attr_accessor :params
7
7
 
8
8
  def initialize(args = {})
9
- raise "Param is missing:" unless args.key? :param
10
- @param = args[:param]
9
+ raise "Params are missing:" unless args.key? :params
10
+ @params = args[:params]
11
11
  args[:message] = translate_message(args[:message_key]) if args.key? :message_key
12
12
  super
13
13
  end
@@ -10,8 +10,8 @@ module Grape
10
10
  def initialize(args = {})
11
11
  @errors = {}
12
12
  args[:errors].each do |validation_error|
13
- @errors[validation_error.param] ||= []
14
- @errors[validation_error.param] << validation_error
13
+ @errors[validation_error.params] ||= []
14
+ @errors[validation_error.params] << validation_error
15
15
  end
16
16
  super message: full_messages.join(', '), status: 400
17
17
  end
@@ -24,17 +24,30 @@ module Grape
24
24
  end
25
25
  end
26
26
 
27
+ def as_json
28
+ errors.map do |k, v|
29
+ {
30
+ params: k,
31
+ messages: v.map(&:to_s)
32
+ }
33
+ end
34
+ end
35
+
36
+ def to_json
37
+ as_json.to_json
38
+ end
39
+
27
40
  private
28
41
 
29
42
  def full_messages
30
- map { |attribute, error| full_message(attribute, error) }.uniq
43
+ map { |attributes, error| full_message(attributes, error) }.uniq
31
44
  end
32
45
 
33
- def full_message(attribute, error)
46
+ def full_message(attributes, error)
34
47
  I18n.t(
35
48
  "grape.errors.format".to_sym,
36
- default: "%{attribute} %{message}",
37
- attribute: translate_attribute(attribute),
49
+ default: "%{attributes} %{message}",
50
+ attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes),
38
51
  message: error.message
39
52
  )
40
53
  end
@@ -1,7 +1,7 @@
1
1
  en:
2
2
  grape:
3
3
  errors:
4
- format: ! '%{attribute} %{message}'
4
+ format: ! '%{attributes} %{message}'
5
5
  messages:
6
6
  coerce: 'is invalid'
7
7
  presence: 'is missing'
@@ -10,7 +10,7 @@ en:
10
10
  missing_vendor_option:
11
11
  problem: 'missing :vendor option.'
12
12
  summary: 'when version using header, you must specify :vendor option. '
13
- resolution: "eg: version 'v1', :using => :header, :vendor => 'twitter'"
13
+ resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
14
14
  missing_mime_type:
15
15
  problem: 'missing mime type for %{new_format}'
16
16
  resolution:
@@ -29,4 +29,6 @@ en:
29
29
  unknown_options: 'unknown options: %{options}'
30
30
  incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
31
31
  mutual_exclusion: 'are mutually exclusive'
32
- exactly_one: "- exactly one parameter must be provided"
32
+ at_least_one: 'are missing, at least one parameter must be provided'
33
+ exactly_one: 'are missing, exactly one parameter must be provided'
34
+
@@ -3,25 +3,41 @@ require 'rack/auth/basic'
3
3
  module Grape
4
4
  module Middleware
5
5
  module Auth
6
- class Base < Grape::Middleware::Base
7
- attr_reader :authenticator
6
+ class Base
7
+ attr_accessor :options, :app, :env
8
8
 
9
- def initialize(app, options = {}, &authenticator)
10
- super(app, options)
11
- @authenticator = authenticator
9
+ def initialize(app, options = {})
10
+ @app = app
11
+ @options = options || {}
12
12
  end
13
13
 
14
- def base_request
15
- raise NotImplementedError, "You must implement base_request."
14
+ def context
15
+ env['api.endpoint']
16
16
  end
17
17
 
18
- def credentials
19
- base_request.provided? ? base_request.credentials : [nil, nil]
18
+ def call(env)
19
+ dup._call(env)
20
20
  end
21
21
 
22
- def before
23
- unless authenticator.call(*credentials)
24
- throw :error, status: 401, message: "API Authorization Failed."
22
+ def _call(env)
23
+ self.env = env
24
+
25
+ if options.key?(:type)
26
+ auth_proc = options[:proc]
27
+ auth_proc_context = context
28
+
29
+ strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
30
+
31
+ throw(:error, status: 401, message: 'API Authorization Failed.') unless strategy_info.present?
32
+
33
+ strategy = strategy_info.create(@app, options) do |*args|
34
+ auth_proc_context.instance_exec(*args, &auth_proc)
35
+ end
36
+
37
+ strategy.call(env)
38
+
39
+ else
40
+ app.call(env)
25
41
  end
26
42
  end
27
43
  end
@@ -0,0 +1,35 @@
1
+ require 'rack/auth/basic'
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Auth
6
+ module DSL
7
+ # Add an authentication type to the API. Currently
8
+ # only `:http_basic`, `:http_digest` are supported.
9
+ def auth(type = nil, options = {}, &block)
10
+ if type
11
+ set(:auth, { type: type.to_sym, proc: block }.merge(options))
12
+ use Grape::Middleware::Auth::Base, settings[:auth]
13
+ else
14
+ settings[:auth]
15
+ end
16
+ end
17
+
18
+ # Add HTTP Basic authorization to the API.
19
+ #
20
+ # @param [Hash] options A hash of options.
21
+ # @option options [String] :realm "API Authorization" The HTTP Basic realm.
22
+ def http_basic(options = {}, &block)
23
+ options[:realm] ||= "API Authorization"
24
+ auth :http_basic, options, &block
25
+ end
26
+
27
+ def http_digest(options = {}, &block)
28
+ options[:realm] ||= "API Authorization"
29
+ options[:opaque] ||= "secret"
30
+ auth :http_digest, options, &block
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module Grape
2
+ module Middleware
3
+ module Auth
4
+ module Strategies
5
+ module_function
6
+
7
+ def add(label, strategy, option_fetcher = ->(_) { [] })
8
+ auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)
9
+ end
10
+
11
+ def auth_strategies
12
+ @auth_strategies ||= {
13
+ http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) {[settings[:realm]] }),
14
+ http_digest: StrategyInfo.new(Rack::Auth::Digest::MD5, ->(settings) { [settings[:realm], settings[:opaque]] })
15
+ }
16
+ end
17
+
18
+ def [](label)
19
+ auth_strategies[label]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module Grape
2
+ module Middleware
3
+ module Auth
4
+ StrategyInfo = Struct.new(:auth_class, :settings_fetcher) do
5
+
6
+ def create(app, options, &block)
7
+ strategy_args = settings_fetcher.call(options)
8
+
9
+ auth_class.new(app, *strategy_args, &block)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -10,10 +10,6 @@ module Grape
10
10
  @attrs = Array(attrs)
11
11
  @required = required
12
12
  @scope = scope
13
-
14
- if options.is_a?(Hash) && !options.empty?
15
- raise Grape::Exceptions.UnknownOptions.new(options.keys)
16
- end
17
13
  end
18
14
 
19
15
  def validate!(params)
@@ -84,6 +80,8 @@ module Grape
84
80
  class ParamsScope
85
81
  attr_accessor :element, :parent
86
82
 
83
+ include Grape::DSL::Parameters
84
+
87
85
  def initialize(opts, &block)
88
86
  @element = opts[:element]
89
87
  @parent = opts[:parent]
@@ -92,7 +90,7 @@ module Grape
92
90
  @type = opts[:type]
93
91
  @declared_params = []
94
92
 
95
- instance_eval(&block)
93
+ instance_eval(&block) if block_given?
96
94
 
97
95
  configure_declared_params
98
96
  end
@@ -103,72 +101,6 @@ module Grape
103
101
  parent.should_validate?(parameters)
104
102
  end
105
103
 
106
- def requires(*attrs, &block)
107
- orig_attrs = attrs.clone
108
-
109
- opts = attrs.last.is_a?(Hash) ? attrs.pop : nil
110
-
111
- if opts && opts[:using]
112
- require_required_and_optional_fields(attrs.first, opts)
113
- else
114
- validate_attributes(attrs, opts, &block)
115
-
116
- block_given? ? new_scope(orig_attrs, &block) :
117
- push_declared_params(attrs)
118
- end
119
- end
120
-
121
- def optional(*attrs, &block)
122
- orig_attrs = attrs
123
-
124
- validations = {}
125
- validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
126
- validations[:type] ||= Array if block_given?
127
- validates(attrs, validations)
128
-
129
- block_given? ? new_scope(orig_attrs, true, &block) :
130
- push_declared_params(attrs)
131
- end
132
-
133
- def mutually_exclusive(*attrs)
134
- validates(attrs, mutual_exclusion: true)
135
- end
136
-
137
- def exactly_one_of(*attrs)
138
- validates(attrs, exactly_one_of: true)
139
- end
140
-
141
- def group(*attrs, &block)
142
- requires(*attrs, &block)
143
- end
144
-
145
- def params(params)
146
- params = @parent.params(params) if @parent
147
- if @element
148
- if params.is_a?(Array)
149
- params = params.flat_map { |el| el[@element] || {} }
150
- elsif params.is_a?(Hash)
151
- params = params[@element] || {}
152
- else
153
- params = {}
154
- end
155
- end
156
- params
157
- end
158
-
159
- def use(*names)
160
- named_params = @api.settings[:named_params] || {}
161
- options = names.last.is_a?(Hash) ? names.pop : {}
162
- names.each do |name|
163
- params_block = named_params.fetch(name) do
164
- raise "Params :#{name} not found!"
165
- end
166
- instance_exec(options, &params_block)
167
- end
168
- end
169
- alias_method :use_scope, :use
170
- alias_method :includes, :use
171
-
172
104
  def full_name(name)
173
105
  return "#{@parent.full_name(@element)}[#{name}]" if @parent
174
106
  name.to_s
@@ -292,27 +224,6 @@ module Grape
292
224
  end
293
225
  end
294
226
  end
295
-
296
- # This module is mixed into the API Class.
297
- module ClassMethods
298
- def reset_validations!
299
- settings.peek[:declared_params] = []
300
- settings.peek[:validations] = []
301
- end
302
-
303
- def params(&block)
304
- ParamsScope.new(api: self, type: Hash, &block)
305
- end
306
-
307
- def document_attribute(names, opts)
308
- @last_description ||= {}
309
- @last_description[:params] ||= {}
310
- Array(names).each do |name|
311
- @last_description[:params][name[:full_name].to_s] ||= {}
312
- @last_description[:params][name[:full_name].to_s].merge!(opts)
313
- end
314
- end
315
- end
316
227
  end
317
228
  end
318
229
 
@@ -0,0 +1,25 @@
1
+ module Grape
2
+ module Validations
3
+ class AtLeastOneOfValidator < Validator
4
+ attr_reader :params
5
+
6
+ def validate!(params)
7
+ @params = params
8
+ if no_exclusive_params_are_present
9
+ raise Grape::Exceptions::Validation, params: attrs.map(&:to_s), message_key: :at_least_one
10
+ end
11
+ params
12
+ end
13
+
14
+ private
15
+
16
+ def no_exclusive_params_are_present
17
+ keys_in_common.length == 0
18
+ end
19
+
20
+ def keys_in_common
21
+ (attrs.map(&:to_s) & params.stringify_keys.keys).map(&:to_s)
22
+ end
23
+ end
24
+ end
25
+ end