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/lib/grape.rb CHANGED
@@ -26,6 +26,7 @@ module Grape
26
26
  module Versioner
27
27
  autoload :Path, 'grape/middleware/versioner/path'
28
28
  autoload :Header, 'grape/middleware/versioner/header'
29
+ autoload :Param, 'grape/middleware/versioner/param'
29
30
  end
30
31
  end
31
32
 
data/lib/grape/api.rb CHANGED
@@ -208,9 +208,14 @@ module Grape
208
208
  # end
209
209
  # end
210
210
  # end
211
- def helpers(mod = nil, &block)
212
- if block_given? || mod
213
- mod ||= settings.peek[:helpers] || Module.new
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
@@ -52,7 +52,9 @@ module Grape
52
52
  anchor = options[:route_options][:anchor]
53
53
  anchor = anchor.nil? ? true : anchor
54
54
 
55
- path = compile_path(prepared_path, anchor && !options[:app])
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.last << '(.:format)'
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.deep_merge(request.params).deep_merge(env['rack.routing_args'] || {})
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.decode(object)
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.encode(object.serializable_hash)
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.encode(object.map {|o| o.serializable_hash })
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.encode(object)
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.encode(result)
27
+ MultiJson.dump(result)
28
28
  end
29
29
 
30
30
  def encode_txt(message, backtrace)
31
- result = message.is_a?(Hash) ? MultiJson.encode(message) : message
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
- hit = parts.last.to_sym
46
-
47
- if parts.size <= 1
48
- nil
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
@@ -18,6 +18,8 @@ module Grape
18
18
  Path
19
19
  when :header
20
20
  Header
21
+ when :param
22
+ Param
21
23
  else
22
24
  raise ArgumentError.new("Unknown :using for versioner: #{strategy}")
23
25
  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
@@ -1,3 +1,3 @@
1
1
  module Grape
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
@@ -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.decode(last_response.body)
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(:hello) do
734
+ subject.get :excel do
685
735
  "some binary content"
686
736
  end
687
- get '/hello.xls'
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
- class TwitterAPI < Grape::API
734
- # version v1
735
- version 'v1', :using => :path
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
- TwitterAPI::versions.should == [ 'v1', 'v2' ]
808
+ subject.versions.should == [ 'v1', 'v2' ]
752
809
  end
753
810
  it "should set route paths" do
754
- TwitterAPI::routes.size.should >= 2
755
- TwitterAPI::routes[0].route_path.should == "/:version/version(.:format)"
756
- TwitterAPI::routes[1].route_path.should == "/p/:version/n1/n2/version(.:format)"
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
- TwitterAPI::routes[0].route_version.should == 'v1'
760
- TwitterAPI::routes[1].route_version.should == 'v2'
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
- TwitterAPI::routes[1].route_namespace.should == "/n1/n2"
820
+ subject.routes[1].route_namespace.should == "/n1/n2"
764
821
  end
765
822
  it "should set prefix" do
766
- TwitterAPI::routes[1].route_prefix.should == 'p'
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.size.should == 1
785
- subject.routes[0].route_params.should == { "string" => "", "token" => "a token" }
786
- subject.routes[0].route_optional_params.should == { "limit" => "the limit" }
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
- describe "empty api structure" do
793
- it "returns an empty array of routes" do
794
- subject.desc "grape api"
795
- subject.routes.should == []
796
- end
797
- end
798
- describe "single method with a desc" do
799
- before(:each) do
800
- subject.desc "ping method"
801
- subject.get :ping do
802
- 'pong'
803
- end
804
- end
805
- it "returns route description" do
806
- subject.routes[0].route_description.should == "ping method"
807
- end
808
- end
809
- describe "single method with a an array of params and a desc hash block" do
810
- before(:each) do
811
- subject.desc "ping method", { :params => { "x" => "y" } }
812
- subject.get "ping/:x" do
813
- 'pong'
814
- end
815
- end
816
- it "returns route description" do
817
- subject.routes[0].route_description.should == "ping method"
818
- end
819
- end
820
- describe "api structure with multiple methods and descriptions" do
821
- before(:each) do
822
- class JitterAPI < Grape::API
823
- desc "first method"
824
- get "first" do; end
825
- get "second" do; end
826
- desc "third method"
827
- get "third" do; end
828
- end
829
- end
830
- it "should return a description for the first method" do
831
- JitterAPI::routes[0].route_description.should == "first method"
832
- JitterAPI::routes[1].route_description.should be_nil
833
- JitterAPI::routes[2].route_description.should == "third method"
834
- end
835
- end
836
- describe "api structure with multiple methods, namespaces, descriptions and options" do
837
- before(:each) do
838
- class LitterAPI < Grape::API
839
- desc "first method"
840
- get "first" do; end
841
- get "second" do; end
842
- namespace "ns" do
843
- desc "ns second", :foo => "bar"
844
- get "second" do; end
845
- end
846
- desc "third method", :details => "details of third method"
847
- get "third" do; end
848
- desc "Reverses a string.", { :params =>
849
- { "s" => { :desc => "string to reverse", :type => "string" }}
850
- }
851
- get "reverse" do
852
- params[:s].reverse
853
- end
854
- end
855
- end
856
- it "should return a description for the first method" do
857
- LitterAPI::routes[0].route_description.should == "first method"
858
- LitterAPI::routes[1].route_description.should be_nil
859
- LitterAPI::routes[2].route_description.should == "ns second"
860
- LitterAPI::routes[2].route_foo.should == "bar"
861
- LitterAPI::routes[3].route_description.should == "third method"
862
- LitterAPI::routes[4].route_description.should == "Reverses a string."
863
- LitterAPI::routes[4].route_params.should == { "s" => { :desc => "string to reverse", :type => "string" }}
864
- end
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