grape 0.2.0 → 0.2.1
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.
- data/CHANGELOG.markdown +68 -0
- data/README.markdown +274 -18
- data/lib/grape.rb +1 -0
- data/lib/grape/api.rb +8 -3
- data/lib/grape/endpoint.rb +53 -6
- data/lib/grape/entity.rb +88 -1
- data/lib/grape/middleware/base.rb +4 -4
- data/lib/grape/middleware/error.rb +2 -2
- data/lib/grape/middleware/formatter.rb +13 -14
- data/lib/grape/middleware/versioner.rb +2 -0
- data/lib/grape/middleware/versioner/param.rb +44 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +166 -95
- data/spec/grape/endpoint_spec.rb +108 -3
- data/spec/grape/entity_spec.rb +74 -3
- data/spec/grape/middleware/formatter_spec.rb +6 -6
- data/spec/grape/middleware/versioner/param_spec.rb +58 -0
- data/spec/grape/middleware/versioner_spec.rb +4 -0
- data/spec/support/versioned_helpers.rb +9 -1
- metadata +31 -27
data/lib/grape.rb
CHANGED
data/lib/grape/api.rb
CHANGED
@@ -208,9 +208,14 @@ module Grape
|
|
208
208
|
# end
|
209
209
|
# end
|
210
210
|
# end
|
211
|
-
def helpers(
|
212
|
-
if block_given? ||
|
213
|
-
mod
|
211
|
+
def helpers(new_mod = nil, &block)
|
212
|
+
if block_given? || new_mod
|
213
|
+
mod = settings.peek[:helpers] || Module.new
|
214
|
+
if new_mod
|
215
|
+
mod.class_eval do
|
216
|
+
include new_mod
|
217
|
+
end
|
218
|
+
end
|
214
219
|
mod.class_eval &block if block_given?
|
215
220
|
set(:helpers, mod)
|
216
221
|
else
|
data/lib/grape/endpoint.rb
CHANGED
@@ -52,7 +52,9 @@ module Grape
|
|
52
52
|
anchor = options[:route_options][:anchor]
|
53
53
|
anchor = anchor.nil? ? true : anchor
|
54
54
|
|
55
|
-
|
55
|
+
requirements = options[:route_options][:requirements] || {}
|
56
|
+
|
57
|
+
path = compile_path(prepared_path, anchor && !options[:app], requirements)
|
56
58
|
regex = Rack::Mount::RegexpWithNamedGroups.new(path)
|
57
59
|
path_params = {}
|
58
60
|
# named parameters in the api path
|
@@ -83,17 +85,17 @@ module Grape
|
|
83
85
|
parts << ':version' if settings[:version] && settings[:version_options][:using] == :path
|
84
86
|
parts << namespace.to_s if namespace
|
85
87
|
parts << path.to_s if path && '/' != path
|
86
|
-
parts.
|
87
|
-
Rack::Mount::Utils.normalize_path(parts.join('/'))
|
88
|
+
Rack::Mount::Utils.normalize_path(parts.join('/') + '(.:format)')
|
88
89
|
end
|
89
90
|
|
90
91
|
def namespace
|
91
92
|
Rack::Mount::Utils.normalize_path(settings.stack.map{|s| s[:namespace]}.join('/'))
|
92
93
|
end
|
93
94
|
|
94
|
-
def compile_path(prepared_path, anchor = true)
|
95
|
+
def compile_path(prepared_path, anchor = true, requirements = {})
|
95
96
|
endpoint_options = {}
|
96
97
|
endpoint_options[:version] = /#{settings[:version].join('|')}/ if settings[:version]
|
98
|
+
endpoint_options.merge!(requirements)
|
97
99
|
Rack::Mount::Strexp.compile(prepared_path, endpoint_options, %w( / . ? ), anchor)
|
98
100
|
end
|
99
101
|
|
@@ -115,7 +117,26 @@ module Grape
|
|
115
117
|
# The parameters passed into the request as
|
116
118
|
# well as parsed from URL segments.
|
117
119
|
def params
|
118
|
-
@params ||= Hashie::Mash.new.
|
120
|
+
@params ||= Hashie::Mash.new.
|
121
|
+
deep_merge(request.params).
|
122
|
+
deep_merge(env['rack.routing_args'] || {}).
|
123
|
+
deep_merge(self.body_params)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Pull out request body params if the content type matches and we're on a POST or PUT
|
127
|
+
def body_params
|
128
|
+
if ['POST', 'PUT'].include?(request.request_method.to_s.upcase)
|
129
|
+
return case env['CONTENT_TYPE']
|
130
|
+
when 'application/json'
|
131
|
+
MultiJson.decode(request.body.read)
|
132
|
+
when 'application/xml'
|
133
|
+
MultiXml.parse(request.body.read)
|
134
|
+
else
|
135
|
+
{}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
{}
|
119
140
|
end
|
120
141
|
|
121
142
|
# The API version as specified in the URL.
|
@@ -130,6 +151,26 @@ module Grape
|
|
130
151
|
throw :error, :message => message, :status => status
|
131
152
|
end
|
132
153
|
|
154
|
+
# Redirect to a new url.
|
155
|
+
#
|
156
|
+
# @param url [String] The url to be redirect.
|
157
|
+
# @param options [Hash] The options used when redirect.
|
158
|
+
# :permanent, default true.
|
159
|
+
def redirect(url, options = {})
|
160
|
+
merged_options = {:permanent => false }.merge(options)
|
161
|
+
if merged_options[:permanent]
|
162
|
+
status 304
|
163
|
+
else
|
164
|
+
if env['HTTP_VERSION'] == 'HTTP/1.1' && request.request_method.to_s.upcase != "GET"
|
165
|
+
status 303
|
166
|
+
else
|
167
|
+
status 302
|
168
|
+
end
|
169
|
+
end
|
170
|
+
header "Location", url
|
171
|
+
body ""
|
172
|
+
end
|
173
|
+
|
133
174
|
# Set or retrieve the HTTP status code.
|
134
175
|
#
|
135
176
|
# @param status [Integer] The HTTP Status Code to return for this request.
|
@@ -156,7 +197,12 @@ module Grape
|
|
156
197
|
@header
|
157
198
|
end
|
158
199
|
end
|
159
|
-
|
200
|
+
|
201
|
+
# Set response content-type
|
202
|
+
def content_type(val)
|
203
|
+
header('Content-Type', val)
|
204
|
+
end
|
205
|
+
|
160
206
|
# Set or get a cookie
|
161
207
|
#
|
162
208
|
# @example
|
@@ -255,6 +301,7 @@ module Grape
|
|
255
301
|
def build_middleware
|
256
302
|
b = Rack::Builder.new
|
257
303
|
|
304
|
+
b.use Rack::Head
|
258
305
|
b.use Grape::Middleware::Error,
|
259
306
|
:default_status => settings[:default_error_status] || 403,
|
260
307
|
:rescue_all => settings[:rescue_all],
|
data/lib/grape/entity.rb
CHANGED
@@ -3,7 +3,8 @@ require 'hashie'
|
|
3
3
|
module Grape
|
4
4
|
# An Entity is a lightweight structure that allows you to easily
|
5
5
|
# represent data from your application in a consistent and abstracted
|
6
|
-
# way in your API.
|
6
|
+
# way in your API. Entities can also provide documentation for the
|
7
|
+
# fields exposed.
|
7
8
|
#
|
8
9
|
# @example Entity Definition
|
9
10
|
#
|
@@ -11,6 +12,7 @@ module Grape
|
|
11
12
|
# module Entities
|
12
13
|
# class User < Grape::Entity
|
13
14
|
# expose :first_name, :last_name, :screen_name, :location
|
15
|
+
# expose :field, :documentation => {:type => "string", :desc => "describe the field"}
|
14
16
|
# expose :latest_status, :using => API::Status, :as => :status, :unless => {:collection => true}
|
15
17
|
# expose :email, :if => {:type => :full}
|
16
18
|
# expose :new_attribute, :if => {:version => 'v2'}
|
@@ -30,6 +32,7 @@ module Grape
|
|
30
32
|
# class Users < Grape::API
|
31
33
|
# version 'v2'
|
32
34
|
#
|
35
|
+
# desc 'User index', { :object_fields => API::Entities::User.documentation }
|
33
36
|
# get '/users' do
|
34
37
|
# @users = User.all
|
35
38
|
# type = current_user.admin? ? :full : :default
|
@@ -63,6 +66,8 @@ module Grape
|
|
63
66
|
# will be called with the represented object as well as the
|
64
67
|
# runtime options that were passed in. You can also just supply a
|
65
68
|
# block to the expose call to achieve the same effect.
|
69
|
+
# @option options :documentation Define documenation for an exposed
|
70
|
+
# field, typically the value is a hash with two fields, type and desc.
|
66
71
|
def self.expose(*args, &block)
|
67
72
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
68
73
|
|
@@ -71,6 +76,8 @@ module Grape
|
|
71
76
|
raise ArgumentError, "You may not use block-setting on multi-attribute exposures." if block_given?
|
72
77
|
end
|
73
78
|
|
79
|
+
raise ArgumentError, "You may not use block-setting when also using format_with" if block_given? && options[:format_with].respond_to?(:call)
|
80
|
+
|
74
81
|
options[:proc] = block if block_given?
|
75
82
|
|
76
83
|
args.each do |attribute|
|
@@ -91,6 +98,68 @@ module Grape
|
|
91
98
|
@exposures
|
92
99
|
end
|
93
100
|
|
101
|
+
# Returns a hash, the keys are symbolized references to fields in the entity,
|
102
|
+
# the values are document keys in the entity's documentation key. When calling
|
103
|
+
# #docmentation, any exposure without a documentation key will be ignored.
|
104
|
+
def self.documentation
|
105
|
+
@documentation ||= exposures.inject({}) do |memo, value|
|
106
|
+
unless value[1][:documentation].nil? || value[1][:documentation].empty?
|
107
|
+
memo[value[0]] = value[1][:documentation]
|
108
|
+
end
|
109
|
+
memo
|
110
|
+
end
|
111
|
+
|
112
|
+
if superclass.respond_to? :documentation
|
113
|
+
@documentation = superclass.documentation.merge(@documentation)
|
114
|
+
end
|
115
|
+
|
116
|
+
@documentation
|
117
|
+
end
|
118
|
+
|
119
|
+
# This allows you to declare a Proc in which exposures can be formatted with.
|
120
|
+
# It take a block with an arity of 1 which is passed as the value of the exposed attribute.
|
121
|
+
#
|
122
|
+
# @param name [Symbol] the name of the formatter
|
123
|
+
# @param block [Proc] the block that will interpret the exposed attribute
|
124
|
+
#
|
125
|
+
#
|
126
|
+
#
|
127
|
+
# @example Formatter declaration
|
128
|
+
#
|
129
|
+
# module API
|
130
|
+
# module Entities
|
131
|
+
# class User < Grape::Entity
|
132
|
+
# format_with :timestamp do |date|
|
133
|
+
# date.strftime('%m/%d/%Y')
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# expose :birthday, :last_signed_in, :format_with => :timestamp
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# @example Formatters are available to all decendants
|
142
|
+
#
|
143
|
+
# Grape::Entity.format_with :timestamp do |date|
|
144
|
+
# date.strftime('%m/%d/%Y')
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
def self.format_with(name, &block)
|
148
|
+
raise ArgumentError, "You must pass a block for formatters" unless block_given?
|
149
|
+
formatters[name.to_sym] = block
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns a hash of all formatters that are registered for this and it's ancestors.
|
153
|
+
def self.formatters
|
154
|
+
@formatters ||= {}
|
155
|
+
|
156
|
+
if superclass.respond_to? :formatters
|
157
|
+
@formatters = superclass.formatters.merge(@formatters)
|
158
|
+
end
|
159
|
+
|
160
|
+
@formatters
|
161
|
+
end
|
162
|
+
|
94
163
|
# This allows you to set a root element name for your representation.
|
95
164
|
#
|
96
165
|
# @param plural [String] the root key to use when representing
|
@@ -171,6 +240,14 @@ module Grape
|
|
171
240
|
self.class.exposures
|
172
241
|
end
|
173
242
|
|
243
|
+
def documentation
|
244
|
+
self.class.documentation
|
245
|
+
end
|
246
|
+
|
247
|
+
def formatters
|
248
|
+
self.class.formatters
|
249
|
+
end
|
250
|
+
|
174
251
|
# The serializable hash is the Entity's primary output. It is the transformed
|
175
252
|
# hash for the given data model and is used as the basis for serialization to
|
176
253
|
# JSON and other formats.
|
@@ -202,6 +279,16 @@ module Grape
|
|
202
279
|
exposure_options[:proc].call(object, options)
|
203
280
|
elsif exposure_options[:using]
|
204
281
|
exposure_options[:using].represent(object.send(attribute), :root => nil)
|
282
|
+
elsif exposure_options[:format_with]
|
283
|
+
format_with = exposure_options[:format_with]
|
284
|
+
|
285
|
+
if format_with.is_a?(Symbol) && formatters[format_with]
|
286
|
+
formatters[format_with].call(object.send(attribute))
|
287
|
+
elsif format_with.is_a?(Symbol)
|
288
|
+
self.send(format_with, object.send(attribute))
|
289
|
+
elsif format_with.respond_to? :call
|
290
|
+
format_with.call(object.send(attribute))
|
291
|
+
end
|
205
292
|
else
|
206
293
|
object.send(attribute)
|
207
294
|
end
|
@@ -107,20 +107,20 @@ module Grape
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def decode_json(object)
|
110
|
-
MultiJson.
|
110
|
+
MultiJson.load(object)
|
111
111
|
end
|
112
112
|
|
113
113
|
def encode_json(object)
|
114
114
|
return object if object.is_a?(String)
|
115
115
|
|
116
116
|
if object.respond_to? :serializable_hash
|
117
|
-
MultiJson.
|
117
|
+
MultiJson.dump(object.serializable_hash)
|
118
118
|
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
|
119
|
-
MultiJson.
|
119
|
+
MultiJson.dump(object.map {|o| o.serializable_hash })
|
120
120
|
elsif object.respond_to? :to_json
|
121
121
|
object.to_json
|
122
122
|
else
|
123
|
-
MultiJson.
|
123
|
+
MultiJson.dump(object)
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
@@ -24,11 +24,11 @@ module Grape
|
|
24
24
|
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
25
25
|
result = result.merge({ :backtrace => backtrace })
|
26
26
|
end
|
27
|
-
MultiJson.
|
27
|
+
MultiJson.dump(result)
|
28
28
|
end
|
29
29
|
|
30
30
|
def encode_txt(message, backtrace)
|
31
|
-
result = message.is_a?(Hash) ? MultiJson.
|
31
|
+
result = message.is_a?(Hash) ? MultiJson.dump(message) : message
|
32
32
|
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
33
33
|
result += "\r\n "
|
34
34
|
result += backtrace.join("\r\n ")
|
@@ -13,11 +13,11 @@ module Grape
|
|
13
13
|
:parsers => {}
|
14
14
|
}
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def headers
|
18
|
-
env.dup.inject({}){|h,(k,v)| h[k.downcase[5..-1]] = v if k.downcase.start_with?('http_'); h}
|
18
|
+
env.dup.inject({}){|h,(k,v)| h[k.to_s.downcase[5..-1]] = v if k.to_s.downcase.start_with?('http_'); h}
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def before
|
22
22
|
fmt = format_from_extension || options[:format] || format_from_header || options[:default_format]
|
23
23
|
if content_types.key?(fmt)
|
@@ -39,18 +39,17 @@ module Grape
|
|
39
39
|
throw :error, :status => 406, :message => 'The requested format is not supported.'
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
def format_from_extension
|
44
44
|
parts = request.path.split('.')
|
45
|
-
|
46
|
-
|
47
|
-
if parts.size
|
48
|
-
|
49
|
-
else
|
50
|
-
hit
|
45
|
+
extension = parts.last.to_sym
|
46
|
+
|
47
|
+
if parts.size > 1 && content_types.key?(extension)
|
48
|
+
return extension
|
51
49
|
end
|
50
|
+
nil
|
52
51
|
end
|
53
|
-
|
52
|
+
|
54
53
|
def format_from_header
|
55
54
|
mime_array.each do |t|
|
56
55
|
if mime_types.key?(t)
|
@@ -59,7 +58,7 @@ module Grape
|
|
59
58
|
end
|
60
59
|
nil
|
61
60
|
end
|
62
|
-
|
61
|
+
|
63
62
|
def mime_array
|
64
63
|
accept = headers['accept'] or return []
|
65
64
|
|
@@ -67,14 +66,14 @@ module Grape
|
|
67
66
|
mime.sub(%r(vnd\.[^+]+\+), '')
|
68
67
|
}
|
69
68
|
end
|
70
|
-
|
69
|
+
|
71
70
|
def after
|
72
71
|
status, headers, bodies = *@app_response
|
73
72
|
formatter = formatter_for env['api.format']
|
74
73
|
bodymap = bodies.collect do |body|
|
75
74
|
formatter.call(body)
|
76
75
|
end
|
77
|
-
headers['Content-Type'] = content_types[env['api.format']]
|
76
|
+
headers['Content-Type'] = content_types[env['api.format']] unless headers['Content-Type']
|
78
77
|
Rack::Response.new(bodymap, status, headers).to_a
|
79
78
|
end
|
80
79
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
# This middleware sets various version related rack environment variables
|
7
|
+
# based on the request parameters and removes that parameter from the
|
8
|
+
# request parameters for subsequent middleware and API.
|
9
|
+
# If the version substring does not match any potential initialized
|
10
|
+
# versions, a 404 error is thrown.
|
11
|
+
# If the version substring is not passed the version (highest mounted)
|
12
|
+
# version will be used.
|
13
|
+
#
|
14
|
+
# Example: For a uri path
|
15
|
+
# /resource?apiver=v1
|
16
|
+
#
|
17
|
+
# The following rack env variables are set and path is rewritten to
|
18
|
+
# '/resource':
|
19
|
+
#
|
20
|
+
# env['api.version'] => 'v1'
|
21
|
+
class Param < Base
|
22
|
+
def default_options
|
23
|
+
{
|
24
|
+
:parameter => "apiver"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def before
|
29
|
+
paramkey = options[:parameter]
|
30
|
+
potential_version = request.params[paramkey]
|
31
|
+
|
32
|
+
unless potential_version.nil?
|
33
|
+
if options[:versions] && !options[:versions].include?(potential_version)
|
34
|
+
throw :error, :status => 404, :message => "404 API Version Not Found", :headers => {'X-Cascade' => 'pass'}
|
35
|
+
end
|
36
|
+
env['api.version'] = potential_version
|
37
|
+
env['rack.request.query_hash'].delete(paramkey)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -31,6 +31,17 @@ describe Grape::API do
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
describe '.version using param' do
|
35
|
+
it_should_behave_like 'versioning' do
|
36
|
+
let(:macro_options) do
|
37
|
+
{
|
38
|
+
:using => :param,
|
39
|
+
:parameter => "apiver"
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
34
45
|
describe '.version using header' do
|
35
46
|
it_should_behave_like 'versioning' do
|
36
47
|
let(:macro_options) do
|
@@ -219,6 +230,10 @@ describe Grape::API do
|
|
219
230
|
"hiya"
|
220
231
|
end
|
221
232
|
|
233
|
+
subject.endpoints.first.routes.each do |route|
|
234
|
+
route.route_path.should eql '/abc(.:format)'
|
235
|
+
end
|
236
|
+
|
222
237
|
get '/abc'
|
223
238
|
last_response.body.should eql 'hiya'
|
224
239
|
post '/abc'
|
@@ -269,7 +284,7 @@ describe Grape::API do
|
|
269
284
|
verb
|
270
285
|
end
|
271
286
|
send(verb, '/example')
|
272
|
-
last_response.body.should eql verb
|
287
|
+
last_response.body.should eql verb == 'head' ? '' : verb
|
273
288
|
# Call it with a method other than the properly constrained one.
|
274
289
|
send(verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
|
275
290
|
last_response.status.should eql 404
|
@@ -480,6 +495,19 @@ describe Grape::API do
|
|
480
495
|
end
|
481
496
|
end
|
482
497
|
|
498
|
+
describe '.logger' do
|
499
|
+
it 'should return an instance of Logger class by default' do
|
500
|
+
subject.logger.class.should eql Logger
|
501
|
+
end
|
502
|
+
|
503
|
+
it 'should allow setting a custom logger' do
|
504
|
+
mylogger = Class.new
|
505
|
+
subject.logger mylogger
|
506
|
+
mylogger.should_receive(:info).exactly(1).times
|
507
|
+
subject.logger.info "this will be logged"
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
483
511
|
describe '.helpers' do
|
484
512
|
it 'should be accessible from the endpoint' do
|
485
513
|
subject.helpers do
|
@@ -560,6 +588,28 @@ describe Grape::API do
|
|
560
588
|
get '/howdy'
|
561
589
|
last_response.body.should eql 'Hello, world.'
|
562
590
|
end
|
591
|
+
|
592
|
+
it 'should allow multiple calls with modules and blocks' do
|
593
|
+
subject.helpers Module.new do
|
594
|
+
def one
|
595
|
+
1
|
596
|
+
end
|
597
|
+
end
|
598
|
+
subject.helpers Module.new do
|
599
|
+
def two
|
600
|
+
2
|
601
|
+
end
|
602
|
+
end
|
603
|
+
subject.helpers do
|
604
|
+
def three
|
605
|
+
3
|
606
|
+
end
|
607
|
+
end
|
608
|
+
subject.get 'howdy' do
|
609
|
+
[one, two, three]
|
610
|
+
end
|
611
|
+
lambda{get '/howdy'}.should_not raise_error
|
612
|
+
end
|
563
613
|
end
|
564
614
|
|
565
615
|
describe '.scope' do
|
@@ -656,7 +706,7 @@ describe Grape::API do
|
|
656
706
|
raise "rain!"
|
657
707
|
end
|
658
708
|
get '/exception'
|
659
|
-
json = MultiJson.
|
709
|
+
json = MultiJson.load(last_response.body)
|
660
710
|
json["error"].should eql 'rain!'
|
661
711
|
json["backtrace"].length.should > 0
|
662
712
|
end
|
@@ -681,12 +731,20 @@ describe Grape::API do
|
|
681
731
|
describe ".content_type" do
|
682
732
|
it "sets additional content-type" do
|
683
733
|
subject.content_type :xls, "application/vnd.ms-excel"
|
684
|
-
subject.get
|
734
|
+
subject.get :excel do
|
685
735
|
"some binary content"
|
686
736
|
end
|
687
|
-
get '/
|
737
|
+
get '/excel.xls'
|
688
738
|
last_response.content_type.should == "application/vnd.ms-excel"
|
689
739
|
end
|
740
|
+
it "allows to override content-type" do
|
741
|
+
subject.get :content do
|
742
|
+
content_type "text/javascript"
|
743
|
+
"var x = 1;"
|
744
|
+
end
|
745
|
+
get '/content'
|
746
|
+
last_response.content_type.should == "text/javascript"
|
747
|
+
end
|
690
748
|
end
|
691
749
|
|
692
750
|
describe ".default_error_status" do
|
@@ -730,16 +788,15 @@ describe Grape::API do
|
|
730
788
|
end
|
731
789
|
end
|
732
790
|
describe "api structure with two versions and a namespace" do
|
733
|
-
|
734
|
-
|
735
|
-
version
|
736
|
-
get "version" do
|
791
|
+
before :each do
|
792
|
+
subject.version 'v1', :using => :path
|
793
|
+
subject.get "version" do
|
737
794
|
api.version
|
738
795
|
end
|
739
796
|
# version v2
|
740
|
-
version 'v2', :using => :path
|
741
|
-
prefix 'p'
|
742
|
-
namespace "n1" do
|
797
|
+
subject.version 'v2', :using => :path
|
798
|
+
subject.prefix 'p'
|
799
|
+
subject.namespace "n1" do
|
743
800
|
namespace "n2" do
|
744
801
|
get "version" do
|
745
802
|
api.version
|
@@ -748,22 +805,22 @@ describe Grape::API do
|
|
748
805
|
end
|
749
806
|
end
|
750
807
|
it "should return versions" do
|
751
|
-
|
808
|
+
subject.versions.should == [ 'v1', 'v2' ]
|
752
809
|
end
|
753
810
|
it "should set route paths" do
|
754
|
-
|
755
|
-
|
756
|
-
|
811
|
+
subject.routes.size.should >= 2
|
812
|
+
subject.routes[0].route_path.should == "/:version/version(.:format)"
|
813
|
+
subject.routes[1].route_path.should == "/p/:version/n1/n2/version(.:format)"
|
757
814
|
end
|
758
815
|
it "should set route versions" do
|
759
|
-
|
760
|
-
|
816
|
+
subject.routes[0].route_version.should == 'v1'
|
817
|
+
subject.routes[1].route_version.should == 'v2'
|
761
818
|
end
|
762
819
|
it "should set a nested namespace" do
|
763
|
-
|
820
|
+
subject.routes[1].route_namespace.should == "/n1/n2"
|
764
821
|
end
|
765
822
|
it "should set prefix" do
|
766
|
-
|
823
|
+
subject.routes[1].route_prefix.should == 'p'
|
767
824
|
end
|
768
825
|
end
|
769
826
|
describe "api structure with additional parameters" do
|
@@ -781,87 +838,101 @@ describe Grape::API do
|
|
781
838
|
last_response.body.should == '["a","b,c"]'
|
782
839
|
end
|
783
840
|
it "should set route_params" do
|
784
|
-
subject.routes.
|
785
|
-
|
786
|
-
|
841
|
+
subject.routes.map { |route|
|
842
|
+
{ :params => route.route_params, :optional_params => route.route_optional_params }
|
843
|
+
}.should eq [
|
844
|
+
{ :params => { "string" => "", "token" => "a token" }, :optional_params => { "limit" => "the limit" } }
|
845
|
+
]
|
787
846
|
end
|
788
847
|
end
|
789
848
|
end
|
790
849
|
|
791
850
|
context "desc" do
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
851
|
+
it "empty array of routes" do
|
852
|
+
subject.routes.should == []
|
853
|
+
end
|
854
|
+
it "empty array of routes" do
|
855
|
+
subject.desc "grape api"
|
856
|
+
subject.routes.should == []
|
857
|
+
end
|
858
|
+
it "should describe a method" do
|
859
|
+
subject.desc "first method"
|
860
|
+
subject.get :first do ; end
|
861
|
+
subject.routes.length.should == 1
|
862
|
+
route = subject.routes.first
|
863
|
+
route.route_description.should == "first method"
|
864
|
+
route.route_foo.should be_nil
|
865
|
+
route.route_params.should == { }
|
866
|
+
end
|
867
|
+
it "should describe methods separately" do
|
868
|
+
subject.desc "first method"
|
869
|
+
subject.get :first do ; end
|
870
|
+
subject.desc "second method"
|
871
|
+
subject.get :second do ; end
|
872
|
+
subject.routes.count.should == 2
|
873
|
+
subject.routes.map { |route|
|
874
|
+
{ :description => route.route_description, :params => route.route_params }
|
875
|
+
}.should eq [
|
876
|
+
{ :description => "first method", :params => {} },
|
877
|
+
{ :description => "second method", :params => {} }
|
878
|
+
]
|
879
|
+
end
|
880
|
+
it "should reset desc" do
|
881
|
+
subject.desc "first method"
|
882
|
+
subject.get :first do ; end
|
883
|
+
subject.get :second do ; end
|
884
|
+
subject.routes.map { |route|
|
885
|
+
{ :description => route.route_description, :params => route.route_params }
|
886
|
+
}.should eq [
|
887
|
+
{ :description => "first method", :params => {} },
|
888
|
+
{ :description => nil, :params => {} }
|
889
|
+
]
|
890
|
+
end
|
891
|
+
it "should namespace and describe arbitrary parameters" do
|
892
|
+
subject.namespace "ns" do
|
893
|
+
desc "ns second", :foo => "bar"
|
894
|
+
get "second" do ; end
|
895
|
+
end
|
896
|
+
subject.routes.map { |route|
|
897
|
+
{ :description => route.route_description, :foo => route.route_foo, :params => route.route_params }
|
898
|
+
}.should eq [
|
899
|
+
{ :description => "ns second", :foo => "bar", :params => {} },
|
900
|
+
]
|
901
|
+
end
|
902
|
+
it "should include details" do
|
903
|
+
subject.desc "method", :details => "method details"
|
904
|
+
subject.get "method" do ; end
|
905
|
+
subject.routes.map { |route|
|
906
|
+
{ :description => route.route_description, :details => route.route_details, :params => route.route_params }
|
907
|
+
}.should eq [
|
908
|
+
{ :description => "method", :details => "method details", :params => {} },
|
909
|
+
]
|
910
|
+
end
|
911
|
+
it "should describe a method with parameters" do
|
912
|
+
subject.desc "Reverses a string.", { :params =>
|
913
|
+
{ "s" => { :desc => "string to reverse", :type => "string" }}
|
914
|
+
}
|
915
|
+
subject.get "reverse" do
|
916
|
+
params[:s].reverse
|
917
|
+
end
|
918
|
+
subject.routes.map { |route|
|
919
|
+
{ :description => route.route_description, :params => route.route_params }
|
920
|
+
}.should eq [
|
921
|
+
{ :description => "Reverses a string.", :params => { "s" => { :desc => "string to reverse", :type => "string" } } }
|
922
|
+
]
|
923
|
+
end
|
924
|
+
it "should not symbolize params" do
|
925
|
+
subject.desc "Reverses a string.", { :params =>
|
926
|
+
{ "s" => { :desc => "string to reverse", :type => "string" }}
|
927
|
+
}
|
928
|
+
subject.get "reverse/:s" do
|
929
|
+
params[:s].reverse
|
930
|
+
end
|
931
|
+
subject.routes.map { |route|
|
932
|
+
{ :description => route.route_description, :params => route.route_params }
|
933
|
+
}.should eq [
|
934
|
+
{ :description => "Reverses a string.", :params => { "s" => { :desc => "string to reverse", :type => "string" } } }
|
935
|
+
]
|
865
936
|
end
|
866
937
|
end
|
867
938
|
|