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
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module DSL
|
5
|
+
module Callbacks
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def before(&block)
|
10
|
+
imbue(:befores, [block])
|
11
|
+
end
|
12
|
+
|
13
|
+
def before_validation(&block)
|
14
|
+
imbue(:before_validations, [block])
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_validation(&block)
|
18
|
+
imbue(:after_validations, [block])
|
19
|
+
end
|
20
|
+
|
21
|
+
def after(&block)
|
22
|
+
imbue(:afters, [block])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module DSL
|
5
|
+
module Configuration
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
attr_writer :logger
|
10
|
+
attr_reader :settings
|
11
|
+
|
12
|
+
def logger(logger = nil)
|
13
|
+
if logger
|
14
|
+
@logger = logger
|
15
|
+
else
|
16
|
+
@logger ||= Logger.new($stdout)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add a description to the next namespace or function.
|
21
|
+
def desc(description, options = {})
|
22
|
+
@last_description = options.merge(description: description)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module DSL
|
5
|
+
module Helpers
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Add helper methods that will be accessible from any
|
10
|
+
# endpoint within this namespace (and child namespaces).
|
11
|
+
#
|
12
|
+
# When called without a block, all known helpers within this scope
|
13
|
+
# are included.
|
14
|
+
#
|
15
|
+
# @param [Module] new_mod optional module of methods to include
|
16
|
+
# @param [Block] block optional block of methods to include
|
17
|
+
#
|
18
|
+
# @example Define some helpers.
|
19
|
+
#
|
20
|
+
# class ExampleAPI < Grape::API
|
21
|
+
# helpers do
|
22
|
+
# def current_user
|
23
|
+
# User.find_by_id(params[:token])
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
def helpers(new_mod = nil, &block)
|
29
|
+
if block_given? || new_mod
|
30
|
+
mod = settings.peek[:helpers] || Module.new
|
31
|
+
if new_mod
|
32
|
+
inject_api_helpers_to_mod(new_mod) if new_mod.is_a?(BaseHelper)
|
33
|
+
mod.class_eval do
|
34
|
+
include new_mod
|
35
|
+
end
|
36
|
+
end
|
37
|
+
if block_given?
|
38
|
+
inject_api_helpers_to_mod(mod) do
|
39
|
+
mod.class_eval(&block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
set(:helpers, mod)
|
43
|
+
else
|
44
|
+
mod = Module.new
|
45
|
+
settings.stack.each do |s|
|
46
|
+
mod.send :include, s[:helpers] if s[:helpers]
|
47
|
+
end
|
48
|
+
change!
|
49
|
+
mod
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def inject_api_helpers_to_mod(mod, &block)
|
56
|
+
mod.extend(BaseHelper)
|
57
|
+
yield if block_given?
|
58
|
+
mod.api_changed(self)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# This module extends user defined helpers
|
63
|
+
# to provide some API-specific functionality
|
64
|
+
module BaseHelper
|
65
|
+
attr_accessor :api
|
66
|
+
def params(name, &block)
|
67
|
+
@named_params ||= {}
|
68
|
+
@named_params.merge! name => block
|
69
|
+
end
|
70
|
+
|
71
|
+
def api_changed(new_api)
|
72
|
+
@api = new_api
|
73
|
+
process_named_params
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def process_named_params
|
79
|
+
if @named_params && @named_params.any?
|
80
|
+
api.imbue(:named_params, @named_params)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module DSL
|
5
|
+
module InsideRoute
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# A filtering method that will return a hash
|
9
|
+
# consisting only of keys that have been declared by a
|
10
|
+
# `params` statement against the current/target endpoint or parent
|
11
|
+
# namespaces
|
12
|
+
#
|
13
|
+
# @param params [Hash] The initial hash to filter. Usually this will just be `params`
|
14
|
+
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
|
15
|
+
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
|
16
|
+
# you want only to return params declared against the current/target endpoint
|
17
|
+
def declared(params, options = {}, declared_params = nil)
|
18
|
+
options[:include_missing] = true unless options.key?(:include_missing)
|
19
|
+
options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
|
20
|
+
if declared_params.nil?
|
21
|
+
declared_params = !options[:include_parent_namespaces] ? settings[:declared_params] :
|
22
|
+
settings.gather(:declared_params)
|
23
|
+
end
|
24
|
+
|
25
|
+
unless declared_params
|
26
|
+
raise ArgumentError, "Tried to filter for declared parameters but none exist."
|
27
|
+
end
|
28
|
+
|
29
|
+
if params.is_a? Array
|
30
|
+
params.map do |param|
|
31
|
+
declared(param || {}, options, declared_params)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
declared_params.inject({}) do |hash, key|
|
35
|
+
key = { key => nil } unless key.is_a? Hash
|
36
|
+
|
37
|
+
key.each_pair do |parent, children|
|
38
|
+
output_key = options[:stringify] ? parent.to_s : parent.to_sym
|
39
|
+
if params.key?(parent) || options[:include_missing]
|
40
|
+
hash[output_key] = if children
|
41
|
+
declared(params[parent] || {}, options, Array(children))
|
42
|
+
else
|
43
|
+
params[parent]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# The API version as specified in the URL.
|
54
|
+
def version
|
55
|
+
env['api.version']
|
56
|
+
end
|
57
|
+
|
58
|
+
# End the request and display an error to the
|
59
|
+
# end user with the specified message.
|
60
|
+
#
|
61
|
+
# @param message [String] The message to display.
|
62
|
+
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
|
63
|
+
def error!(message, status = nil, headers = nil)
|
64
|
+
self.status(status || settings[:default_error_status])
|
65
|
+
throw :error, message: message, status: self.status, headers: headers
|
66
|
+
end
|
67
|
+
|
68
|
+
# Redirect to a new url.
|
69
|
+
#
|
70
|
+
# @param url [String] The url to be redirect.
|
71
|
+
# @param options [Hash] The options used when redirect.
|
72
|
+
# :permanent, default false.
|
73
|
+
def redirect(url, options = {})
|
74
|
+
merged_options = { permanent: false }.merge(options)
|
75
|
+
if merged_options[:permanent]
|
76
|
+
status 301
|
77
|
+
else
|
78
|
+
if env['HTTP_VERSION'] == 'HTTP/1.1' && request.request_method.to_s.upcase != "GET"
|
79
|
+
status 303
|
80
|
+
else
|
81
|
+
status 302
|
82
|
+
end
|
83
|
+
end
|
84
|
+
header "Location", url
|
85
|
+
body ""
|
86
|
+
end
|
87
|
+
|
88
|
+
# Set or retrieve the HTTP status code.
|
89
|
+
#
|
90
|
+
# @param status [Integer] The HTTP Status Code to return for this request.
|
91
|
+
def status(status = nil)
|
92
|
+
if status
|
93
|
+
@status = status
|
94
|
+
else
|
95
|
+
return @status if @status
|
96
|
+
case request.request_method.to_s.upcase
|
97
|
+
when 'POST'
|
98
|
+
201
|
99
|
+
else
|
100
|
+
200
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set an individual header or retrieve
|
106
|
+
# all headers that have been set.
|
107
|
+
def header(key = nil, val = nil)
|
108
|
+
if key
|
109
|
+
val ? @header[key.to_s] = val : @header.delete(key.to_s)
|
110
|
+
else
|
111
|
+
@header
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Set response content-type
|
116
|
+
def content_type(val = nil)
|
117
|
+
if val
|
118
|
+
header('Content-Type', val)
|
119
|
+
else
|
120
|
+
header['Content-Type']
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Set or get a cookie
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# cookies[:mycookie] = 'mycookie val'
|
128
|
+
# cookies['mycookie-string'] = 'mycookie string val'
|
129
|
+
# cookies[:more] = { value: '123', expires: Time.at(0) }
|
130
|
+
# cookies.delete :more
|
131
|
+
#
|
132
|
+
def cookies
|
133
|
+
@cookies ||= Cookies.new
|
134
|
+
end
|
135
|
+
|
136
|
+
# Allows you to define the response body as something other than the
|
137
|
+
# return value.
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# get '/body' do
|
141
|
+
# body "Body"
|
142
|
+
# "Not the Body"
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# GET /body # => "Body"
|
146
|
+
def body(value = nil)
|
147
|
+
if value
|
148
|
+
@body = value
|
149
|
+
else
|
150
|
+
@body
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Allows you to make use of Grape Entities by setting
|
155
|
+
# the response body to the serializable hash of the
|
156
|
+
# entity provided in the `:with` option. This has the
|
157
|
+
# added benefit of automatically passing along environment
|
158
|
+
# and version information to the serialization, making it
|
159
|
+
# very easy to do conditional exposures. See Entity docs
|
160
|
+
# for more info.
|
161
|
+
#
|
162
|
+
# @example
|
163
|
+
#
|
164
|
+
# get '/users/:id' do
|
165
|
+
# present User.find(params[:id]),
|
166
|
+
# with: API::Entities::User,
|
167
|
+
# admin: current_user.admin?
|
168
|
+
# end
|
169
|
+
def present(*args)
|
170
|
+
options = args.count > 1 ? args.extract_options! : {}
|
171
|
+
key, object = if args.count == 2 && args.first.is_a?(Symbol)
|
172
|
+
args
|
173
|
+
else
|
174
|
+
[nil, args.first]
|
175
|
+
end
|
176
|
+
entity_class = entity_class_for_obj(object, options)
|
177
|
+
|
178
|
+
root = options.delete(:root)
|
179
|
+
|
180
|
+
representation = if entity_class
|
181
|
+
embeds = { env: env }
|
182
|
+
embeds[:version] = env['api.version'] if env['api.version']
|
183
|
+
entity_class.represent(object, embeds.merge(options))
|
184
|
+
else
|
185
|
+
object
|
186
|
+
end
|
187
|
+
|
188
|
+
representation = { root => representation } if root
|
189
|
+
representation = (@body || {}).merge(key => representation) if key
|
190
|
+
body representation
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns route information for the current request.
|
194
|
+
#
|
195
|
+
# @example
|
196
|
+
#
|
197
|
+
# desc "Returns the route description."
|
198
|
+
# get '/' do
|
199
|
+
# route.route_description
|
200
|
+
# end
|
201
|
+
def route
|
202
|
+
env["rack.routing_args"][:route_info]
|
203
|
+
end
|
204
|
+
|
205
|
+
def entity_class_for_obj(object, options)
|
206
|
+
entity_class = options.delete(:with)
|
207
|
+
|
208
|
+
if entity_class.nil?
|
209
|
+
# entity class not explicitely defined, auto-detect from relation#klass or first object in the collection
|
210
|
+
object_class = if object.respond_to?(:klass)
|
211
|
+
object.klass
|
212
|
+
else
|
213
|
+
object.respond_to?(:first) ? object.first.class : object.class
|
214
|
+
end
|
215
|
+
|
216
|
+
object_class.ancestors.each do |potential|
|
217
|
+
entity_class ||= (settings[:representations] || {})[potential]
|
218
|
+
end
|
219
|
+
|
220
|
+
entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
|
221
|
+
end
|
222
|
+
|
223
|
+
entity_class
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module DSL
|
5
|
+
module Middleware
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Apply a custom middleware to the API. Applies
|
10
|
+
# to the current namespace and any children, but
|
11
|
+
# not parents.
|
12
|
+
#
|
13
|
+
# @param middleware_class [Class] The class of the middleware you'd like
|
14
|
+
# to inject.
|
15
|
+
def use(middleware_class, *args, &block)
|
16
|
+
arr = [middleware_class, *args]
|
17
|
+
arr << block if block_given?
|
18
|
+
imbue(:middleware, [arr])
|
19
|
+
end
|
20
|
+
|
21
|
+
# Retrieve an array of the middleware classes
|
22
|
+
# and arguments that are currently applied to the
|
23
|
+
# application.
|
24
|
+
def middleware
|
25
|
+
settings.stack.inject([]) do |a, s|
|
26
|
+
a += s[:middleware] if s[:middleware]
|
27
|
+
a
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module DSL
|
5
|
+
module Parameters
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def use(*names)
|
9
|
+
named_params = @api.settings[:named_params] || {}
|
10
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
11
|
+
names.each do |name|
|
12
|
+
params_block = named_params.fetch(name) do
|
13
|
+
raise "Params :#{name} not found!"
|
14
|
+
end
|
15
|
+
instance_exec(options, ¶ms_block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias_method :use_scope, :use
|
19
|
+
alias_method :includes, :use
|
20
|
+
|
21
|
+
def requires(*attrs, &block)
|
22
|
+
orig_attrs = attrs.clone
|
23
|
+
|
24
|
+
opts = attrs.last.is_a?(Hash) ? attrs.pop : nil
|
25
|
+
|
26
|
+
if opts && opts[:using]
|
27
|
+
require_required_and_optional_fields(attrs.first, opts)
|
28
|
+
else
|
29
|
+
validate_attributes(attrs, opts, &block)
|
30
|
+
|
31
|
+
block_given? ? new_scope(orig_attrs, &block) :
|
32
|
+
push_declared_params(attrs)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def optional(*attrs, &block)
|
37
|
+
orig_attrs = attrs
|
38
|
+
|
39
|
+
validations = {}
|
40
|
+
validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
|
41
|
+
validations[:type] ||= Array if block_given?
|
42
|
+
validates(attrs, validations)
|
43
|
+
|
44
|
+
block_given? ? new_scope(orig_attrs, true, &block) :
|
45
|
+
push_declared_params(attrs)
|
46
|
+
end
|
47
|
+
|
48
|
+
def mutually_exclusive(*attrs)
|
49
|
+
validates(attrs, mutual_exclusion: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def exactly_one_of(*attrs)
|
53
|
+
validates(attrs, exactly_one_of: true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def at_least_one_of(*attrs)
|
57
|
+
validates(attrs, at_least_one_of: true)
|
58
|
+
end
|
59
|
+
|
60
|
+
def group(*attrs, &block)
|
61
|
+
requires(*attrs, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def params(params)
|
65
|
+
params = @parent.params(params) if @parent
|
66
|
+
if @element
|
67
|
+
if params.is_a?(Array)
|
68
|
+
params = params.flat_map { |el| el[@element] || {} }
|
69
|
+
elsif params.is_a?(Hash)
|
70
|
+
params = params[@element] || {}
|
71
|
+
else
|
72
|
+
params = {}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
params
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|