rack-api 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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