rack-api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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