grape 0.2.4 → 0.2.5
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 +10 -0
- data/Gemfile +0 -1
- data/README.markdown +59 -15
- data/lib/grape/api.rb +16 -1
- data/lib/grape/endpoint.rb +8 -22
- data/lib/grape/locale/en.yml +7 -0
- data/lib/grape/middleware/base.rb +2 -12
- data/lib/grape/middleware/formatter.rb +76 -48
- data/lib/grape/util/content_types.rb +19 -0
- data/lib/grape/validations.rb +25 -18
- data/lib/grape/validations/coerce.rb +11 -11
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +2 -2
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +78 -18
- data/spec/grape/endpoint_spec.rb +44 -11
- data/spec/grape/middleware/formatter_spec.rb +24 -3
- data/spec/grape/validations/coerce_spec.rb +15 -2
- data/spec/grape/validations/presence_spec.rb +9 -3
- data/spec/grape/validations/zh-CN.yml +9 -0
- metadata +32 -5
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
0.2.5 (01/10/2013)
|
2
|
+
==================
|
3
|
+
|
4
|
+
* Added support for custom parsers via `parser`, in addition to built-in multipart, JSON and XML parsers - [@dblock](http://github.com/dblock).
|
5
|
+
* Removed `body_params`, data sent via a POST or PUT with a supported content-type is merged into `params` - [@dblock](http://github.com/dblock).
|
6
|
+
* Setting `format` will automatically remove other content-types by calling `content_type` - [@dblock](http://github.com/dblock).
|
7
|
+
* Setting `content_type` will prevent any input data other than the matching content-type or any Rack-supported form and parseable media types (`application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`) from being parsed - [@dblock](http://github.com/dblock).
|
8
|
+
* [#305](https://github.com/intridea/grape/issues/305): Fix: presenting arrays of objects via `represent` or when auto-detecting an `Entity` constant in the objects being presented - [@brandonweiss](https://github.com/brandonweiss).
|
9
|
+
* [#306](https://github.com/intridea/grape/issues/306): Added i18n support for validation error messages - [@niedhui](https://github.com/niedhui).
|
10
|
+
|
1
11
|
0.2.4 (01/06/2013)
|
2
12
|
==================
|
3
13
|
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -10,6 +10,11 @@ content negotiation, versioning and much more.
|
|
10
10
|
|
11
11
|
[![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape)
|
12
12
|
|
13
|
+
## Stable Release
|
14
|
+
|
15
|
+
You're reading the documentation for the next release of Grape, which should be 0.2.5.
|
16
|
+
The current stable release is [0.2.4](https://github.com/intridea/grape/blob/v0.2.4/README.markdown).
|
17
|
+
|
13
18
|
## Project Tracking
|
14
19
|
|
15
20
|
* [Grape Google Group](http://groups.google.com/group/ruby-grape)
|
@@ -215,8 +220,8 @@ end
|
|
215
220
|
|
216
221
|
## Parameters
|
217
222
|
|
218
|
-
Request parameters are available through the `params` hash object. This includes `GET
|
219
|
-
along with any named parameters you specify in your route strings.
|
223
|
+
Request parameters are available through the `params` hash object. This includes `GET`, `POST`
|
224
|
+
and `PUT` parameters, along with any named parameters you specify in your route strings.
|
220
225
|
|
221
226
|
```ruby
|
222
227
|
get :public_timeline do
|
@@ -224,7 +229,8 @@ get :public_timeline do
|
|
224
229
|
end
|
225
230
|
```
|
226
231
|
|
227
|
-
Parameters are
|
232
|
+
Parameters are automatically populated from the request body on POST and PUT for form input, JSON and
|
233
|
+
XML content-types.
|
228
234
|
|
229
235
|
The request:
|
230
236
|
|
@@ -638,7 +644,7 @@ end
|
|
638
644
|
|
639
645
|
## API Formats
|
640
646
|
|
641
|
-
By default, Grape supports _XML_, _JSON_, and _TXT_ content-types. The default format is `:txt`.
|
647
|
+
By default, Grape supports _XML_, _JSON_, and _TXT_ content-types. The default format is `:txt`.
|
642
648
|
|
643
649
|
Serialization takes place automatically. For example, you do not have to call `to_json` in each JSON API implementation.
|
644
650
|
|
@@ -646,21 +652,23 @@ Your API can declare which types to support by using `content_type`. Response fo
|
|
646
652
|
is determined by the request's extension, an explicit `format` parameter in the query
|
647
653
|
string, or `Accept` header.
|
648
654
|
|
649
|
-
The following API will only respond to the JSON content-type
|
650
|
-
|
655
|
+
The following API will only respond to the JSON content-type and will not parse any other input than `application/json`,
|
656
|
+
'application/x-www-form-urlencoded', 'multipart/form-data', 'multipart/related' and 'multipart/mixed'. All other requests
|
657
|
+
will fail with an HTTP 406 error code.
|
651
658
|
|
652
659
|
```ruby
|
653
660
|
class Twitter::API < Grape::API
|
654
661
|
format :json
|
655
|
-
content_type :json, "application/json"
|
656
662
|
end
|
657
663
|
```
|
658
664
|
|
659
|
-
If you combine `format` with `rescue_from :all`, errors will be rendered using the same format.
|
665
|
+
If you combine `format` with `rescue_from :all`, errors will be rendered using the same format.
|
666
|
+
If you do not want this behavior, set the default error formatter with `default_error_formatter`.
|
660
667
|
|
661
668
|
```ruby
|
662
669
|
class Twitter::API < Grape::API
|
663
670
|
format :json
|
671
|
+
content_type :txt, "text/plain"
|
664
672
|
default_error_formatter :txt
|
665
673
|
end
|
666
674
|
```
|
@@ -696,7 +704,7 @@ Built-in formats are the following.
|
|
696
704
|
* `:txt`: use object's `to_txt` when available, otherwise `to_s`
|
697
705
|
* `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
|
698
706
|
|
699
|
-
Use `default_format` to set the fallback format when the format could not be determined from the `Accept` header.
|
707
|
+
Use `default_format` to set the fallback format when the format could not be determined from the `Accept` header.
|
700
708
|
See below for the order for choosing the API format.
|
701
709
|
|
702
710
|
```ruby
|
@@ -728,6 +736,42 @@ class API < Grape::API
|
|
728
736
|
end
|
729
737
|
```
|
730
738
|
|
739
|
+
## API Data Formats
|
740
|
+
|
741
|
+
Grape accepts and parses input data sent with the POST and PUT methods as described in the Parameters
|
742
|
+
section above. It also supports custom data formats. You must declare additional content-types via
|
743
|
+
`content_type` and optionally supply a parser via `parser` unless a parser is already available within
|
744
|
+
Grape to enable a custom format. Such a parser can be a function or a class.
|
745
|
+
|
746
|
+
Without a parser, data is available "as-is" and can be read with `env['rack.input'].read`.
|
747
|
+
|
748
|
+
The following example is a trivial parser that will assign any input with the "text/custom" content-type
|
749
|
+
to `:value`. The parameter will be available via `params[:value]` inside the API call.
|
750
|
+
|
751
|
+
```ruby
|
752
|
+
module CustomParser
|
753
|
+
def self.call(object, env)
|
754
|
+
{ :value => object.to_s }
|
755
|
+
end
|
756
|
+
end
|
757
|
+
```
|
758
|
+
|
759
|
+
```ruby
|
760
|
+
content_type :txt, "text/plain"
|
761
|
+
content_type :custom, "text/custom"
|
762
|
+
parser :custom, CustomParser
|
763
|
+
|
764
|
+
put "value" do
|
765
|
+
params[:value]
|
766
|
+
end
|
767
|
+
```
|
768
|
+
|
769
|
+
You can invoke the above API as follows.
|
770
|
+
|
771
|
+
```
|
772
|
+
curl -X PUT -d 'data' 'http://localhost:9292/value' -H Content-Type:text/custom -v
|
773
|
+
```
|
774
|
+
|
731
775
|
## Reusable Responses with Entities
|
732
776
|
|
733
777
|
Entities are a reusable means for converting Ruby objects to API responses.
|
@@ -817,10 +861,10 @@ module API
|
|
817
861
|
desc 'Statuses index', {
|
818
862
|
:object_fields => API::Entities::Status.documentation
|
819
863
|
}
|
820
|
-
get '/
|
864
|
+
get '/statuses' do
|
821
865
|
statuses = Status.all
|
822
866
|
type = current_user.admin? ? :full : :default
|
823
|
-
present
|
867
|
+
present statuses, with: API::Entities::Status, :type => type
|
824
868
|
end
|
825
869
|
end
|
826
870
|
end
|
@@ -927,9 +971,9 @@ class MyAPI < Grape::API
|
|
927
971
|
end
|
928
972
|
```
|
929
973
|
|
930
|
-
The current endpoint responding to the request is `self` within the API block
|
931
|
-
or `env['api.endpoint']` elsewhere. The endpoint has some interesting properties,
|
932
|
-
such as `source` which gives you access to the original code block of the API
|
974
|
+
The current endpoint responding to the request is `self` within the API block
|
975
|
+
or `env['api.endpoint']` elsewhere. The endpoint has some interesting properties,
|
976
|
+
such as `source` which gives you access to the original code block of the API
|
933
977
|
implementation. This can be particularly useful for building a logger middleware.
|
934
978
|
|
935
979
|
```ruby
|
@@ -956,7 +1000,7 @@ For instance when you're API needs to get part of an URL, for instance:
|
|
956
1000
|
|
957
1001
|
```ruby
|
958
1002
|
class TwitterAPI < Grape::API
|
959
|
-
namespace :
|
1003
|
+
namespace :statuses do
|
960
1004
|
get '/(*:status)', :anchor => false do
|
961
1005
|
|
962
1006
|
end
|
data/lib/grape/api.rb
CHANGED
@@ -3,6 +3,7 @@ require 'rack/auth/basic'
|
|
3
3
|
require 'rack/auth/digest/md5'
|
4
4
|
require 'logger'
|
5
5
|
require 'grape/util/deep_merge'
|
6
|
+
require 'grape/util/content_types'
|
6
7
|
|
7
8
|
module Grape
|
8
9
|
# The API class is the primary entry point for
|
@@ -129,7 +130,12 @@ module Grape
|
|
129
130
|
def format(new_format = nil)
|
130
131
|
if new_format
|
131
132
|
set(:format, new_format.to_sym)
|
133
|
+
# define the default error formatters
|
132
134
|
set(:default_error_formatter, Grape::ErrorFormatter::Base.formatter_for(new_format, {}))
|
135
|
+
# define a single mime type
|
136
|
+
mime_type = content_types[new_format.to_sym]
|
137
|
+
raise "missing mime type for #{new_format}" unless mime_type
|
138
|
+
settings.imbue(:content_types, new_format.to_sym => mime_type)
|
133
139
|
else
|
134
140
|
settings[:format]
|
135
141
|
end
|
@@ -140,6 +146,11 @@ module Grape
|
|
140
146
|
settings.imbue(:formatters, content_type.to_sym => new_formatter)
|
141
147
|
end
|
142
148
|
|
149
|
+
# Specify a custom parser for a content-type.
|
150
|
+
def parser(content_type, new_parser)
|
151
|
+
settings.imbue(:parsers, content_type.to_sym => new_parser)
|
152
|
+
end
|
153
|
+
|
143
154
|
# Specify a default error formatter.
|
144
155
|
def default_error_formatter(new_formatter = nil)
|
145
156
|
new_formatter ? set(:default_error_formatter, new_formatter) : settings[:default_error_formatter]
|
@@ -155,6 +166,11 @@ module Grape
|
|
155
166
|
settings.imbue(:content_types, key.to_sym => val)
|
156
167
|
end
|
157
168
|
|
169
|
+
# All available content types.
|
170
|
+
def content_types
|
171
|
+
Grape::ContentTypes.content_types_for(settings[:content_types])
|
172
|
+
end
|
173
|
+
|
158
174
|
# Specify the default status code for errors.
|
159
175
|
def default_error_status(new_status = nil)
|
160
176
|
new_status ? set(:default_error_status, new_status) : settings[:default_error_status]
|
@@ -283,7 +299,6 @@ module Grape
|
|
283
299
|
if app.respond_to?(:inherit_settings)
|
284
300
|
app.inherit_settings(settings.clone)
|
285
301
|
end
|
286
|
-
|
287
302
|
endpoints << Grape::Endpoint.new(settings.clone,
|
288
303
|
:method => :any,
|
289
304
|
:path => path,
|
data/lib/grape/endpoint.rb
CHANGED
@@ -159,8 +159,7 @@ module Grape
|
|
159
159
|
def params
|
160
160
|
@params ||= Hashie::Mash.new.
|
161
161
|
deep_merge(request.params).
|
162
|
-
deep_merge(env['rack.routing_args'] || {})
|
163
|
-
deep_merge(self.body_params)
|
162
|
+
deep_merge(env['rack.routing_args'] || {})
|
164
163
|
end
|
165
164
|
|
166
165
|
# A filtering method that will return a hash
|
@@ -185,23 +184,6 @@ module Grape
|
|
185
184
|
}
|
186
185
|
end
|
187
186
|
|
188
|
-
# Pull out request body params if the content type matches and we're on a POST or PUT
|
189
|
-
def body_params
|
190
|
-
if ['POST', 'PUT'].include?(request.request_method.to_s.upcase) && request.content_length.to_i > 0
|
191
|
-
return @body_params ||=
|
192
|
-
case env['CONTENT_TYPE']
|
193
|
-
when 'application/json'
|
194
|
-
MultiJson.load(request.body.read)
|
195
|
-
when 'application/xml'
|
196
|
-
MultiXml.parse(request.body.read)
|
197
|
-
else
|
198
|
-
{}
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
{}
|
203
|
-
end
|
204
|
-
|
205
187
|
# The API version as specified in the URL.
|
206
188
|
def version; env['api.version'] end
|
207
189
|
|
@@ -314,11 +296,14 @@ module Grape
|
|
314
296
|
def present(object, options = {})
|
315
297
|
entity_class = options.delete(:with)
|
316
298
|
|
317
|
-
object
|
299
|
+
# auto-detect the entity from the first object in the collection
|
300
|
+
object_instance = object.is_a?(Array) ? object.first : object
|
301
|
+
|
302
|
+
object_instance.class.ancestors.each do |potential|
|
318
303
|
entity_class ||= (settings[:representations] || {})[potential]
|
319
304
|
end
|
320
305
|
|
321
|
-
entity_class ||=
|
306
|
+
entity_class ||= object_instance.class.const_get(:Entity) if object_instance.class.const_defined?(:Entity)
|
322
307
|
|
323
308
|
root = options.delete(:root)
|
324
309
|
|
@@ -410,7 +395,8 @@ module Grape
|
|
410
395
|
:format => settings[:format],
|
411
396
|
:default_format => settings[:default_format] || :txt,
|
412
397
|
:content_types => settings[:content_types],
|
413
|
-
:formatters => settings[:formatters]
|
398
|
+
:formatters => settings[:formatters],
|
399
|
+
:parsers => settings[:parsers]
|
414
400
|
|
415
401
|
aggregate_setting(:middleware).each do |m|
|
416
402
|
m = m.dup
|
@@ -1,21 +1,11 @@
|
|
1
|
-
require 'active_support/ordered_hash'
|
2
1
|
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'grape/util/content_types'
|
3
3
|
require 'multi_json'
|
4
4
|
require 'multi_xml'
|
5
5
|
|
6
6
|
module Grape
|
7
7
|
module Middleware
|
8
8
|
class Base
|
9
|
-
# Content types are listed in order of preference.
|
10
|
-
CONTENT_TYPES = ActiveSupport::OrderedHash[
|
11
|
-
:xml, 'application/xml',
|
12
|
-
:serializable_hash, 'application/json',
|
13
|
-
:json, 'application/json',
|
14
|
-
:atom, 'application/atom+xml',
|
15
|
-
:rss, 'application/rss+xml',
|
16
|
-
:txt, 'text/plain',
|
17
|
-
]
|
18
|
-
|
19
9
|
attr_reader :app, :env, :options
|
20
10
|
|
21
11
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
@@ -61,7 +51,7 @@ module Grape
|
|
61
51
|
end
|
62
52
|
|
63
53
|
def content_types
|
64
|
-
options[:content_types]
|
54
|
+
ContentTypes.content_types_for(options[:content_types])
|
65
55
|
end
|
66
56
|
|
67
57
|
def content_type
|
@@ -17,71 +17,99 @@ module Grape
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def before
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
negotiate_content_type
|
21
|
+
read_body_input
|
22
|
+
end
|
23
|
+
|
24
|
+
def after
|
25
|
+
status, headers, bodies = *@app_response
|
26
|
+
formatter = Grape::Formatter::Base.formatter_for env['api.format'], options
|
27
|
+
bodymap = bodies.collect do |body|
|
28
|
+
formatter.call body, env
|
29
|
+
end
|
30
|
+
headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
|
31
|
+
Rack::Response.new(bodymap, status, headers).to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def read_body_input
|
37
|
+
request_method = request.request_method.to_s.upcase
|
38
|
+
if [ 'POST', 'PUT' ].include?(request_method) && (! request.form_data?) && (request.content_length.to_i > 0)
|
39
|
+
if env['rack.input'] && (body = env['rack.input'].read).strip.length > 0
|
25
40
|
begin
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
41
|
+
fmt = mime_types[format_from_content_type]
|
42
|
+
if content_type_for(fmt)
|
43
|
+
parser = Grape::Parser::Base.parser_for fmt, options
|
44
|
+
unless parser.nil?
|
45
|
+
begin
|
46
|
+
body = parser.call body, env
|
47
|
+
env['rack.request.form_hash'] = env['rack.request.form_hash'] ? env['rack.request.form_hash'].merge(body) : body
|
48
|
+
env['rack.request.form_input'] = env['rack.input']
|
49
|
+
rescue
|
50
|
+
# It's possible that it's just regular POST content -- just back off
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
throw :error, :status => 406, :message => 'The requested content-type is not supported.'
|
55
|
+
end
|
56
|
+
ensure
|
57
|
+
env['rack.input'].rewind
|
31
58
|
end
|
32
59
|
end
|
33
|
-
env['rack.input'].rewind
|
34
60
|
end
|
35
|
-
env['api.format'] = fmt
|
36
|
-
else
|
37
|
-
throw :error, :status => 406, :message => 'The requested format is not supported.'
|
38
61
|
end
|
39
|
-
end
|
40
62
|
|
41
|
-
|
42
|
-
|
63
|
+
def negotiate_content_type
|
64
|
+
fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
|
65
|
+
if content_type_for(fmt)
|
66
|
+
env['api.format'] = fmt
|
67
|
+
else
|
68
|
+
throw :error, :status => 406, :message => 'The requested format is not supported.'
|
69
|
+
end
|
70
|
+
end
|
43
71
|
|
44
|
-
|
45
|
-
|
72
|
+
def format_from_content_type
|
73
|
+
fmt = env['CONTENT_TYPE']
|
46
74
|
# avoid symbol memory leak on an unknown format
|
47
|
-
return
|
75
|
+
return fmt.to_sym if content_type_for(fmt)
|
76
|
+
fmt
|
48
77
|
end
|
49
|
-
nil
|
50
|
-
end
|
51
78
|
|
52
|
-
|
53
|
-
|
54
|
-
# avoid symbol memory leak on an unknown format
|
55
|
-
return fmt.to_sym if content_type_for(fmt)
|
56
|
-
fmt
|
57
|
-
end
|
79
|
+
def format_from_extension
|
80
|
+
parts = request.path.split('.')
|
58
81
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
return
|
82
|
+
if parts.size > 1
|
83
|
+
extension = parts.last
|
84
|
+
# avoid symbol memory leak on an unknown format
|
85
|
+
return extension.to_sym if content_type_for(extension)
|
63
86
|
end
|
87
|
+
nil
|
64
88
|
end
|
65
|
-
nil
|
66
|
-
end
|
67
89
|
|
68
|
-
|
69
|
-
|
90
|
+
def format_from_params
|
91
|
+
fmt = Rack::Utils.parse_nested_query(env['QUERY_STRING'])["format"]
|
92
|
+
# avoid symbol memory leak on an unknown format
|
93
|
+
return fmt.to_sym if content_type_for(fmt)
|
94
|
+
fmt
|
95
|
+
end
|
70
96
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
97
|
+
def format_from_header
|
98
|
+
mime_array.each do |t|
|
99
|
+
if mime_types.key?(t)
|
100
|
+
return mime_types[t]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
nil
|
104
|
+
end
|
75
105
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
106
|
+
def mime_array
|
107
|
+
accept = headers['accept'] or return []
|
108
|
+
|
109
|
+
accept.gsub(/\b/,'').scan(%r((\w+/[\w+.-]+)(?:(?:;[^,]*?)?;\s*q=([\d.]+))?)).sort_by { |_, q| -q.to_f }.map {|mime, _|
|
110
|
+
mime.sub(%r(vnd\.[^+]+\+), '')
|
111
|
+
}
|
81
112
|
end
|
82
|
-
headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
|
83
|
-
Rack::Response.new(bodymap, status, headers).to_a
|
84
|
-
end
|
85
113
|
|
86
114
|
end
|
87
115
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'active_support/ordered_hash'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module ContentTypes
|
5
|
+
# Content types are listed in order of preference.
|
6
|
+
CONTENT_TYPES = ActiveSupport::OrderedHash[
|
7
|
+
:xml, 'application/xml',
|
8
|
+
:serializable_hash, 'application/json',
|
9
|
+
:json, 'application/json',
|
10
|
+
:atom, 'application/atom+xml',
|
11
|
+
:rss, 'application/rss+xml',
|
12
|
+
:txt, 'text/plain',
|
13
|
+
]
|
14
|
+
|
15
|
+
def self.content_types_for(from_settings)
|
16
|
+
from_settings || Grape::ContentTypes::CONTENT_TYPES
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/grape/validations.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'virtus'
|
2
|
+
require 'i18n'
|
2
3
|
|
4
|
+
I18n.load_path << File.expand_path('../locale/en.yml', __FILE__)
|
3
5
|
module Grape
|
4
|
-
|
6
|
+
|
5
7
|
module Validations
|
6
|
-
|
8
|
+
|
7
9
|
##
|
8
10
|
# All validators must inherit from this class.
|
9
|
-
#
|
11
|
+
#
|
10
12
|
class Validator
|
11
13
|
attr_reader :attrs
|
12
14
|
|
@@ -30,6 +32,11 @@ module Grape
|
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
35
|
+
def i18n_message(type, attribute)
|
36
|
+
i18n_attr = I18n.t("grape.errors.attributes.#{attribute}", :default => attribute.to_s)
|
37
|
+
I18n.t("grape.errors.messages.#{type}", :attribute => i18n_attr)
|
38
|
+
end
|
39
|
+
|
33
40
|
private
|
34
41
|
|
35
42
|
def self.convert_to_short_name(klass)
|
@@ -60,17 +67,17 @@ module Grape
|
|
60
67
|
Validations::register_validator(short_name, klass)
|
61
68
|
end
|
62
69
|
end
|
63
|
-
|
70
|
+
|
64
71
|
class << self
|
65
72
|
attr_accessor :validators
|
66
73
|
end
|
67
|
-
|
74
|
+
|
68
75
|
self.validators = {}
|
69
|
-
|
76
|
+
|
70
77
|
def self.register_validator(short_name, klass)
|
71
78
|
validators[short_name] = klass
|
72
79
|
end
|
73
|
-
|
80
|
+
|
74
81
|
class ParamsScope
|
75
82
|
attr_accessor :element, :parent
|
76
83
|
|
@@ -80,17 +87,17 @@ module Grape
|
|
80
87
|
@api = api
|
81
88
|
instance_eval(&block)
|
82
89
|
end
|
83
|
-
|
90
|
+
|
84
91
|
def requires(*attrs)
|
85
92
|
validations = {:presence => true}
|
86
93
|
if attrs.last.is_a?(Hash)
|
87
94
|
validations.merge!(attrs.pop)
|
88
95
|
end
|
89
|
-
|
96
|
+
|
90
97
|
push_declared_params(attrs)
|
91
98
|
validates(attrs, validations)
|
92
99
|
end
|
93
|
-
|
100
|
+
|
94
101
|
def optional(*attrs)
|
95
102
|
validations = {}
|
96
103
|
if attrs.last.is_a?(Hash)
|
@@ -119,16 +126,16 @@ module Grape
|
|
119
126
|
private
|
120
127
|
def validates(attrs, validations)
|
121
128
|
doc_attrs = { :required => validations.keys.include?(:presence) }
|
122
|
-
|
129
|
+
|
123
130
|
# special case (type = coerce)
|
124
131
|
if validations[:type]
|
125
132
|
validations[:coerce] = validations.delete(:type)
|
126
133
|
end
|
127
|
-
|
134
|
+
|
128
135
|
if coerce_type = validations[:coerce]
|
129
136
|
doc_attrs[:type] = coerce_type.to_s
|
130
137
|
end
|
131
|
-
|
138
|
+
|
132
139
|
if desc = validations.delete(:desc)
|
133
140
|
doc_attrs[:desc] = desc
|
134
141
|
end
|
@@ -169,18 +176,18 @@ module Grape
|
|
169
176
|
@api.settings[:declared_params] += attrs
|
170
177
|
end
|
171
178
|
end
|
172
|
-
|
179
|
+
|
173
180
|
# This module is mixed into the API Class.
|
174
181
|
module ClassMethods
|
175
182
|
def reset_validations!
|
176
183
|
settings.peek[:declared_params] = []
|
177
184
|
settings.peek[:validations] = []
|
178
185
|
end
|
179
|
-
|
186
|
+
|
180
187
|
def params(&block)
|
181
188
|
ParamsScope.new(self, nil, nil, &block)
|
182
189
|
end
|
183
|
-
|
190
|
+
|
184
191
|
def document_attribute(names, opts)
|
185
192
|
@last_description ||= {}
|
186
193
|
@last_description[:params] ||= {}
|
@@ -189,9 +196,9 @@ module Grape
|
|
189
196
|
@last_description[:params][name[:full_name].to_s].merge!(opts)
|
190
197
|
end
|
191
198
|
end
|
192
|
-
|
199
|
+
|
193
200
|
end
|
194
|
-
|
201
|
+
|
195
202
|
end
|
196
203
|
end
|
197
204
|
|
@@ -3,28 +3,28 @@ module Grape
|
|
3
3
|
class API
|
4
4
|
Boolean = Virtus::Attribute::Boolean
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
module Validations
|
8
|
-
|
8
|
+
|
9
9
|
class CoerceValidator < SingleOptionValidator
|
10
10
|
def validate_param!(attr_name, params)
|
11
11
|
new_value = coerce_value(@option, params[attr_name])
|
12
12
|
if valid_type?(new_value)
|
13
13
|
params[attr_name] = new_value
|
14
14
|
else
|
15
|
-
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message =>
|
15
|
+
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => i18n_message(:coerce, attr_name)
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
class InvalidValue; end
|
20
20
|
private
|
21
|
-
|
21
|
+
|
22
22
|
def _valid_array_type?(type, values)
|
23
23
|
values.all? do |val|
|
24
24
|
_valid_single_type?(type, val)
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def _valid_single_type?(klass, val)
|
29
29
|
# allow nil, to ignore when a parameter is absent
|
30
30
|
return true if val.nil?
|
@@ -36,7 +36,7 @@ module Grape
|
|
36
36
|
val.is_a?(klass)
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
def valid_type?(val)
|
41
41
|
if @option.is_a?(Array)
|
42
42
|
_valid_array_type?(@option[0], val)
|
@@ -44,18 +44,18 @@ module Grape
|
|
44
44
|
_valid_single_type?(@option, val)
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
def coerce_value(type, val)
|
49
49
|
converter = Virtus::Attribute.build(:a, type)
|
50
50
|
converter.coerce(val)
|
51
|
-
|
51
|
+
|
52
52
|
# not the prettiest but some invalid coercion can currently trigger
|
53
53
|
# errors in Virtus (see coerce_spec.rb)
|
54
54
|
rescue
|
55
55
|
InvalidValue.new
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
end
|
61
61
|
end
|
@@ -3,7 +3,7 @@ module Grape
|
|
3
3
|
class PresenceValidator < Validator
|
4
4
|
def validate_param!(attr_name, params)
|
5
5
|
unless params.has_key?(attr_name)
|
6
|
-
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message =>
|
6
|
+
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => i18n_message(:presence, attr_name)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
|
-
|
3
|
+
|
4
4
|
class RegexpValidator < SingleOptionValidator
|
5
5
|
def validate_param!(attr_name, params)
|
6
6
|
if params[attr_name] && !( params[attr_name].to_s =~ @option )
|
7
|
-
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message =>
|
7
|
+
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => i18n_message(:regexp, attr_name)
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -1078,6 +1078,44 @@ describe Grape::API do
|
|
1078
1078
|
end
|
1079
1079
|
end
|
1080
1080
|
|
1081
|
+
describe '.parser' do
|
1082
|
+
context 'lambda parser' do
|
1083
|
+
before :each do
|
1084
|
+
subject.content_type :txt, "text/plain"
|
1085
|
+
subject.content_type :custom, "text/custom"
|
1086
|
+
subject.parser :custom, lambda { |object, env| { object.to_sym => object.to_s.reverse } }
|
1087
|
+
subject.put :simple do
|
1088
|
+
params[:simple]
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
it 'uses parser' do
|
1092
|
+
put '/simple', "simple", "CONTENT_TYPE" => "text/custom"
|
1093
|
+
last_response.status.should == 200
|
1094
|
+
last_response.body.should eql "elpmis"
|
1095
|
+
end
|
1096
|
+
end
|
1097
|
+
context 'custom parser class' do
|
1098
|
+
module CustomParser
|
1099
|
+
def self.call(object, env)
|
1100
|
+
{ object.to_sym => object.to_s.reverse }
|
1101
|
+
end
|
1102
|
+
end
|
1103
|
+
before :each do
|
1104
|
+
subject.content_type :txt, "text/plain"
|
1105
|
+
subject.content_type :custom, "text/custom"
|
1106
|
+
subject.parser :custom, CustomParser
|
1107
|
+
subject.put :simple do
|
1108
|
+
params[:simple]
|
1109
|
+
end
|
1110
|
+
end
|
1111
|
+
it 'uses custom parser' do
|
1112
|
+
put '/simple', "simple", "CONTENT_TYPE" => "text/custom"
|
1113
|
+
last_response.status.should == 200
|
1114
|
+
last_response.body.should eql "elpmis"
|
1115
|
+
end
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
|
1081
1119
|
describe '.default_error_status' do
|
1082
1120
|
it 'allows setting default_error_status' do
|
1083
1121
|
subject.rescue_from :all
|
@@ -1270,10 +1308,10 @@ describe Grape::API do
|
|
1270
1308
|
subject.routes.map { |route|
|
1271
1309
|
{ :description => route.route_description, :params => route.route_params }
|
1272
1310
|
}.should eq [
|
1273
|
-
{ :description => "method",
|
1274
|
-
:params => {
|
1275
|
-
"ns_param" => { :required => true, :desc => "namespace parameter" },
|
1276
|
-
"method_param" => { :required => false, :desc => "method parameter" }
|
1311
|
+
{ :description => "method",
|
1312
|
+
:params => {
|
1313
|
+
"ns_param" => { :required => true, :desc => "namespace parameter" },
|
1314
|
+
"method_param" => { :required => false, :desc => "method parameter" }
|
1277
1315
|
}
|
1278
1316
|
}
|
1279
1317
|
]
|
@@ -1301,13 +1339,13 @@ describe Grape::API do
|
|
1301
1339
|
subject.routes.map { |route|
|
1302
1340
|
{ :description => route.route_description, :params => route.route_params }
|
1303
1341
|
}.should eq [
|
1304
|
-
{ :description => "method",
|
1305
|
-
:params => {
|
1306
|
-
"ns_param" => { :required => true, :desc => "ns param 2" },
|
1307
|
-
"ns1_param" => { :required => true, :desc => "ns1 param" },
|
1308
|
-
"ns2_param" => { :required => true, :desc => "ns2 param" },
|
1309
|
-
"method_param" => { :required => false, :desc => "method param" }
|
1310
|
-
}
|
1342
|
+
{ :description => "method",
|
1343
|
+
:params => {
|
1344
|
+
"ns_param" => { :required => true, :desc => "ns param 2" },
|
1345
|
+
"ns1_param" => { :required => true, :desc => "ns1 param" },
|
1346
|
+
"ns2_param" => { :required => true, :desc => "ns2 param" },
|
1347
|
+
"method_param" => { :required => false, :desc => "method param" }
|
1348
|
+
}
|
1311
1349
|
}
|
1312
1350
|
]
|
1313
1351
|
end
|
@@ -1325,12 +1363,12 @@ describe Grape::API do
|
|
1325
1363
|
end
|
1326
1364
|
subject.get "method" do ; end
|
1327
1365
|
|
1328
|
-
subject.routes.map { |route|
|
1366
|
+
subject.routes.map { |route|
|
1329
1367
|
route.route_params
|
1330
1368
|
}.should eq [{
|
1331
1369
|
"group1[param1]" => { :required => false, :desc => "group1 param1 desc" },
|
1332
1370
|
"group1[param2]" => { :required => true, :desc => "group1 param2 desc" },
|
1333
|
-
"group2[param1]" => { :required => false, :desc => "group2 param1 desc" },
|
1371
|
+
"group2[param1]" => { :required => false, :desc => "group2 param1 desc" },
|
1334
1372
|
"group2[param2]" => { :required => true, :desc => "group2 param2 desc" }
|
1335
1373
|
}]
|
1336
1374
|
end
|
@@ -1346,11 +1384,11 @@ describe Grape::API do
|
|
1346
1384
|
subject.routes.map { |route|
|
1347
1385
|
{ :description => route.route_description, :params => route.route_params }
|
1348
1386
|
}.should eq [
|
1349
|
-
{ :description => "nesting",
|
1350
|
-
:params => {
|
1351
|
-
"root_param" => { :required => true, :desc => "root param" },
|
1352
|
-
"nested[nested_param]" => { :required => true, :desc => "nested param" }
|
1353
|
-
}
|
1387
|
+
{ :description => "nesting",
|
1388
|
+
:params => {
|
1389
|
+
"root_param" => { :required => true, :desc => "root param" },
|
1390
|
+
"nested[nested_param]" => { :required => true, :desc => "nested param" }
|
1391
|
+
}
|
1354
1392
|
}
|
1355
1393
|
]
|
1356
1394
|
end
|
@@ -1550,6 +1588,7 @@ describe Grape::API do
|
|
1550
1588
|
context ':txt' do
|
1551
1589
|
before(:each) do
|
1552
1590
|
subject.format :txt
|
1591
|
+
subject.content_type :json, "application/json"
|
1553
1592
|
subject.get '/meaning_of_life' do
|
1554
1593
|
{ :meaning_of_life => 42 }
|
1555
1594
|
end
|
@@ -1567,9 +1606,30 @@ describe Grape::API do
|
|
1567
1606
|
last_response.body.should == { :meaning_of_life => 42 }.to_s
|
1568
1607
|
end
|
1569
1608
|
end
|
1609
|
+
context ':txt only' do
|
1610
|
+
before(:each) do
|
1611
|
+
subject.format :txt
|
1612
|
+
subject.get '/meaning_of_life' do
|
1613
|
+
{ :meaning_of_life => 42 }
|
1614
|
+
end
|
1615
|
+
end
|
1616
|
+
it 'forces txt without an extension' do
|
1617
|
+
get '/meaning_of_life'
|
1618
|
+
last_response.body.should == { :meaning_of_life => 42 }.to_s
|
1619
|
+
end
|
1620
|
+
it 'forces txt with the wrong extension' do
|
1621
|
+
get '/meaning_of_life.json'
|
1622
|
+
last_response.body.should == { :meaning_of_life => 42 }.to_s
|
1623
|
+
end
|
1624
|
+
it 'forces txt from a non-accepting header' do
|
1625
|
+
get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'application/json' }
|
1626
|
+
last_response.body.should == { :meaning_of_life => 42 }.to_s
|
1627
|
+
end
|
1628
|
+
end
|
1570
1629
|
context ':json' do
|
1571
1630
|
before(:each) do
|
1572
1631
|
subject.format :json
|
1632
|
+
subject.content_type :txt, "text/plain"
|
1573
1633
|
subject.get '/meaning_of_life' do
|
1574
1634
|
{ :meaning_of_life => 42 }
|
1575
1635
|
end
|
data/spec/grape/endpoint_spec.rb
CHANGED
@@ -7,11 +7,11 @@ describe Grape::Endpoint do
|
|
7
7
|
describe '#initialize' do
|
8
8
|
it 'takes a settings stack, options, and a block' do
|
9
9
|
p = Proc.new {}
|
10
|
-
expect {
|
10
|
+
expect {
|
11
11
|
Grape::Endpoint.new(Grape::Util::HashStack.new, {
|
12
12
|
:path => '/',
|
13
13
|
:method => :get
|
14
|
-
}, &p)
|
14
|
+
}, &p)
|
15
15
|
}.not_to raise_error
|
16
16
|
end
|
17
17
|
end
|
@@ -246,7 +246,6 @@ describe Grape::Endpoint do
|
|
246
246
|
subject.post '/request_body' do
|
247
247
|
params[:user]
|
248
248
|
end
|
249
|
-
|
250
249
|
subject.put '/request_body' do
|
251
250
|
params[:user]
|
252
251
|
end
|
@@ -274,18 +273,24 @@ describe Grape::Endpoint do
|
|
274
273
|
|
275
274
|
it 'does not include parameters not defined by the body' do
|
276
275
|
subject.post '/omitted_params' do
|
277
|
-
|
276
|
+
params[:version].should == nil
|
277
|
+
params[:user].should == 'Bob'
|
278
278
|
end
|
279
|
-
post '/omitted_params', MultiJson.dump(:user => '
|
279
|
+
post '/omitted_params', MultiJson.dump(:user => 'Bob'), {'CONTENT_TYPE' => 'application/json'}
|
280
280
|
end
|
281
|
+
end
|
281
282
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
283
|
+
it "responds with a 406 for an unsupported content-type" do
|
284
|
+
subject.format :json
|
285
|
+
# subject.content_type :json, "application/json"
|
286
|
+
subject.put '/request_body' do
|
287
|
+
params[:user]
|
287
288
|
end
|
289
|
+
put '/request_body', '<user>Bobby T.</user>', {'CONTENT_TYPE' => 'application/xml'}
|
290
|
+
last_response.status.should == 406
|
291
|
+
last_response.body.should == '{"error":"The requested content-type is not supported."}'
|
288
292
|
end
|
293
|
+
|
289
294
|
end
|
290
295
|
|
291
296
|
describe '#error!' do
|
@@ -437,6 +442,20 @@ describe Grape::Endpoint do
|
|
437
442
|
last_response.body.should == 'Hiya'
|
438
443
|
end
|
439
444
|
|
445
|
+
it 'pulls a representation from the class options if the presented object is a collection of objects' do
|
446
|
+
entity = Class.new(Grape::Entity)
|
447
|
+
entity.stub!(:represent).and_return("Hiya")
|
448
|
+
|
449
|
+
class TestObject; end
|
450
|
+
|
451
|
+
subject.represent TestObject, :with => entity
|
452
|
+
subject.get '/example' do
|
453
|
+
present [TestObject.new]
|
454
|
+
end
|
455
|
+
get '/example'
|
456
|
+
last_response.body.should == "Hiya"
|
457
|
+
end
|
458
|
+
|
440
459
|
it 'pulls a representation from the class ancestor if it exists' do
|
441
460
|
entity = Class.new(Grape::Entity)
|
442
461
|
entity.stub!(:represent).and_return("Hiya")
|
@@ -465,6 +484,20 @@ describe Grape::Endpoint do
|
|
465
484
|
last_response.body.should == 'Auto-detect!'
|
466
485
|
end
|
467
486
|
|
487
|
+
it 'automatically uses Klass::Entity based on the first object in the collection being presented' do
|
488
|
+
some_model = Class.new
|
489
|
+
entity = Class.new(Grape::Entity)
|
490
|
+
entity.stub!(:represent).and_return("Auto-detect!")
|
491
|
+
|
492
|
+
some_model.const_set :Entity, entity
|
493
|
+
|
494
|
+
subject.get '/example' do
|
495
|
+
present [some_model.new]
|
496
|
+
end
|
497
|
+
get '/example'
|
498
|
+
last_response.body.should == 'Auto-detect!'
|
499
|
+
end
|
500
|
+
|
468
501
|
it 'adds a root key to the output if one is given' do
|
469
502
|
subject.get '/example' do
|
470
503
|
present({:abc => 'def'}, :root => :root)
|
@@ -606,5 +639,5 @@ describe Grape::Endpoint do
|
|
606
639
|
last_response.body.should == "http://example.org/api/v1/url"
|
607
640
|
end
|
608
641
|
end
|
609
|
-
|
642
|
+
|
610
643
|
end
|
@@ -149,16 +149,37 @@ describe Grape::Middleware::Formatter do
|
|
149
149
|
|
150
150
|
context 'Input' do
|
151
151
|
it 'parses the body from a POST/PUT and put the contents into rack.request.form_hash' do
|
152
|
-
|
152
|
+
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
|
153
|
+
subject.call({
|
154
|
+
'PATH_INFO' => '/info',
|
155
|
+
'REQUEST_METHOD' => 'POST',
|
156
|
+
'CONTENT_TYPE' => 'application/json',
|
157
|
+
'rack.input' => io,
|
158
|
+
'CONTENT_LENGTH' => io.length
|
159
|
+
})
|
153
160
|
subject.env['rack.request.form_hash']['is_boolean'].should be_true
|
154
161
|
subject.env['rack.request.form_hash']['string'].should == 'thing'
|
155
162
|
end
|
156
163
|
it 'parses the body from an xml POST/PUT and put the contents into rack.request.from_hash' do
|
157
|
-
|
164
|
+
io = StringIO.new('<thing><name>Test</name></thing>')
|
165
|
+
subject.call({
|
166
|
+
'PATH_INFO' => '/info.xml',
|
167
|
+
'REQUEST_METHOD' => 'POST',
|
168
|
+
'CONTENT_TYPE' => 'application/xml',
|
169
|
+
'rack.input' => io,
|
170
|
+
'CONTENT_LENGTH' => io.length
|
171
|
+
})
|
158
172
|
subject.env['rack.request.form_hash']['thing']['name'].should == 'Test'
|
159
173
|
end
|
160
174
|
it 'is able to fail gracefully if the body is regular POST content' do
|
161
|
-
|
175
|
+
io = StringIO.new('name=Other+Test+Thing')
|
176
|
+
subject.call({
|
177
|
+
'PATH_INFO' => '/info',
|
178
|
+
'REQUEST_METHOD' => 'POST',
|
179
|
+
'CONTENT_TYPE' => 'application/json',
|
180
|
+
'rack.input' => io,
|
181
|
+
'CONTENT_LENGTH' => io.length
|
182
|
+
})
|
162
183
|
subject.env['rack.request.form_hash'].should be_nil
|
163
184
|
end
|
164
185
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'spec_helper'
|
2
3
|
|
3
4
|
describe Grape::Validations::CoerceValidator do
|
@@ -5,11 +6,23 @@ describe Grape::Validations::CoerceValidator do
|
|
5
6
|
def app; subject end
|
6
7
|
|
7
8
|
describe 'coerce' do
|
9
|
+
it "i18n error on malformed input" do
|
10
|
+
I18n.load_path << File.expand_path('../zh-CN.yml',__FILE__)
|
11
|
+
I18n.locale = :'zh-CN'
|
12
|
+
subject.params { requires :age, :type => Integer }
|
13
|
+
subject.get '/single' do 'int works'; end
|
14
|
+
|
15
|
+
get '/single', :age => '43a'
|
16
|
+
last_response.status.should == 400
|
17
|
+
last_response.body.should == '年龄格式不正确'
|
18
|
+
I18n.locale = :en
|
19
|
+
|
20
|
+
end
|
8
21
|
it 'error on malformed input' do
|
9
22
|
subject.params { requires :int, :type => Integer }
|
10
23
|
subject.get '/single' do 'int works'; end
|
11
24
|
|
12
|
-
get '/single', :int => '43a'
|
25
|
+
get '/single', :int => '43a'
|
13
26
|
last_response.status.should == 400
|
14
27
|
last_response.body.should == 'invalid parameter: int'
|
15
28
|
|
@@ -89,7 +102,7 @@ describe Grape::Validations::CoerceValidator do
|
|
89
102
|
get '/bool', { :bool => 1 }
|
90
103
|
last_response.status.should == 200
|
91
104
|
last_response.body.should == 'TrueClass'
|
92
|
-
|
105
|
+
|
93
106
|
get '/bool', { :bool => 0 }
|
94
107
|
last_response.status.should == 200
|
95
108
|
last_response.body.should == 'FalseClass'
|
@@ -64,15 +64,21 @@ describe Grape::Validations::PresenceValidator do
|
|
64
64
|
end
|
65
65
|
|
66
66
|
it 'validates id' do
|
67
|
-
post
|
67
|
+
post '/'
|
68
68
|
last_response.status.should == 400
|
69
69
|
last_response.body.should == '{"error":"missing parameter: id"}'
|
70
70
|
|
71
|
-
|
71
|
+
io = StringIO.new('{"id" : "a56b"}')
|
72
|
+
post '/', {}, 'rack.input' => io,
|
73
|
+
'CONTENT_TYPE' => 'application/json',
|
74
|
+
'CONTENT_LENGTH' => io.length
|
72
75
|
last_response.body.should == '{"error":"invalid parameter: id"}'
|
73
76
|
last_response.status.should == 400
|
74
77
|
|
75
|
-
|
78
|
+
io = StringIO.new('{"id" : 56}')
|
79
|
+
post '/', {}, 'rack.input' => io,
|
80
|
+
'CONTENT_TYPE' => 'application/json',
|
81
|
+
'CONTENT_LENGTH' => io.length
|
76
82
|
last_response.body.should == '{"ret":56}'
|
77
83
|
last_response.status.should == 201
|
78
84
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -285,6 +285,7 @@ files:
|
|
285
285
|
- lib/grape/formatter/serializable_hash.rb
|
286
286
|
- lib/grape/formatter/txt.rb
|
287
287
|
- lib/grape/formatter/xml.rb
|
288
|
+
- lib/grape/locale/en.yml
|
288
289
|
- lib/grape/middleware/auth/basic.rb
|
289
290
|
- lib/grape/middleware/auth/digest.rb
|
290
291
|
- lib/grape/middleware/auth/oauth2.rb
|
@@ -300,6 +301,7 @@ files:
|
|
300
301
|
- lib/grape/parser/json.rb
|
301
302
|
- lib/grape/parser/xml.rb
|
302
303
|
- lib/grape/route.rb
|
304
|
+
- lib/grape/util/content_types.rb
|
303
305
|
- lib/grape/util/deep_merge.rb
|
304
306
|
- lib/grape/util/hash_stack.rb
|
305
307
|
- lib/grape/validations.rb
|
@@ -325,6 +327,7 @@ files:
|
|
325
327
|
- spec/grape/validations/coerce_spec.rb
|
326
328
|
- spec/grape/validations/presence_spec.rb
|
327
329
|
- spec/grape/validations/regexp_spec.rb
|
330
|
+
- spec/grape/validations/zh-CN.yml
|
328
331
|
- spec/grape/validations_spec.rb
|
329
332
|
- spec/shared/versioning_examples.rb
|
330
333
|
- spec/spec_helper.rb
|
@@ -345,7 +348,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
345
348
|
version: '0'
|
346
349
|
segments:
|
347
350
|
- 0
|
348
|
-
hash:
|
351
|
+
hash: 3430639806354577699
|
349
352
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
350
353
|
none: false
|
351
354
|
requirements:
|
@@ -354,12 +357,36 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
354
357
|
version: '0'
|
355
358
|
segments:
|
356
359
|
- 0
|
357
|
-
hash:
|
360
|
+
hash: 3430639806354577699
|
358
361
|
requirements: []
|
359
362
|
rubyforge_project: grape
|
360
363
|
rubygems_version: 1.8.24
|
361
364
|
signing_key:
|
362
365
|
specification_version: 3
|
363
366
|
summary: A simple Ruby framework for building REST-like APIs.
|
364
|
-
test_files:
|
367
|
+
test_files:
|
368
|
+
- spec/grape/api_spec.rb
|
369
|
+
- spec/grape/endpoint_spec.rb
|
370
|
+
- spec/grape/entity_spec.rb
|
371
|
+
- spec/grape/middleware/auth/basic_spec.rb
|
372
|
+
- spec/grape/middleware/auth/digest_spec.rb
|
373
|
+
- spec/grape/middleware/auth/oauth2_spec.rb
|
374
|
+
- spec/grape/middleware/base_spec.rb
|
375
|
+
- spec/grape/middleware/error_spec.rb
|
376
|
+
- spec/grape/middleware/exception_spec.rb
|
377
|
+
- spec/grape/middleware/formatter_spec.rb
|
378
|
+
- spec/grape/middleware/versioner/header_spec.rb
|
379
|
+
- spec/grape/middleware/versioner/param_spec.rb
|
380
|
+
- spec/grape/middleware/versioner/path_spec.rb
|
381
|
+
- spec/grape/middleware/versioner_spec.rb
|
382
|
+
- spec/grape/util/hash_stack_spec.rb
|
383
|
+
- spec/grape/validations/coerce_spec.rb
|
384
|
+
- spec/grape/validations/presence_spec.rb
|
385
|
+
- spec/grape/validations/regexp_spec.rb
|
386
|
+
- spec/grape/validations/zh-CN.yml
|
387
|
+
- spec/grape/validations_spec.rb
|
388
|
+
- spec/shared/versioning_examples.rb
|
389
|
+
- spec/spec_helper.rb
|
390
|
+
- spec/support/basic_auth_encode_helpers.rb
|
391
|
+
- spec/support/versioned_helpers.rb
|
365
392
|
has_rdoc:
|