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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -2
- data/.rubocop_todo.yml +80 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.md +21 -2
- data/Gemfile +1 -6
- data/Guardfile +1 -5
- data/README.md +110 -27
- data/Rakefile +1 -1
- data/UPGRADING.md +35 -0
- data/grape.gemspec +5 -2
- data/lib/grape.rb +20 -4
- data/lib/grape/api.rb +25 -467
- data/lib/grape/api/helpers.rb +7 -0
- data/lib/grape/dsl/callbacks.rb +27 -0
- data/lib/grape/dsl/configuration.rb +27 -0
- data/lib/grape/dsl/helpers.rb +86 -0
- data/lib/grape/dsl/inside_route.rb +227 -0
- data/lib/grape/dsl/middleware.rb +33 -0
- data/lib/grape/dsl/parameters.rb +79 -0
- data/lib/grape/dsl/request_response.rb +152 -0
- data/lib/grape/dsl/routing.rb +172 -0
- data/lib/grape/dsl/validations.rb +29 -0
- data/lib/grape/endpoint.rb +6 -226
- data/lib/grape/error_formatter/base.rb +28 -0
- data/lib/grape/error_formatter/json.rb +2 -0
- data/lib/grape/error_formatter/txt.rb +2 -0
- data/lib/grape/error_formatter/xml.rb +2 -0
- data/lib/grape/exceptions/base.rb +6 -0
- data/lib/grape/exceptions/validation.rb +3 -3
- data/lib/grape/exceptions/validation_errors.rb +19 -6
- data/lib/grape/locale/en.yml +5 -3
- data/lib/grape/middleware/auth/base.rb +28 -12
- data/lib/grape/middleware/auth/dsl.rb +35 -0
- data/lib/grape/middleware/auth/strategies.rb +24 -0
- data/lib/grape/middleware/auth/strategy_info.rb +15 -0
- data/lib/grape/validations.rb +3 -92
- data/lib/grape/validations/at_least_one_of.rb +25 -0
- data/lib/grape/validations/coerce.rb +2 -2
- data/lib/grape/validations/exactly_one_of.rb +2 -2
- data/lib/grape/validations/mutual_exclusion.rb +2 -2
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +1 -1
- data/lib/grape/validations/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/helpers_spec.rb +36 -0
- data/spec/grape/api_spec.rb +72 -19
- data/spec/grape/dsl/callbacks_spec.rb +44 -0
- data/spec/grape/dsl/configuration_spec.rb +37 -0
- data/spec/grape/dsl/helpers_spec.rb +54 -0
- data/spec/grape/dsl/inside_route_spec.rb +222 -0
- data/spec/grape/dsl/middleware_spec.rb +40 -0
- data/spec/grape/dsl/parameters_spec.rb +108 -0
- data/spec/grape/dsl/request_response_spec.rb +123 -0
- data/spec/grape/dsl/routing_spec.rb +132 -0
- data/spec/grape/dsl/validations_spec.rb +55 -0
- data/spec/grape/endpoint_spec.rb +60 -11
- data/spec/grape/entity_spec.rb +9 -4
- data/spec/grape/exceptions/validation_errors_spec.rb +31 -1
- data/spec/grape/middleware/auth/base_spec.rb +34 -0
- data/spec/grape/middleware/auth/dsl_spec.rb +53 -0
- data/spec/grape/middleware/auth/strategies_spec.rb +81 -0
- data/spec/grape/middleware/error_spec.rb +33 -1
- data/spec/grape/middleware/exception_spec.rb +13 -0
- data/spec/grape/validations/at_least_one_of_spec.rb +63 -0
- data/spec/grape/validations/exactly_one_of_spec.rb +1 -1
- data/spec/grape/validations/presence_spec.rb +159 -122
- data/spec/grape/validations/zh-CN.yml +1 -1
- data/spec/grape/validations_spec.rb +77 -15
- data/spec/spec_helper.rb +1 -0
- data/spec/support/endpoint_faker.rb +23 -0
- metadata +93 -15
- data/lib/grape/middleware/auth/basic.rb +0 -13
- data/lib/grape/middleware/auth/digest.rb +0 -13
- data/lib/grape/middleware/auth/oauth2.rb +0 -83
- data/spec/grape/middleware/auth/basic_spec.rb +0 -31
- data/spec/grape/middleware/auth/digest_spec.rb +0 -47
- 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 :
|
6
|
+
attr_accessor :params
|
7
7
|
|
8
8
|
def initialize(args = {})
|
9
|
-
raise "
|
10
|
-
@
|
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.
|
14
|
-
@errors[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 { |
|
43
|
+
map { |attributes, error| full_message(attributes, error) }.uniq
|
31
44
|
end
|
32
45
|
|
33
|
-
def full_message(
|
46
|
+
def full_message(attributes, error)
|
34
47
|
I18n.t(
|
35
48
|
"grape.errors.format".to_sym,
|
36
|
-
default: "%{
|
37
|
-
|
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
|
data/lib/grape/locale/en.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
en:
|
2
2
|
grape:
|
3
3
|
errors:
|
4
|
-
format: ! '%{
|
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', :
|
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
|
-
|
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
|
7
|
-
|
6
|
+
class Base
|
7
|
+
attr_accessor :options, :app, :env
|
8
8
|
|
9
|
-
def initialize(app, options = {}
|
10
|
-
|
11
|
-
@
|
9
|
+
def initialize(app, options = {})
|
10
|
+
@app = app
|
11
|
+
@options = options || {}
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
14
|
+
def context
|
15
|
+
env['api.endpoint']
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def call(env)
|
19
|
+
dup._call(env)
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
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
|
data/lib/grape/validations.rb
CHANGED
@@ -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, ¶ms_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
|