rack-api 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rack-api (0.1.2)
5
- activesupport (~> 3.0.6)
4
+ rack-api (0.2.0)
5
+ activesupport (~> 3.0.7)
6
6
  rack (~> 1.2.1)
7
7
  rack-mount (~> 0.6.14)
8
8
 
9
9
  GEM
10
10
  remote: http://rubygems.org/
11
11
  specs:
12
- activesupport (3.0.6)
12
+ activesupport (3.0.7)
13
13
  archive-tar-minitar (0.5.2)
14
14
  columnize (0.3.2)
15
15
  diff-lcs (1.1.2)
@@ -20,6 +20,7 @@ GEM
20
20
  rack (>= 1.0.0)
21
21
  rack-test (0.5.7)
22
22
  rack (>= 1.0)
23
+ redis (2.2.0)
23
24
  rspec (2.5.0)
24
25
  rspec-core (~> 2.5.0)
25
26
  rspec-expectations (~> 2.5.0)
@@ -45,5 +46,6 @@ PLATFORMS
45
46
  DEPENDENCIES
46
47
  rack-api!
47
48
  rack-test (~> 0.5.7)
49
+ redis (~> 2.2.0)
48
50
  rspec (~> 2.5.0)
49
51
  ruby-debug19
@@ -124,6 +124,108 @@ Your <tt>Helpers</tt> module may look like this:
124
124
  end
125
125
  end
126
126
 
127
+ == Helpers
128
+
129
+ Every Rack::API action has several helper methods available through the
130
+ Rack::API::App class. Here's some of them:
131
+
132
+ === logger
133
+
134
+ Logs specified message to the STDOUT.
135
+
136
+ get "/" do
137
+ logger.info "Hello index page!"
138
+ {}
139
+ end
140
+
141
+ === headers
142
+
143
+ Define custom headers that will be sent to the client.
144
+
145
+ get "/" do
146
+ headers["X-Awesome"] = "U R Awesome"
147
+ {}
148
+ end
149
+
150
+ == params
151
+
152
+ Return current request parameters.
153
+
154
+ get "/" do
155
+ {:message => "Hello #{params[:name]}"}
156
+ end
157
+
158
+ == request
159
+
160
+ Return an object relative to the current request.
161
+
162
+ == credentials
163
+
164
+ This method will return an array container both username and password if client
165
+ sent Basic Authentication headers.
166
+
167
+ get "/" do
168
+ user, pass = credentials
169
+ {:message => "Hello #{user}"}
170
+ end
171
+
172
+ == url_for
173
+
174
+ Build an URL by merging segments, default URL options and hash with parameters.
175
+
176
+ Rack::API.app do
177
+ default_url_options :host => "example.com", :protocol => "https"
178
+
179
+ version "v1" do
180
+ get "/" do
181
+ {:url => url_for(:users, User.find(params[:id])), :format => :json}
182
+ end
183
+ end
184
+ end
185
+
186
+ == Useful middlewares
187
+
188
+ === Rack::API::Middleware::SSL
189
+
190
+ This middleware will accept only HTTPS requests. Any request on HTTP will be dropped.
191
+
192
+ Rack::API.app do
193
+ use Rack::API::Middleware::SSL
194
+ end
195
+
196
+ === Rack::API::Middleware::Limit
197
+
198
+ This middleware will limit access to API based on requests per hour. It requires a Redis connection.
199
+
200
+ Rack::API.app do
201
+ # Use the default settings.
202
+ # Will accept 60 requests/hour limited by IP address (REMOTE_ADDR)
203
+ use Rack::API::Middleware::Limit, :with => Redis.new
204
+ end
205
+
206
+ Other usages:
207
+
208
+ # Set custom limit/hour.
209
+ # Will accept ± 1 request/second.
210
+ use Rack::API::Middleware::Limit, :with => $redis, :limit => 3600
211
+
212
+ # Set custom string key.
213
+ # Will limit by something like env["X-Forwarded-For"].
214
+ use Rack::API::Middleware::Limit, :with => $redis, :key => "X-Forwarded-For"
215
+
216
+ # Set custom block key.
217
+ # Will limit by credential (Basic Auth).
218
+ Rack::API.app do
219
+ basic_auth do |user, pass|
220
+ User.authorize(user, pass)
221
+ end
222
+
223
+ use Rack::API::Middleware::Limit, :with => $redis, :key => proc {|env|
224
+ request = Rack::Auth::Basic::Request.new(env)
225
+ request.credentials[0]
226
+ }
227
+ end
228
+
127
229
  == Maintainer
128
230
 
129
231
  * Nando Vieira (http://nandovieira.com.br)
@@ -1,23 +1,24 @@
1
1
  require "rack"
2
2
  require "rack/mount"
3
3
  require "active_support/hash_with_indifferent_access"
4
+ require "active_support/core_ext/object/to_query"
4
5
  require "json"
5
6
  require "logger"
6
7
  require "forwardable"
7
8
 
8
9
  module Rack
9
10
  class API
10
- autoload :App, "rack/api/app"
11
- autoload :Formatter, "rack/api/formatter"
12
- autoload :Middleware, "rack/api/middleware"
13
- autoload :Runner, "rack/api/runner"
14
- autoload :Response, "rack/api/response"
15
- autoload :Version, "rack/api/version"
11
+ autoload :App , "rack/api/app"
12
+ autoload :Formatter , "rack/api/formatter"
13
+ autoload :Middleware , "rack/api/middleware"
14
+ autoload :Runner , "rack/api/runner"
15
+ autoload :Response , "rack/api/response"
16
+ autoload :Version , "rack/api/version"
16
17
 
17
18
  class << self
18
19
  extend Forwardable
19
20
 
20
- def_delegators :runner, :version, :use, :prefix, :basic_auth, :helper, :respond_to
21
+ def_delegators :runner, *Runner::DELEGATE_METHODS
21
22
  end
22
23
 
23
24
  # A shortcut for defining new APIs. Instead of creating a
@@ -23,13 +23,34 @@ module Rack
23
23
  #
24
24
  DEFAULT_MIME_TYPE = "application/octet-stream"
25
25
 
26
- attr_reader :block
27
- attr_reader :env
28
-
29
26
  # Hold block that will be executed in case the
30
27
  # route is recognized.
31
28
  #
29
+ attr_accessor :handler
30
+
31
+ # Hold environment from current request.
32
+ #
33
+ attr_accessor :env
34
+
35
+ # Define which will be the default format when <tt>format=<format></tt>
36
+ # is not defined.
37
+ attr_accessor :default_format
38
+
39
+ # Set the default prefix path.
40
+ #
41
+ attr_accessor :prefix
42
+
43
+ # Specify the API version.
44
+ #
45
+ attr_accessor :version
46
+
47
+ # Hold url options.
48
+ #
49
+ attr_accessor :url_options
50
+
32
51
  def initialize(options)
52
+ @url_options = {}
53
+
33
54
  options.each do |name, value|
34
55
  instance_variable_set("@#{name}", value)
35
56
  end
@@ -62,7 +83,7 @@ module Rack
62
83
  # Return the requested format. Defaults to JSON.
63
84
  #
64
85
  def format
65
- params.fetch(:format, "json")
86
+ params.fetch(:format, default_format)
66
87
  end
67
88
 
68
89
  # Stop processing by rendering the provided information.
@@ -126,14 +147,14 @@ module Rack
126
147
  end
127
148
  end
128
149
 
129
- # Render the result of block.
150
+ # Render the result of handler.
130
151
  #
131
152
  def call(env) # :nodoc:
132
153
  reset!
133
154
  @env = env
134
155
 
135
156
  response = catch(:error) do
136
- render instance_eval(&block)
157
+ render instance_eval(&handler)
137
158
  end
138
159
 
139
160
  response.respond_to?(:to_rack) ? response.to_rack : response
@@ -149,6 +170,48 @@ module Rack
149
170
  headers.fetch("Content-Type", mime)
150
171
  end
151
172
 
173
+ # Return a URL path for all segments.
174
+ # You can set default options by using the
175
+ # Rack::API::Runner#default_url_options method.
176
+ #
177
+ # url_for :users
178
+ # #=> /users
179
+ #
180
+ # url_for :users, User.first
181
+ # #=> /users/1
182
+ #
183
+ # url_for :users, 1, :format => :json
184
+ # #=> /users/1?format=json
185
+ #
186
+ # url_for :users, :filters => [:name, :age]
187
+ # #=> /users?filters[]=name&filters[]=age
188
+ #
189
+ # URL segments can be any kind of object, first checking it responds to the
190
+ # <tt>to_param</tt> method. If not, converts object to string by using the
191
+ # <tt>to_s</tt> method.
192
+ #
193
+ def url_for(*args)
194
+ options = {}
195
+ options = args.pop if args.last.kind_of?(Hash)
196
+
197
+ segments = []
198
+ segments << url_options[:base_path] if url_options[:base_path]
199
+ segments << prefix if prefix
200
+ segments << version
201
+ segments += args.collect {|part| part.respond_to?(:to_param) ? part.to_param : part.to_s }
202
+
203
+ url = ""
204
+ url << url_options.fetch(:protocol, "http").to_s << "://"
205
+ url << url_options.fetch(:host, env["SERVER_NAME"])
206
+
207
+ port = url_options.fetch(:port, env["SERVER_PORT"]).to_i
208
+ url << ":" << port.to_s if port.nonzero? && port != 80
209
+
210
+ url << Rack::Mount::Utils.normalize_path(segments.join("/"))
211
+ url << "?" << options.to_param if options.any?
212
+ url
213
+ end
214
+
152
215
  private
153
216
  def render(response) # :nodoc:
154
217
  [status, headers.merge("Content-Type" => content_type), [format_response(response)]]
@@ -3,6 +3,7 @@ module Rack
3
3
  module Middleware
4
4
  autoload :Format, "rack/api/middleware/format"
5
5
  autoload :SSL, "rack/api/middleware/ssl"
6
+ autoload :Limit, "rack/api/middleware/limit"
6
7
  end
7
8
  end
8
9
  end
@@ -2,19 +2,21 @@ module Rack
2
2
  class API
3
3
  module Middleware
4
4
  class Format
5
- def initialize(app, formats)
6
- @app, @formats = app, formats.collect {|f| f.to_s}
5
+ def initialize(app, default_format, formats)
6
+ @app = app
7
+ @default_format = default_format.to_s
8
+ @formats = formats.collect {|f| f.to_s}
7
9
  end
8
10
 
9
11
  def call(env)
10
12
  request = Rack::Request.new(env)
11
13
  params = request.env["rack.routing_args"].merge(request.params)
12
- requested_format = params.fetch(:format, "json")
14
+ requested_format = params.fetch(:format, @default_format)
13
15
 
14
16
  if @formats.include?(requested_format)
15
17
  @app.call(env)
16
18
  else
17
- [406, {"Content-Type" => "text/plain"}, ["Invalid format"]]
19
+ [406, {"Content-Type" => "text/plain"}, ["Invalid format. Accepts one of [#{@formats.join(", ")}]"]]
18
20
  end
19
21
  end
20
22
  end
@@ -0,0 +1,54 @@
1
+ module Rack
2
+ class API
3
+ module Middleware
4
+ class Limit
5
+ attr_reader :options, :env
6
+
7
+ def initialize(app, options = {})
8
+ @app = app
9
+ @options = {
10
+ :limit => 60,
11
+ :key => "REMOTE_ADDR",
12
+ :with => Redis.new
13
+ }.merge(options)
14
+ end
15
+
16
+ def call(env)
17
+ @env = env
18
+
19
+ if authorized?
20
+ @app.call(env)
21
+ else
22
+ [503, {"Content-Type" => "text/plain"}, ["Over Rate Limit."]]
23
+ end
24
+ rescue Exception => e
25
+ @app.call(env)
26
+ end
27
+
28
+ private
29
+ def authorized?
30
+ count = redis.incr(key)
31
+ redis.expire(key, 3600)
32
+
33
+ count <= options[:limit] || redis.sismember("api:whitelist", identifier)
34
+ end
35
+
36
+ def redis
37
+ options[:with]
38
+ end
39
+
40
+ def identifier
41
+ @identifier ||= begin
42
+ options[:key].respond_to?(:call) ? options[:key].call(env).to_s : env[options[:key].to_s]
43
+ end
44
+ end
45
+
46
+ def key
47
+ @key ||= begin
48
+ "api:#{identifier}:#{Time.now.strftime("%Y%m%d%H")}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -3,6 +3,11 @@ module Rack
3
3
  class Runner
4
4
  HTTP_METHODS = %w[get post put delete head]
5
5
 
6
+ DELEGATE_METHODS = %w[
7
+ version use prefix basic_auth
8
+ helper respond_to default_url_options
9
+ ]
10
+
6
11
  attr_accessor :settings
7
12
 
8
13
  def initialize
@@ -79,6 +84,32 @@ module Rack
79
84
  set :helpers, mod, :append
80
85
  end
81
86
 
87
+ # Define the server endpoint. Will be used if you call the method
88
+ # Rack::API::App#url_for.
89
+ #
90
+ # The following options are supported:
91
+ #
92
+ # * <tt>:host</tt> – Specifies the host the link should be targeted at.
93
+ # * <tt>:protocol</tt> – The protocol to connect to. Defaults to 'http'.
94
+ # * <tt>:port</tt> – Optionally specify the port to connect to.
95
+ # * <tt>:base_path</tt> – Optionally specify a base path.
96
+ #
97
+ # default_url_options :host => "myhost.com"
98
+ # #=> http://myhost.com
99
+ #
100
+ # default_url_options :host => "myhost.com", :protocol => "https"
101
+ # #=> https://myhost.com
102
+ #
103
+ # default_url_options :host => "myhost.com", :port => 3000
104
+ # #=> http://myhost.com:3000
105
+ #
106
+ # default_url_options :host => "myhost.com", :base_path => "my/custom/path"
107
+ # #=> http://myhost.com/my/custom/path
108
+ #
109
+ def default_url_options(options)
110
+ set :url_options, options
111
+ end
112
+
82
113
  # Create a new API version.
83
114
  #
84
115
  def version(name, &block)
@@ -157,6 +188,13 @@ module Rack
157
188
  #
158
189
  # The code above will accept only <tt>:json</tt> as format on version <tt>:v1</tt>.
159
190
  #
191
+ # Also, the first value provided to this method will be used as default format,
192
+ # which means that requests that don't provide the <tt>:format</tt> param, will use
193
+ # this value.
194
+ #
195
+ # respond_to :fffuuu, :json
196
+ # #=> the default format is fffuuu
197
+ #
160
198
  def respond_to(*formats)
161
199
  set :formats, formats
162
200
  end
@@ -199,8 +237,19 @@ module Rack
199
237
  Rack::Mount::Utils.normalize_path([option(:prefix), settings[:version], path].join("/"))
200
238
  end
201
239
 
202
- def build_app(block) # :nodoc:
203
- app = App.new(:block => block)
240
+ def default_format # :nodoc:
241
+ (option(:formats).first || "json").to_s
242
+ end
243
+
244
+ def build_app(handler) # :nodoc:
245
+ app = App.new({
246
+ :handler => handler,
247
+ :default_format => default_format,
248
+ :version => option(:version),
249
+ :prefix => option(:prefix),
250
+ :url_options => option(:url_options)
251
+ })
252
+
204
253
  builder = Rack::Builder.new
205
254
 
206
255
  # Add middleware for basic authentication.
@@ -208,7 +257,7 @@ module Rack
208
257
  builder.use Rack::Auth::Basic, auth[0], &auth[1] if auth && auth != :none
209
258
 
210
259
  # Add middleware for format validation.
211
- builder.use Rack::API::Middleware::Format, option(:formats)
260
+ builder.use Rack::API::Middleware::Format, default_format, option(:formats)
212
261
 
213
262
  # Add middlewares to executation stack.
214
263
  option(:middlewares, :merge).each {|middleware| builder.use(*middleware)}
@@ -2,8 +2,8 @@ module Rack
2
2
  class API
3
3
  module Version
4
4
  MAJOR = 0
5
- MINOR = 1
6
- PATCH = 2
5
+ MINOR = 2
6
+ PATCH = 0
7
7
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
8
8
  end
9
9
  end
@@ -19,8 +19,9 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.add_dependency "rack", "~> 1.2.1"
21
21
  s.add_dependency "rack-mount", "~> 0.6.14"
22
- s.add_dependency "activesupport", "~> 3.0.6"
22
+ s.add_dependency "activesupport", "~> 3.0.7"
23
23
  s.add_development_dependency "rspec", "~> 2.5.0"
24
24
  s.add_development_dependency "rack-test", "~> 0.5.7"
25
+ s.add_development_dependency "redis", "~> 2.2.0"
25
26
  s.add_development_dependency "ruby-debug19" if RUBY_VERSION >= "1.9"
26
27
  end
@@ -0,0 +1,89 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API::Middleware::Limit do
4
+ let(:action) { proc {|env| [200, {}, ["success"]] } }
5
+ let(:env) {
6
+ Rack::MockRequest.env_for("/v1",
7
+ "REMOTE_ADDR" => "127.0.0.1",
8
+ "X-API_KEY" => "fo7hy7ra",
9
+ "HTTP_AUTHORIZATION" => basic_auth("admin", "test")
10
+ )
11
+ }
12
+
13
+ subject { Rack::API::Middleware::Limit.new(action) }
14
+
15
+ before do
16
+ Time.stub :now => Time.parse("2011-04-08 00:00:00")
17
+ @stamp = Time.now.strftime("%Y%m%d%H")
18
+
19
+ begin
20
+ $redis = Redis.new
21
+ $redis.del "api:127.0.0.1:#{@stamp}"
22
+ $redis.del "api:fo7hy7ra:#{@stamp}"
23
+ $redis.del "api:admin:#{@stamp}"
24
+ $redis.del "api:whitelist"
25
+ subject.options.merge!(:with => $redis)
26
+ rescue Errno::ECONNREFUSED => e
27
+ pending "Redis is not running"
28
+ end
29
+ end
30
+
31
+ context "using default options" do
32
+ it "renders action when limit wasn't exceeded" do
33
+ results = 60.times.collect { subject.call(env) }
34
+
35
+ $redis.get("api:127.0.0.1:#{@stamp}").to_i.should == 60
36
+ results.last[0].should == 200
37
+ end
38
+
39
+ it "renders 503 when limit was exceeded" do
40
+ results = 61.times.collect { subject.call(env) }
41
+
42
+ $redis.get("api:127.0.0.1:#{@stamp}").to_i.should == 61
43
+ results.last[0].should == 503
44
+ end
45
+ end
46
+
47
+ context "using custom options" do
48
+ it "respects limit" do
49
+ subject.options.merge!(:limit => 20)
50
+
51
+ results = 20.times.collect { subject.call(env) }
52
+
53
+ $redis.get("api:127.0.0.1:#{@stamp}").to_i.should == 20
54
+ results.last[0].should == 200
55
+ end
56
+
57
+ it "uses custom string key" do
58
+ subject.options.merge!(:key => "X-API_KEY")
59
+ status, headers, result = subject.call(env)
60
+
61
+ $redis.get("api:fo7hy7ra:#{@stamp}").to_i.should == 1
62
+ status.should == 200
63
+ end
64
+
65
+ it "uses custom block key" do
66
+ subject.options.merge! :key => proc {|env|
67
+ request = Rack::Auth::Basic::Request.new(env)
68
+ request.credentials[0]
69
+ }
70
+
71
+ status, headers, result = subject.call(env)
72
+
73
+ $redis.get("api:admin:#{@stamp}").to_i.should == 1
74
+ status.should == 200
75
+ end
76
+ end
77
+
78
+ context "whitelist" do
79
+ it "bypasses API limit" do
80
+ $redis.sadd("api:whitelist", "127.0.0.1")
81
+
82
+ subject.options.merge!(:limit => 5)
83
+ results = 10.times.collect { subject.call(env) }
84
+
85
+ $redis.get("api:127.0.0.1:#{@stamp}").to_i.should == 10
86
+ results.last[0].should == 200
87
+ end
88
+ end
89
+ end
@@ -26,12 +26,39 @@ describe Rack::API, "Format" do
26
26
  end
27
27
  end
28
28
 
29
+ context "default format" do
30
+ it "is json" do
31
+ Rack::API.app do
32
+ version :v2 do
33
+ get("/") { {:success => true} }
34
+ end
35
+ end
36
+
37
+ get "/v2"
38
+ last_response.headers["Content-Type"].should == "application/json"
39
+ end
40
+
41
+ it "is set to the first respond_to value" do
42
+ Rack::API::App::MIME_TYPES["fffuuu"] = "application/x-fffuuu"
43
+
44
+ Rack::API.app do
45
+ version :v2 do
46
+ respond_to :fffuuu, :json
47
+ get("/") { OpenStruct.new(:to_fffuuu => "Fffuuu") }
48
+ end
49
+ end
50
+
51
+ get "/v2"
52
+ last_response.headers["Content-Type"].should == "application/x-fffuuu"
53
+ end
54
+ end
55
+
29
56
  context "invalid format" do
30
57
  it "renders 406" do
31
58
  get "/v1/users.invalid"
32
59
 
33
60
  last_response.status.should == 406
34
- last_response.body.should == "Invalid format"
61
+ last_response.body.should == "Invalid format. Accepts one of [json, jsonp, awesome, fffuuu, zomg]"
35
62
  last_response.headers["Content-Type"].should == "text/plain"
36
63
  end
37
64
  end
@@ -6,7 +6,7 @@ describe Rack::API, "Headers" do
6
6
  version :v1 do
7
7
  get("/users(.:format)") do
8
8
  headers["X-Awesome"] = "U R Awesome"
9
- headers["Content-Type"] = "application/x-json"
9
+ headers["Content-Type"] = "application/x-json" # the default json header is application/json
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "delegators" do
4
+ subject { Rack::API }
5
+
6
+ it { should respond_to(:version) }
7
+ it { should respond_to(:use) }
8
+ it { should respond_to(:prefix) }
9
+ it { should respond_to(:basic_auth) }
10
+ it { should respond_to(:helper) }
11
+ it { should respond_to(:default_url_options) }
12
+ end
@@ -1,6 +1,11 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Rack::API::Runner do
4
+ specify "sanity check for delegate methods" do
5
+ # remember to update spec/method_delegation_spec.rb
6
+ Rack::API::Runner::DELEGATE_METHODS.size.should == 7
7
+ end
8
+
4
9
  it "responds to http methods" do
5
10
  subject.should respond_to(:get)
6
11
  subject.should respond_to(:post)
@@ -19,6 +24,11 @@ describe Rack::API::Runner do
19
24
  subject.option(:prefix).should == "my/awesome/api"
20
25
  end
21
26
 
27
+ it "stores default url options" do
28
+ subject.default_url_options(:host => "example.com")
29
+ subject.option(:url_options).should == {:host => "example.com"}
30
+ end
31
+
22
32
  it "stores middleware" do
23
33
  subject.use Rack::Auth::Basic
24
34
  subject.option(:middlewares, :merge).should == [[Rack::Auth::Basic]]
@@ -30,4 +40,23 @@ describe Rack::API::Runner do
30
40
  subject.basic_auth("Get out!", &handler)
31
41
  subject.settings[:global][:auth].should == ["Get out!", handler]
32
42
  end
43
+
44
+ it "initializes application with correct parameters" do
45
+ expected = {
46
+ :version => "v1",
47
+ :url_options => {:host => "mysite.com"},
48
+ :default_format => "fffuuu",
49
+ :prefix => "api",
50
+ :handler => proc {}
51
+ }
52
+
53
+ Rack::API::App.should_receive(:new).with(hash_including(expected)).once
54
+ subject.version("v1") do
55
+ respond_to :fffuuu
56
+ prefix "api"
57
+ default_url_options :host => "mysite.com"
58
+
59
+ get("/", &expected[:handler])
60
+ end
61
+ end
33
62
  end
@@ -0,0 +1,63 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API::App, "#url_for" do
4
+ subject { Rack::API::App.new(
5
+ :version => "v1",
6
+ :url_options => {},
7
+ :env => Rack::MockRequest.env_for("/v1")
8
+ )}
9
+
10
+ it "returns url considering prefix" do
11
+ subject.prefix = "api"
12
+ subject.url_for.should == "http://example.org/api/v1"
13
+ end
14
+
15
+ it "ignores prefix when is not set" do
16
+ subject.prefix = nil
17
+ subject.url_for.should == "http://example.org/v1"
18
+ end
19
+
20
+ it "returns host" do
21
+ subject.url_for.should == "http://example.org/v1"
22
+ end
23
+
24
+ it "uses a different host" do
25
+ subject.url_options.merge!(:host => "mysite.com")
26
+ subject.url_for.should == "http://mysite.com/v1"
27
+ end
28
+
29
+ it "uses a different protocol" do
30
+ subject.url_options.merge!(:protocol => "https")
31
+ subject.url_for.should == "https://example.org/v1"
32
+ end
33
+
34
+ it "uses a different port" do
35
+ subject.url_options.merge!(:port => "2345")
36
+ subject.url_for.should == "http://example.org:2345/v1"
37
+ end
38
+
39
+ it "uses #to_param when available" do
40
+ subject.url_for("users", mock(:user, :to_param => "1-john-doe")).should == "http://example.org/v1/users/1-john-doe"
41
+ end
42
+
43
+ it "converts other data types" do
44
+ subject.url_for(:users, 1).should == "http://example.org/v1/users/1"
45
+ end
46
+
47
+ it "adds query string" do
48
+ actual = subject.url_for(:format => :json, :filters => [:name, :age])
49
+ actual.should == "http://example.org/v1?filters[]=name&filters[]=age&format=json"
50
+ end
51
+
52
+ it "uses host from request" do
53
+ env = Rack::MockRequest.env_for("/v1", "SERVER_NAME" => "mysite.com")
54
+ subject = Rack::API::App.new(:version => "v1", :env => env)
55
+ subject.url_for.should == "http://mysite.com/v1"
56
+ end
57
+
58
+ it "uses port from request" do
59
+ env = Rack::MockRequest.env_for("/v1", "SERVER_PORT" => "2345")
60
+ subject = Rack::API::App.new(:version => "v1", :env => env)
61
+ subject.url_for.should == "http://example.org:2345/v1"
62
+ end
63
+ end
@@ -2,6 +2,8 @@ require "rack/test"
2
2
  require "rspec"
3
3
  require "rack/api"
4
4
  require "base64"
5
+ require "redis"
6
+ require "ostruct"
5
7
 
6
8
  Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|file| require file}
7
9
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: rack-api
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.2
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Nando Vieira
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-08 00:00:00 Z
13
+ date: 2011-04-19 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: 3.0.6
45
+ version: 3.0.7
46
46
  type: :runtime
47
47
  version_requirements: *id003
48
48
  - !ruby/object:Gem::Dependency
@@ -68,16 +68,27 @@ dependencies:
68
68
  type: :development
69
69
  version_requirements: *id005
70
70
  - !ruby/object:Gem::Dependency
71
- name: ruby-debug19
71
+ name: redis
72
72
  prerelease: false
73
73
  requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: 2.2.0
79
+ type: :development
80
+ version_requirements: *id006
81
+ - !ruby/object:Gem::Dependency
82
+ name: ruby-debug19
83
+ prerelease: false
84
+ requirement: &id007 !ruby/object:Gem::Requirement
74
85
  none: false
75
86
  requirements:
76
87
  - - ">="
77
88
  - !ruby/object:Gem::Version
78
89
  version: "0"
79
90
  type: :development
80
- version_requirements: *id006
91
+ version_requirements: *id007
81
92
  description: Create web app APIs that respond to one or more formats using an elegant DSL.
82
93
  email:
83
94
  - fnando.vieira@gmail.com
@@ -110,17 +121,20 @@ files:
110
121
  - lib/rack/api/formatter/jsonp.rb
111
122
  - lib/rack/api/middleware.rb
112
123
  - lib/rack/api/middleware/format.rb
124
+ - lib/rack/api/middleware/limit.rb
113
125
  - lib/rack/api/middleware/ssl.rb
114
126
  - lib/rack/api/response.rb
115
127
  - lib/rack/api/runner.rb
116
128
  - lib/rack/api/version.rb
117
129
  - rack-api.gemspec
130
+ - spec/rack-api/api_throttling_spec.rb
118
131
  - spec/rack-api/basic_auth_spec.rb
119
132
  - spec/rack-api/format_spec.rb
120
133
  - spec/rack-api/headers_spec.rb
121
134
  - spec/rack-api/helpers_spec.rb
122
135
  - spec/rack-api/http_methods_spec.rb
123
136
  - spec/rack-api/inheritance_spec.rb
137
+ - spec/rack-api/method_delegation_spec.rb
124
138
  - spec/rack-api/middlewares_spec.rb
125
139
  - spec/rack-api/params_spec.rb
126
140
  - spec/rack-api/paths_spec.rb
@@ -128,6 +142,7 @@ files:
128
142
  - spec/rack-api/settings_spec.rb
129
143
  - spec/rack-api/short_circuit_spec.rb
130
144
  - spec/rack-api/ssl_spec.rb
145
+ - spec/rack-api/url_for_spec.rb
131
146
  - spec/spec_helper.rb
132
147
  - spec/support/awesome_middleware.rb
133
148
  - spec/support/core_ext.rb
@@ -162,12 +177,14 @@ signing_key:
162
177
  specification_version: 3
163
178
  summary: Create web app APIs that respond to one or more formats using an elegant DSL.
164
179
  test_files:
180
+ - spec/rack-api/api_throttling_spec.rb
165
181
  - spec/rack-api/basic_auth_spec.rb
166
182
  - spec/rack-api/format_spec.rb
167
183
  - spec/rack-api/headers_spec.rb
168
184
  - spec/rack-api/helpers_spec.rb
169
185
  - spec/rack-api/http_methods_spec.rb
170
186
  - spec/rack-api/inheritance_spec.rb
187
+ - spec/rack-api/method_delegation_spec.rb
171
188
  - spec/rack-api/middlewares_spec.rb
172
189
  - spec/rack-api/params_spec.rb
173
190
  - spec/rack-api/paths_spec.rb
@@ -175,6 +192,7 @@ test_files:
175
192
  - spec/rack-api/settings_spec.rb
176
193
  - spec/rack-api/short_circuit_spec.rb
177
194
  - spec/rack-api/ssl_spec.rb
195
+ - spec/rack-api/url_for_spec.rb
178
196
  - spec/spec_helper.rb
179
197
  - spec/support/awesome_middleware.rb
180
198
  - spec/support/core_ext.rb