optionsful 0.1.0 → 0.1.1
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/README.textile +6 -2
- data/Rakefile +11 -0
- data/lib/baurets/optionsful.rb +93 -0
- data/lib/tasks/optionsful.rake +4 -0
- data/rails/init.rb +1 -1
- data/spec/optionsful_spec.rb +115 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +67 -0
- metadata +10 -5
- data/lib/optionsful.rb +0 -89
data/README.textile
CHANGED
@@ -34,9 +34,13 @@ Link: <http://baurets.net/api/resources>; type=text/html; rel=help
|
|
34
34
|
h2. INSTALLATION:
|
35
35
|
|
36
36
|
# Change directory to your Ruby on Rails web application,
|
37
|
-
#
|
37
|
+
# Add gem dependency to @config/environment.rb@:
|
38
38
|
<pre>
|
39
|
-
|
39
|
+
config.gem "optionsful"
|
40
|
+
</pre>
|
41
|
+
# To install it, run:
|
42
|
+
<pre>
|
43
|
+
$ gem install optionsful
|
40
44
|
</pre>
|
41
45
|
# Have fun!
|
42
46
|
|
data/Rakefile
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Baurets
|
2
|
+
|
3
|
+
class Optionsful
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
unless env["REQUEST_METHOD"] == "OPTIONS"
|
11
|
+
@app.call(env)
|
12
|
+
else
|
13
|
+
extract_options_information(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def extract_options_information(env)
|
20
|
+
[204, {"Allow" => extract_allowed_methods(env), "Link" => build_help_link}, ""]
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_allowed_methods(env)
|
24
|
+
# do routing introspection:
|
25
|
+
routes = do_routing_introspection
|
26
|
+
# do request path investigation
|
27
|
+
route_guess = guess_route(routes, env["PATH_INFO"])
|
28
|
+
allows = ""
|
29
|
+
# do the matches:
|
30
|
+
routes.each do |route|
|
31
|
+
if route.first == route_guess
|
32
|
+
allows += route[1].to_s.upcase! + "|"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
allows = allows.split("|").join(", ")
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_help_link
|
39
|
+
#PENDING
|
40
|
+
"<http://baurets.net/api/resources>; type=text/html; rel=help"
|
41
|
+
end
|
42
|
+
|
43
|
+
def guess_route(routes, path)
|
44
|
+
parts = prepare_request_path(path)
|
45
|
+
guess = []
|
46
|
+
index = 0
|
47
|
+
parts.each do |part|
|
48
|
+
if is_part_static?(routes, index, part)
|
49
|
+
guess << part
|
50
|
+
else
|
51
|
+
guess << :dynamic
|
52
|
+
end
|
53
|
+
index += 1
|
54
|
+
end
|
55
|
+
guess
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_part_static?(routes, index, value)
|
59
|
+
routes.each do |route|
|
60
|
+
if route[0][index] == value
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_routing_introspection
|
68
|
+
routes = []
|
69
|
+
ActionController::Routing::Routes.routes.each do |route|
|
70
|
+
static_path = []
|
71
|
+
route.segments.each do |segment|
|
72
|
+
if segment.kind_of?(ActionController::Routing::StaticSegment)
|
73
|
+
static_path << segment.value if (segment.respond_to?(:value) && segment.value != "/")
|
74
|
+
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
75
|
+
#TODO ignoring (.:format), think about it:
|
76
|
+
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
routes << [static_path, route.conditions[:method]] unless route.conditions[:method].nil?
|
80
|
+
end
|
81
|
+
routes
|
82
|
+
end
|
83
|
+
|
84
|
+
def prepare_request_path(path)
|
85
|
+
path = path[0..(path.rindex('.')-1)] if path.include?('.') #TODO ignoring (.:format), think about it
|
86
|
+
path_parts = path.split("/")
|
87
|
+
path_parts.delete("")
|
88
|
+
path_parts
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/rails/init.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# Adding Optionsful to the Rack middleware stack:
|
2
|
-
ActionController::Dispatcher.middleware.use
|
2
|
+
ActionController::Dispatcher.middleware.use Baurets::Optionsful
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe "Baurets::Optionsful," do
|
5
|
+
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
ActionController::Routing::Routes.draw do |map|
|
10
|
+
map.resources :posts, :has_many => :comments
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
describe "as a Rack middleware" do
|
16
|
+
|
17
|
+
it "is a Ruby object that responds to call;" do
|
18
|
+
assert Baurets::Optionsful.new(app).respond_to? :call
|
19
|
+
end
|
20
|
+
|
21
|
+
it "takes exactly one argument, (the environment) and returns an Array;" do
|
22
|
+
response = Baurets::Optionsful.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
23
|
+
assert response.kind_of?(Array)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "the returned Array must have exactly three values: the status, the headers and the body;" do
|
27
|
+
response = Baurets::Optionsful.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
28
|
+
assert response.size.should == 3
|
29
|
+
assert response[0].kind_of? Fixnum
|
30
|
+
assert response[1].kind_of? Hash
|
31
|
+
assert response[2].kind_of? String
|
32
|
+
end
|
33
|
+
|
34
|
+
it "must be nice, acting somewhere on a Rack middleware stack." do
|
35
|
+
response = fake_app.call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
36
|
+
assert response.size.should == 3
|
37
|
+
assert response[0].kind_of? Fixnum
|
38
|
+
assert response[0].should == 204
|
39
|
+
assert response[1].kind_of? Hash
|
40
|
+
assert response[1]["Allow"]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
describe "to collect the allowed HTTP methods" do
|
47
|
+
|
48
|
+
describe "overrides Rails routing recognition " do
|
49
|
+
|
50
|
+
describe "must understand the default Rails resource routing: " do
|
51
|
+
|
52
|
+
it "the index action displays a list of all posts in response of a GET request;" do
|
53
|
+
response = http_options_request("/posts")
|
54
|
+
assert response.kind_of?(Array)
|
55
|
+
assert allows?(response[1], "GET")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "the new action return from a GET request an HTML form for creating a new post;" do
|
59
|
+
response = http_options_request("/posts/new")
|
60
|
+
assert response.kind_of?(Array)
|
61
|
+
assert allows?(response[1], "GET")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "the create action uses POST to create a new post instance;" do
|
65
|
+
response = http_options_request("/posts")
|
66
|
+
assert response.kind_of?(Array)
|
67
|
+
assert allows?(response[1], "POST")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "the show action display a specific post in response of a GET request;" do
|
71
|
+
response = http_options_request("/posts/1")
|
72
|
+
assert response.kind_of?(Array)
|
73
|
+
assert allows?(response[1], "GET")
|
74
|
+
end
|
75
|
+
|
76
|
+
it "the edit action return an HTML form for editing a post in response of a GET request;" do
|
77
|
+
response = http_options_request("/posts/1/edit")
|
78
|
+
assert response.kind_of?(Array)
|
79
|
+
assert allows?(response[1], "GET")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "the update action uses PUT to update a specific post;" do
|
83
|
+
response = http_options_request("/posts/1")
|
84
|
+
assert response.kind_of?(Array)
|
85
|
+
assert allows?(response[1], "PUT")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "the destroy action uses DELETE to delete a specific post." do
|
89
|
+
response = http_options_request("/posts/1")
|
90
|
+
assert response.kind_of?(Array)
|
91
|
+
assert allows?(response[1], "DELETE")
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe " dispatch the call to the application unless HTTP method equals OPTIONS " do
|
101
|
+
|
102
|
+
it " method GET example" do
|
103
|
+
complex_env = mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/lobster" })
|
104
|
+
response = fake_app.call(complex_env)
|
105
|
+
assert response.kind_of?(Array)
|
106
|
+
assert response.size.should == 3
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
after(:all) do
|
112
|
+
ActionController::Routing::Routes.reload!
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'baurets','optionsful.rb')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'sinatra'
|
5
|
+
require 'rack/test'
|
6
|
+
require 'rack'
|
7
|
+
require 'rack/lobster'
|
8
|
+
require 'spec'
|
9
|
+
require 'spec/autorun'
|
10
|
+
require 'spec/interop/test'
|
11
|
+
require 'action_controller'
|
12
|
+
require 'ruby-debug'
|
13
|
+
# require 'baurets/optionsful'
|
14
|
+
|
15
|
+
# set test environment set :environment, :test set :run, false set :raise_errors, true set :logging, false
|
16
|
+
|
17
|
+
DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "rack.errors" => StringIO.new,
|
18
|
+
"rack.multithread" => true, "rack.multiprocess" => true, "rack.run_once" => false, }
|
19
|
+
|
20
|
+
SAMPLE_ENV = {"rack.session"=>{:session_id=>"f66c8af1e89f318775e923159e046e71"}, "SERVER_NAME"=>"localhost",
|
21
|
+
"HTTP_HOST"=>"localhost:3332", "REMOTE_HOST"=>"localhost", "HTTP_USER_AGENT"=>"HTTP%20Client0.9.1
|
22
|
+
CFNetwork/438.14 Darwin/9.8.0 (i386) (MacBook4%2C1)", "rack.url_scheme"=>"http", "REQUEST_PATH"=>"/",
|
23
|
+
"SERVER_PROTOCOL"=>"HTTP/1.1", "rack.errors"=>"", "PATH_INFO"=>"/", "REMOTE_ADDR"=>"127.0.0.1",
|
24
|
+
"SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.8.7/2010-06-23)", "rack.run_once"=>false, "rack.version"=>[1, 1],
|
25
|
+
"SCRIPT_NAME"=>"", "rack.multithread"=>false, "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_URI"=>"",
|
26
|
+
"rack.multiprocess"=>false, "SERVER_PORT"=>"3332", "rack.session.options"=>{:domain=>"nilly", :path=>"/",
|
27
|
+
:key=>"_session_id", :httponly=>true, :expire_after=>300, :id=>"f66c8af1e89f318775e923159e046e71"},
|
28
|
+
"REQUEST_METHOD"=>"HEAD", "GATEWAY_INTERFACE"=>"CGI/1.1", "QUERY_STRING"=>"", "HTTP_CONNECTION"=>"close"}
|
29
|
+
|
30
|
+
FAKE_ENV = SAMPLE_ENV.merge!(DEFAULT_ENV)
|
31
|
+
|
32
|
+
def app
|
33
|
+
app = Rack::Builder.new {
|
34
|
+
use Rack::CommonLogger
|
35
|
+
use Rack::ShowExceptions
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def fake_app
|
41
|
+
app = Rack::Builder.new {
|
42
|
+
use Rack::CommonLogger
|
43
|
+
use Rack::ShowExceptions
|
44
|
+
use Baurets::Optionsful
|
45
|
+
map "/lobster" do
|
46
|
+
use Rack::Lint
|
47
|
+
run Rack::Lobster.new
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def mock_env(options = {})
|
53
|
+
FAKE_ENV.merge!(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def http_options_request(path)
|
57
|
+
complex_env = mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => path })
|
58
|
+
response = Baurets::Optionsful.new(app).call(complex_env)
|
59
|
+
response
|
60
|
+
end
|
61
|
+
|
62
|
+
def allows?(headers, method)
|
63
|
+
headers["Allow"].include?(method)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optionsful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Marco Antonio Gonzalez Junior
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-07-
|
18
|
+
date: 2010-07-19 00:00:00 -03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -30,8 +30,13 @@ extra_rdoc_files: []
|
|
30
30
|
files:
|
31
31
|
- README.textile
|
32
32
|
- MIT-LICENSE
|
33
|
-
- lib/optionsful.rb
|
33
|
+
- lib/baurets/optionsful.rb
|
34
34
|
- rails/init.rb
|
35
|
+
- lib/tasks/optionsful.rake
|
36
|
+
- Rakefile
|
37
|
+
- spec/optionsful_spec.rb
|
38
|
+
- spec/spec.opts
|
39
|
+
- spec/spec_helper.rb
|
35
40
|
has_rdoc: true
|
36
41
|
homepage: http://optionsful.rubyforge.org
|
37
42
|
licenses: []
|
data/lib/optionsful.rb
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
class Optionsful
|
2
|
-
|
3
|
-
def initialize(app)
|
4
|
-
@app = app
|
5
|
-
end
|
6
|
-
|
7
|
-
def call(env)
|
8
|
-
unless env["REQUEST_METHOD"] == "OPTIONS"
|
9
|
-
@app.call(env)
|
10
|
-
else
|
11
|
-
extract_options_information(env)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def extract_options_information(env)
|
18
|
-
[204, {"Allow" => extract_allowed_methods(env), "Link" => build_help_link}, ""]
|
19
|
-
end
|
20
|
-
|
21
|
-
def extract_allowed_methods(env)
|
22
|
-
# do routing introspection:
|
23
|
-
routes = do_routing_introspection
|
24
|
-
# do request path investigation
|
25
|
-
route_guess = guess_route(routes, env["PATH_INFO"])
|
26
|
-
allows = ""
|
27
|
-
# do the matches:
|
28
|
-
routes.each do |route|
|
29
|
-
if route.first == route_guess
|
30
|
-
allows += route[1].to_s.upcase! + "|"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
allows = allows.split("|").join(", ")
|
34
|
-
end
|
35
|
-
|
36
|
-
def build_help_link
|
37
|
-
#PENDING
|
38
|
-
"<http://baurets.net/api/resources>; type=text/html; rel=help"
|
39
|
-
end
|
40
|
-
|
41
|
-
def guess_route(routes, path)
|
42
|
-
parts = prepare_request_path(path)
|
43
|
-
guess = []
|
44
|
-
index = 0
|
45
|
-
parts.each do |part|
|
46
|
-
if is_part_static?(routes, index, part)
|
47
|
-
guess << part
|
48
|
-
else
|
49
|
-
guess << :dynamic
|
50
|
-
end
|
51
|
-
index += 1
|
52
|
-
end
|
53
|
-
guess
|
54
|
-
end
|
55
|
-
|
56
|
-
def is_part_static?(routes, index, value)
|
57
|
-
routes.each do |route|
|
58
|
-
if route[0][index] == value
|
59
|
-
return true
|
60
|
-
end
|
61
|
-
end
|
62
|
-
return false
|
63
|
-
end
|
64
|
-
|
65
|
-
def do_routing_introspection
|
66
|
-
routes = []
|
67
|
-
ActionController::Routing::Routes.routes.each do |route|
|
68
|
-
static_path = []
|
69
|
-
route.segments.each do |segment|
|
70
|
-
if segment.kind_of?(ActionController::Routing::StaticSegment)
|
71
|
-
static_path << segment.value if (segment.respond_to?(:value) && segment.value != "/")
|
72
|
-
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
73
|
-
#TODO ignoring (.:format), think about it:
|
74
|
-
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
routes << [static_path, route.conditions[:method]] unless route.conditions[:method].nil?
|
78
|
-
end
|
79
|
-
routes
|
80
|
-
end
|
81
|
-
|
82
|
-
def prepare_request_path(path)
|
83
|
-
path = path[0..(path.rindex('.')-1)] if path.include?('.') #TODO ignoring (.:format), think about it
|
84
|
-
path_parts = path.split("/")
|
85
|
-
path_parts.delete("")
|
86
|
-
path_parts
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|