rack-api 0.1.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.
data/lib/rack/api.rb ADDED
@@ -0,0 +1,83 @@
1
+ require "rack"
2
+ require "rack/mount"
3
+ require "active_support/hash_with_indifferent_access"
4
+ require "json"
5
+ require "logger"
6
+
7
+ module Rack
8
+ class API
9
+ autoload :App, "rack/api/app"
10
+ autoload :Formatter, "rack/api/formatter"
11
+ autoload :Runner, "rack/api/runner"
12
+ autoload :Response, "rack/api/response"
13
+ autoload :Version, "rack/api/version"
14
+
15
+ # A shortcut for defining new APIs. Instead of creating a
16
+ # class that inherits from Rack::API, you can simply pass a
17
+ # block to the Rack::API.app method.
18
+ #
19
+ # Rack::API.app do
20
+ # # define your API
21
+ # end
22
+ #
23
+ def self.app(&block)
24
+ runner.instance_eval(&block)
25
+ runner
26
+ end
27
+
28
+ # Add a middleware to the stack execution.
29
+ #
30
+ # Rack::API.app do
31
+ # use MyMiddleware
32
+ # end
33
+ #
34
+ def self.use(m)
35
+ runner.use(m)
36
+ end
37
+
38
+ # Create a new API version.
39
+ #
40
+ # Rack::API.app do
41
+ # version "v1" do
42
+ # # define your API
43
+ # end
44
+ # end
45
+ #
46
+ def self.version(name, &block)
47
+ runner.version(name, &block)
48
+ end
49
+
50
+ # Set an additional url prefix.
51
+ #
52
+ # Rack::API.app do
53
+ # prefix "api"
54
+ # version("v1") {}
55
+ # end
56
+ #
57
+ # This API will be available through <tt>/api/v1</tt> path.
58
+ #
59
+ def self.prefix(name)
60
+ runner.prefix(name)
61
+ end
62
+
63
+ # Reset all API definitions while using the Rack::API.app method.
64
+ #
65
+ def self.reset!
66
+ @runner = nil
67
+ end
68
+
69
+ # Required by Rack.
70
+ #
71
+ def self.call(env) # :nodoc:
72
+ runner.call(env)
73
+ end
74
+
75
+ private
76
+ # Initialize a new Rack::API::Middleware instance, so
77
+ # we can use it on other class methods.
78
+ #
79
+ def self.runner
80
+ @runner ||= Runner.new
81
+ end
82
+ end
83
+ end
data/rack-api.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/api"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-api"
7
+ s.version = Rack::API::Version::STRING
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Nando Vieira"]
10
+ s.email = ["fnando.vieira@gmail.com"]
11
+ s.homepage = "http://rubygems.org/gems/rack-api"
12
+ s.summary = "Create web app APIs that respond to one or more formats using an elegant DSL."
13
+ s.description = s.summary
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency "rack", "~> 1.2.1"
21
+ s.add_dependency "rack-mount", "~> 0.6.14"
22
+ s.add_dependency "activesupport", "~> 3.0.6"
23
+ s.add_development_dependency "rspec", "~> 2.5.0"
24
+ s.add_development_dependency "rack-test", "~> 0.5.7"
25
+ s.add_development_dependency "ruby-debug19" if RUBY_VERSION >= "1.9"
26
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "Basic Authentication" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ basic_auth do |user, pass|
8
+ user == "admin" && pass == "test"
9
+ end
10
+
11
+ get("/") { {:success => true} }
12
+ end
13
+ end
14
+ end
15
+
16
+ it "denies access" do
17
+ get "/v1/"
18
+ last_response.status.should == 401
19
+
20
+ get "/v1/", {}, "HTTP_AUTHORIZATION" => basic_auth("admin", "invalid")
21
+ last_response.status.should == 401
22
+ end
23
+
24
+ it "grants access" do
25
+ get "/v1/", {}, "HTTP_AUTHORIZATION" => basic_auth("admin", "test")
26
+
27
+ last_response.status.should == 200
28
+ JSON.load(last_response.body).should == {"success" => true}
29
+ end
30
+ end
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "Format" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ respond_to :json, :jsonp, :awesome, :fffuuu, :zomg
8
+ get("/") { {:success => true} }
9
+ get("users(.:format)") { {:users => []} }
10
+ end
11
+ end
12
+ end
13
+
14
+ it "ignores unknown formats" do
15
+ get "/users.xml"
16
+ last_response.status.should == 404
17
+ end
18
+
19
+ context "missing formatter" do
20
+ it "renders 406" do
21
+ get "/v1/users.zomg"
22
+
23
+ last_response.status.should == 406
24
+ last_response.body.should == "Unknown format"
25
+ last_response.headers["Content-Type"].should == "text/plain"
26
+ end
27
+ end
28
+
29
+ context "JSONP" do
30
+ it "renders when set through query string" do
31
+ get "/v1", :format => "jsonp"
32
+
33
+ last_response.status.should == 200
34
+ last_response.body.should == %[callback({"success":true});]
35
+ end
36
+
37
+ it "renders when set through extension" do
38
+ get "/v1/users.jsonp"
39
+
40
+ last_response.status.should == 200
41
+ last_response.body.should == %[callback({"users":[]});]
42
+ end
43
+
44
+ it "sends header" do
45
+ get "/v1/users.jsonp"
46
+ last_response.headers["Content-Type"].should == "application/javascript"
47
+ end
48
+ end
49
+
50
+ context "JSON" do
51
+ it "renders when set through query string" do
52
+ get "/v1", :format => "json"
53
+
54
+ last_response.status.should == 200
55
+ JSON.load(last_response.body).should == {"success" => true}
56
+ end
57
+
58
+ it "renders when set through extension" do
59
+ get "/v1/users.json"
60
+
61
+ last_response.status.should == 200
62
+ JSON.load(last_response.body).should == {"users" => []}
63
+ end
64
+
65
+ it "sends header" do
66
+ get "/v1/users.json"
67
+ last_response.headers["Content-Type"].should == "application/json"
68
+ end
69
+ end
70
+
71
+ context "custom formatter extension" do
72
+ it "renders when set through query string" do
73
+ get "/v1", :format => "awesome"
74
+
75
+ last_response.status.should == 200
76
+ last_response.body.should == "U R Awesome"
77
+ end
78
+
79
+ it "renders when set through extension" do
80
+ get "/v1/users.awesome"
81
+
82
+ last_response.status.should == 200
83
+ last_response.body.should == "U R Awesome"
84
+ end
85
+ end
86
+
87
+ context "custom formatter class" do
88
+ before :all do
89
+ Rack::API::Formatter::Fffuuu = Class.new(Rack::API::Formatter::Base) do
90
+ def to_format
91
+ "ZOMG! Fffuuu!"
92
+ end
93
+ end
94
+ end
95
+
96
+ it "renders when set through query string" do
97
+ get "/v1", :format => "fffuuu"
98
+
99
+ last_response.status.should == 200
100
+ last_response.body.should == "ZOMG! Fffuuu!"
101
+ end
102
+
103
+ it "renders when set through extension" do
104
+ get "/v1/users.fffuuu"
105
+
106
+ last_response.status.should == 200
107
+ last_response.body.should == "ZOMG! Fffuuu!"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "Headers" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ get("/users(.:format)") do
8
+ headers["X-Awesome"] = "U R Awesome"
9
+ headers["Content-Type"] = "application/x-json"
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ it "sends custom headers" do
16
+ get "/v1/users"
17
+ last_response.headers["X-Awesome"].should == "U R Awesome"
18
+ end
19
+
20
+ it "overrides inferred content type" do
21
+ get "/v1/users.json"
22
+ last_response.headers["Content-Type"].should == "application/x-json"
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "HTTP Methods" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ get("get") { {:get => true} }
8
+ post("post") { {:post => true} }
9
+ put("put") { {:put => true} }
10
+ delete("delete") { {:delete => true} }
11
+ head("head") { {:head => true} }
12
+ end
13
+ end
14
+ end
15
+
16
+ Rack::API::Runner::HTTP_METHODS.each do |method|
17
+ it "renders #{method}" do
18
+ send method, "/v1/#{method}"
19
+ last_response.status.should == 200
20
+ JSON.load(last_response.body).should == {method => true}
21
+ end
22
+ end
23
+
24
+ it "does not render unknown methods" do
25
+ post "/get"
26
+ last_response.status.should == 404
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API do
4
+ before do
5
+ @app = MyApp
6
+ end
7
+
8
+ it "renders action from MyApp" do
9
+ get "/v1"
10
+ JSON.load(last_response.body).should == {"myapp" => true}
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "Middlewares" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ use AwesomeMiddleware
8
+ get("/") {}
9
+ end
10
+ end
11
+ end
12
+
13
+ it "sends custom headers" do
14
+ get "/v1"
15
+ last_response.headers["X-Awesome"].should == "U R Awesome"
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "Params" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ get("users/:id(.:format)") { params }
8
+ post("users") { params }
9
+ end
10
+ end
11
+ end
12
+
13
+ it "detects optional names from routing params" do
14
+ get "/v1/users/1.json"
15
+ JSON.load(last_response.body).should == {"id" => "1", "format" => "json"}
16
+ end
17
+
18
+ it "detects query string params" do
19
+ get "/v1/users/1?include=articles"
20
+ JSON.load(last_response.body).should == {"id" => "1", "include" => "articles"}
21
+ end
22
+
23
+ it "detects post params" do
24
+ post "/v1/users", :name => "John Doe"
25
+ JSON.load(last_response.body).should == {"name" => "John Doe"}
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "Paths" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ prefix "api"
8
+ get("users") { {:users => []} }
9
+ end
10
+
11
+ version :v2 do
12
+ prefix "/"
13
+ get("users") { {:users => []} }
14
+ end
15
+ end
16
+ end
17
+
18
+ it "does not render root" do
19
+ get "/"
20
+ last_response.status.should == 404
21
+ end
22
+
23
+ it "does not render unknown paths" do
24
+ get "/api/v1/users/index"
25
+ last_response.status.should == 404
26
+ end
27
+
28
+ it "renders known paths" do
29
+ get "/api/v1/users"
30
+ last_response.status.should == 200
31
+
32
+ get "/v2/users"
33
+ last_response.status.should == 200
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API::Runner do
4
+ it "responds to http methods" do
5
+ subject.should respond_to(:get)
6
+ subject.should respond_to(:post)
7
+ subject.should respond_to(:put)
8
+ subject.should respond_to(:delete)
9
+ subject.should respond_to(:head)
10
+ end
11
+
12
+ it "sets available formats" do
13
+ subject.respond_to(:json, :jsonp, :atom)
14
+ subject.settings[:formats].should == [:json, :jsonp, :atom]
15
+ end
16
+
17
+ it "sets prefix option" do
18
+ subject.prefix("my/awesome/api")
19
+ subject.settings[:prefix].should == "my/awesome/api"
20
+ end
21
+
22
+ it "considers prefix and version when building paths" do
23
+ subject.settings.merge!(:prefix => "api", :version => "v1")
24
+ subject.mount_path("users").should == "/api/v1/users"
25
+ end
26
+
27
+ it "stores middleware" do
28
+ subject.use Rack::Auth::Basic
29
+ subject.settings[:middlewares].should == [[Rack::Auth::Basic]]
30
+ end
31
+
32
+ it "stores basic auth info" do
33
+ handler = proc {}
34
+
35
+ subject.basic_auth("Get out!", &handler)
36
+ subject.settings[:auth].should == ["Get out!", handler]
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Rack::API, "Short circuit" do
4
+ before do
5
+ Rack::API.app do
6
+ version :v1 do
7
+ get("/") { error :status => 412, :headers => {"X-Awesome" => "UR NO Awesome"}, :message => "ZOMG! Nothing to see here!" }
8
+ get("/custom") do
9
+ error_message = Object.new
10
+ def error_message.to_rack
11
+ [412, {"X-Awesome" => "UR NO Awesome Indeed"}, ["Keep going!"]]
12
+ end
13
+
14
+ error(error_message)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ it "renders hash error" do
21
+ get "/v1"
22
+ last_response.status.should == 412
23
+ last_response.headers["X-Awesome"].should == "UR NO Awesome"
24
+ last_response.body.should == "ZOMG! Nothing to see here!"
25
+ end
26
+
27
+ it "renders object#to_rack method" do
28
+ get "/v1/custom"
29
+ last_response.status.should == 412
30
+ last_response.headers["X-Awesome"].should == "UR NO Awesome Indeed"
31
+ last_response.body.should == "Keep going!"
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ require "rack/test"
2
+ require "rspec"
3
+ require "rack/api"
4
+ require "base64"
5
+
6
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|file| require file}
7
+
8
+ RSpec.configure do |config|
9
+ config.include Rack::Test::Methods
10
+ config.include Helpers
11
+
12
+ config.before { Rack::API.reset! }
13
+ end
@@ -0,0 +1,10 @@
1
+ class AwesomeMiddleware
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def call(env)
7
+ status, headers, response = @app.call(env)
8
+ [status, headers.merge("X-Awesome" => "U R Awesome"), response]
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def to_awesome
3
+ "U R Awesome"
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Helpers
2
+ def app
3
+ @app ||= Rack::API
4
+ end
5
+
6
+ def basic_auth(username, password)
7
+ "Basic " + Base64.encode64("#{username}:#{password}")
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ class MyApp < Rack::API
2
+ version :v1 do
3
+ get "/" do
4
+ {:myapp => true}
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-api
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Nando Vieira
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-07 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rack
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.1
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: rack-mount
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 0.6.14
35
+ type: :runtime
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: activesupport
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 3.0.6
46
+ type: :runtime
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: 2.5.0
57
+ type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: rack-test
61
+ prerelease: false
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: 0.5.7
68
+ type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: ruby-debug19
72
+ prerelease: false
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id006
81
+ description: Create web app APIs that respond to one or more formats using an elegant DSL.
82
+ email:
83
+ - fnando.vieira@gmail.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files: []
89
+
90
+ files:
91
+ - .gitignore
92
+ - .rspec
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - README.rdoc
96
+ - Rakefile
97
+ - examples/basic_auth.rb
98
+ - examples/custom_class.rb
99
+ - examples/custom_headers.rb
100
+ - examples/middleware.rb
101
+ - examples/multiple_versions.rb
102
+ - examples/params.rb
103
+ - examples/simple.rb
104
+ - lib/rack/api.rb
105
+ - lib/rack/api/app.rb
106
+ - lib/rack/api/formatter.rb
107
+ - lib/rack/api/formatter/base.rb
108
+ - lib/rack/api/formatter/jsonp.rb
109
+ - lib/rack/api/response.rb
110
+ - lib/rack/api/runner.rb
111
+ - lib/rack/api/version.rb
112
+ - rack-api.gemspec
113
+ - spec/rack-api/basic_auth_spec.rb
114
+ - spec/rack-api/format_spec.rb
115
+ - spec/rack-api/headers_spec.rb
116
+ - spec/rack-api/http_methods_spec.rb
117
+ - spec/rack-api/inheritance_spec.rb
118
+ - spec/rack-api/middlewares_spec.rb
119
+ - spec/rack-api/params_spec.rb
120
+ - spec/rack-api/paths_spec.rb
121
+ - spec/rack-api/runner_spec.rb
122
+ - spec/rack-api/short_circuit_spec.rb
123
+ - spec/spec_helper.rb
124
+ - spec/support/awesome_middleware.rb
125
+ - spec/support/core_ext.rb
126
+ - spec/support/helpers.rb
127
+ - spec/support/myapp.rb
128
+ homepage: http://rubygems.org/gems/rack-api
129
+ licenses: []
130
+
131
+ post_install_message:
132
+ rdoc_options: []
133
+
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: "0"
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: "0"
148
+ requirements: []
149
+
150
+ rubyforge_project:
151
+ rubygems_version: 1.7.2
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: Create web app APIs that respond to one or more formats using an elegant DSL.
155
+ test_files:
156
+ - spec/rack-api/basic_auth_spec.rb
157
+ - spec/rack-api/format_spec.rb
158
+ - spec/rack-api/headers_spec.rb
159
+ - spec/rack-api/http_methods_spec.rb
160
+ - spec/rack-api/inheritance_spec.rb
161
+ - spec/rack-api/middlewares_spec.rb
162
+ - spec/rack-api/params_spec.rb
163
+ - spec/rack-api/paths_spec.rb
164
+ - spec/rack-api/runner_spec.rb
165
+ - spec/rack-api/short_circuit_spec.rb
166
+ - spec/spec_helper.rb
167
+ - spec/support/awesome_middleware.rb
168
+ - spec/support/core_ext.rb
169
+ - spec/support/helpers.rb
170
+ - spec/support/myapp.rb