http_router 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/ext/rack/rack_mapper.rb +57 -0
- data/lib/ext/rack/uri_escape.rb +26 -0
- data/lib/http_router/glob.rb +2 -1
- data/lib/http_router/{sinatra.rb → interface/sinatra.rb} +7 -7
- data/lib/http_router/node.rb +109 -59
- data/lib/http_router/path.rb +23 -2
- data/lib/http_router/response.rb +36 -15
- data/lib/http_router/root.rb +12 -63
- data/lib/http_router/route.rb +74 -24
- data/lib/http_router/variable.rb +16 -9
- data/lib/http_router.rb +55 -31
- data/spec/generate_spec.rb +81 -12
- data/spec/misc_spec.rb +38 -0
- data/spec/rack/dispatch_spec.rb +2 -2
- data/spec/rack/generate_spec.rb +1 -1
- data/spec/recognize_spec.rb +25 -2
- data/spec/sinatra/recognize_spec.rb +2 -2
- metadata +14 -6
- data/lib/rack/uri_escape.rb +0 -38
data/lib/http_router/route.rb
CHANGED
@@ -4,14 +4,25 @@ class HttpRouter
|
|
4
4
|
attr_accessor :trailing_slash_ignore, :partially_match, :default_values
|
5
5
|
|
6
6
|
def initialize(base, path)
|
7
|
-
@
|
7
|
+
@router = base
|
8
8
|
@path = path
|
9
9
|
@original_path = path.dup
|
10
10
|
@partially_match = extract_partial_match(path)
|
11
11
|
@trailing_slash_ignore = extract_trailing_slash(path)
|
12
12
|
@variable_store = {}
|
13
13
|
@matches_with = {}
|
14
|
+
@additional_matchers = {}
|
14
15
|
@conditions = {}
|
16
|
+
@default_values = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def significant_variable_names
|
20
|
+
unless @significant_variable_names
|
21
|
+
@significant_variable_names = @paths.map { |p| p.variable_names }
|
22
|
+
@significant_variable_names.flatten!
|
23
|
+
@significant_variable_names.uniq!
|
24
|
+
end
|
25
|
+
@significant_variable_names
|
15
26
|
end
|
16
27
|
|
17
28
|
def method_missing(method, *args, &block)
|
@@ -22,9 +33,27 @@ class HttpRouter
|
|
22
33
|
end
|
23
34
|
end
|
24
35
|
|
36
|
+
def with_options(options)
|
37
|
+
if options && options[:matching]
|
38
|
+
default(options[:matching])
|
39
|
+
end
|
40
|
+
if options && options[:conditions]
|
41
|
+
condition(options[:conditions])
|
42
|
+
end
|
43
|
+
if options && options[:default_values]
|
44
|
+
default(options[:default_values])
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
25
49
|
def name(name)
|
26
50
|
@name = name
|
27
|
-
|
51
|
+
router.named_routes[name] = self
|
52
|
+
end
|
53
|
+
|
54
|
+
def default(v)
|
55
|
+
@default_values.merge!(v)
|
56
|
+
self
|
28
57
|
end
|
29
58
|
|
30
59
|
def get
|
@@ -36,7 +65,7 @@ class HttpRouter
|
|
36
65
|
end
|
37
66
|
|
38
67
|
def head
|
39
|
-
request_method('
|
68
|
+
request_method('HEAD')
|
40
69
|
end
|
41
70
|
|
42
71
|
def put
|
@@ -62,11 +91,19 @@ class HttpRouter
|
|
62
91
|
end
|
63
92
|
alias_method :conditions, :condition
|
64
93
|
|
65
|
-
def matching(
|
94
|
+
def matching(match)
|
66
95
|
guard_compiled
|
67
|
-
|
68
|
-
|
69
|
-
|
96
|
+
match.each do |var_name, matchers|
|
97
|
+
matchers = Array(matchers)
|
98
|
+
matchers.each do |m|
|
99
|
+
if m.respond_to?(:call)
|
100
|
+
(@additional_matchers[var_name] ||= []) << m
|
101
|
+
else
|
102
|
+
@matches_with.key?(var_name) ?
|
103
|
+
raise :
|
104
|
+
@matches_with[var_name] = m
|
105
|
+
end
|
106
|
+
end
|
70
107
|
end
|
71
108
|
self
|
72
109
|
end
|
@@ -93,9 +130,14 @@ class HttpRouter
|
|
93
130
|
def compile
|
94
131
|
unless @paths
|
95
132
|
@paths = compile_paths
|
133
|
+
@paths.each_with_index do |p1, i|
|
134
|
+
@paths[i+1, @paths.size].each do |p2|
|
135
|
+
raise AmbiguousRouteException.new if p1 === p2
|
136
|
+
end
|
137
|
+
end
|
96
138
|
@paths.each do |path|
|
97
139
|
path.route = self
|
98
|
-
current_node =
|
140
|
+
current_node = router.root.add_path(path)
|
99
141
|
working_set = current_node.add_request_methods(@conditions)
|
100
142
|
working_set.each do |current_node|
|
101
143
|
current_node.value = path
|
@@ -138,25 +180,31 @@ class HttpRouter
|
|
138
180
|
|
139
181
|
def url(*args)
|
140
182
|
options = args.last.is_a?(Hash) ? args.pop : nil
|
183
|
+
options ||= {} if default_values
|
141
184
|
options = default_values.merge(options) if default_values && options
|
142
|
-
path =
|
185
|
+
path = if args.empty?
|
186
|
+
matching_path(options)
|
187
|
+
else
|
188
|
+
matching_path(args, options)
|
189
|
+
end
|
143
190
|
raise UngeneratableRouteException.new unless path
|
144
191
|
path.url(args, options)
|
145
192
|
end
|
146
193
|
|
147
194
|
private
|
195
|
+
|
196
|
+
attr_reader :router
|
148
197
|
|
149
|
-
def matching_path(params)
|
198
|
+
def matching_path(params, other_hash = nil)
|
150
199
|
if @paths.size == 1
|
151
200
|
@paths.first
|
152
201
|
else
|
153
202
|
if params.is_a?(Array)
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
nil
|
203
|
+
significant_keys = other_hash && significant_variable_names & other_hash.keys
|
204
|
+
@paths.find { |path|
|
205
|
+
var_count = significant_keys ? params.size + significant_keys.size : params.size
|
206
|
+
path.variables.size == var_count
|
207
|
+
}
|
160
208
|
else
|
161
209
|
@paths.reverse_each do |path|
|
162
210
|
if params && !params.empty?
|
@@ -181,7 +229,8 @@ class HttpRouter
|
|
181
229
|
def extract_extension(path)
|
182
230
|
if match = path.match(/^(.*)(\.:([a-zA-Z_]+))$/)
|
183
231
|
path.replace(match[1])
|
184
|
-
|
232
|
+
v_name = match[3].to_sym
|
233
|
+
router.variable(match[3].to_sym, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
|
185
234
|
elsif match = path.match(/^(.*)(\.([a-zA-Z_]+))$/)
|
186
235
|
path.replace(match[1])
|
187
236
|
match[3]
|
@@ -221,14 +270,14 @@ class HttpRouter
|
|
221
270
|
paths.map do |path|
|
222
271
|
original_path = path.dup
|
223
272
|
extension = extract_extension(path)
|
224
|
-
new_path =
|
273
|
+
new_path = router.split(path).map do |part|
|
225
274
|
case part[0]
|
226
275
|
when ?:
|
227
276
|
v_name = part[1, part.size].to_sym
|
228
|
-
@variable_store[v_name] ||=
|
277
|
+
@variable_store[v_name] ||= router.variable(v_name, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
|
229
278
|
when ?*
|
230
279
|
v_name = part[1, part.size].to_sym
|
231
|
-
@variable_store[v_name] ||=
|
280
|
+
@variable_store[v_name] ||= router.glob(v_name, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
|
232
281
|
else
|
233
282
|
generate_interstitial_parts(part)
|
234
283
|
end
|
@@ -245,13 +294,14 @@ class HttpRouter
|
|
245
294
|
part_segments.map do |seg|
|
246
295
|
new_seg = if seg[0] == ?:
|
247
296
|
next_index = index + 1
|
297
|
+
v_name = seg[1, seg.size].to_sym
|
298
|
+
matcher = @matches_with[v_name]
|
248
299
|
scan_regex = if next_index == part_segments.size
|
249
|
-
/^[^\/]+/
|
300
|
+
matcher || /^[^\/]+/
|
250
301
|
else
|
251
|
-
|
302
|
+
/^#{matcher || '.*?'}(?=#{Regexp.quote(part_segments[next_index])})/
|
252
303
|
end
|
253
|
-
v_name
|
254
|
-
@variable_store[v_name] ||= Variable.new(@base, v_name, scan_regex)
|
304
|
+
@variable_store[v_name] ||= router.variable(v_name, scan_regex, @additional_matchers && @additional_matchers[v_name])
|
255
305
|
else
|
256
306
|
/^#{Regexp.quote(seg)}/
|
257
307
|
end
|
data/lib/http_router/variable.rb
CHANGED
@@ -2,25 +2,32 @@ class HttpRouter
|
|
2
2
|
class Variable
|
3
3
|
attr_reader :name, :matches_with
|
4
4
|
|
5
|
-
def initialize(base, name, matches_with = nil)
|
6
|
-
@
|
5
|
+
def initialize(base, name, matches_with = nil, additional_matchers = nil)
|
6
|
+
@router = base
|
7
7
|
@name = name
|
8
8
|
@matches_with = matches_with
|
9
|
+
@additional_matchers = additional_matchers
|
9
10
|
end
|
10
|
-
|
11
|
-
def matches(parts, whole_path)
|
11
|
+
|
12
|
+
def matches(env, parts, whole_path)
|
12
13
|
if @matches_with.nil?
|
13
|
-
parts.first
|
14
|
-
elsif @matches_with
|
14
|
+
additional_matchers(env, parts.first) ? parts.first : nil
|
15
|
+
elsif @matches_with and match = @matches_with.match(whole_path) and additional_matchers(env, parts.first)
|
15
16
|
whole_path.slice!(0, match[0].size)
|
16
|
-
parts.replace(
|
17
|
+
parts.replace(router.split(whole_path))
|
17
18
|
match[0]
|
18
19
|
end
|
19
20
|
end
|
20
|
-
|
21
|
+
|
22
|
+
def additional_matchers(env, test)
|
23
|
+
@additional_matchers.nil? || @additional_matchers.all?{|m| m.call(env, test)}
|
24
|
+
end
|
25
|
+
|
21
26
|
def ===(part)
|
22
27
|
@matches_with.nil?
|
23
28
|
end
|
24
|
-
|
29
|
+
|
30
|
+
protected
|
31
|
+
attr_reader :router
|
25
32
|
end
|
26
33
|
end
|
data/lib/http_router.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
$LOAD_PATH << File.dirname(__FILE__)
|
2
2
|
require 'rack'
|
3
|
-
require 'rack/uri_escape'
|
3
|
+
require 'ext/rack/uri_escape'
|
4
4
|
|
5
5
|
class HttpRouter
|
6
6
|
autoload :Node, 'http_router/node'
|
@@ -11,21 +11,24 @@ class HttpRouter
|
|
11
11
|
autoload :Response, 'http_router/response'
|
12
12
|
autoload :Path, 'http_router/path'
|
13
13
|
|
14
|
-
UngeneratableRouteException
|
15
|
-
MissingParameterException
|
16
|
-
TooManyParametersException
|
17
|
-
AlreadyCompiledException
|
18
|
-
|
14
|
+
UngeneratableRouteException = Class.new(RuntimeError)
|
15
|
+
MissingParameterException = Class.new(RuntimeError)
|
16
|
+
TooManyParametersException = Class.new(RuntimeError)
|
17
|
+
AlreadyCompiledException = Class.new(RuntimeError)
|
18
|
+
AmbiguousRouteException = Class.new(RuntimeError)
|
19
|
+
UnsupportedRequestConditionError = Class.new(RuntimeError)
|
20
|
+
AmbiguousVariableException = Class.new(RuntimeError)
|
19
21
|
|
20
22
|
attr_reader :named_routes, :routes, :root
|
21
23
|
|
22
|
-
def initialize(options = nil)
|
24
|
+
def initialize(options = nil, &block)
|
23
25
|
@default_app = options && options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
|
24
26
|
@ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
|
25
27
|
@redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
|
26
28
|
@routes = []
|
27
29
|
@named_routes = {}
|
28
30
|
reset!
|
31
|
+
instance_eval(&block) if block
|
29
32
|
end
|
30
33
|
|
31
34
|
def ignore_trailing_slash?
|
@@ -46,41 +49,41 @@ class HttpRouter
|
|
46
49
|
@default_app = app
|
47
50
|
end
|
48
51
|
|
49
|
-
def split(path
|
50
|
-
path
|
51
|
-
with_delimiter ? path.split('(/)') : path.split('/')
|
52
|
+
def split(path)
|
53
|
+
(path[0] == ?/ ? path[1, path.size] : path).split('/')
|
52
54
|
end
|
53
55
|
|
54
|
-
def add(path)
|
55
|
-
route = Route.new(self, path.dup)
|
56
|
+
def add(path, options = nil)
|
57
|
+
route = Route.new(self, path.dup).with_options(options)
|
56
58
|
@routes << route
|
57
59
|
route
|
58
60
|
end
|
59
61
|
|
60
|
-
def get(path)
|
61
|
-
add(path).get
|
62
|
+
def get(path, options = nil)
|
63
|
+
add(path, options).get
|
62
64
|
end
|
63
65
|
|
64
|
-
def post(path)
|
65
|
-
add(path).post
|
66
|
+
def post(path, options = nil)
|
67
|
+
add(path, options).post
|
66
68
|
end
|
67
69
|
|
68
|
-
def put(path)
|
69
|
-
add(path).put
|
70
|
+
def put(path, options = nil)
|
71
|
+
add(path, options).put
|
70
72
|
end
|
71
73
|
|
72
|
-
def delete(path)
|
73
|
-
add(path).delete
|
74
|
+
def delete(path, options = nil)
|
75
|
+
add(path, options).delete
|
74
76
|
end
|
75
77
|
|
76
|
-
def only_get(path)
|
77
|
-
add(path).only_get
|
78
|
+
def only_get(path, options = nil)
|
79
|
+
add(path, options).only_get
|
78
80
|
end
|
79
81
|
|
80
82
|
def recognize(env)
|
81
83
|
response = @root.find(env.is_a?(Hash) ? Rack::Request.new(env) : env)
|
82
84
|
end
|
83
85
|
|
86
|
+
# Generate a URL for a specified route.
|
84
87
|
def url(route, *args)
|
85
88
|
case route
|
86
89
|
when Symbol
|
@@ -92,6 +95,7 @@ class HttpRouter
|
|
92
95
|
end
|
93
96
|
end
|
94
97
|
|
98
|
+
# Allow the router to be called via Rake / Middleware.
|
95
99
|
def call(env)
|
96
100
|
request = Rack::Request.new(env)
|
97
101
|
if redirect_trailing_slash? && (request.head? || request.get?) && request.path_info[-1] == ?/
|
@@ -99,19 +103,39 @@ class HttpRouter
|
|
99
103
|
response.redirect(request.path_info[0, request.path_info.size - 1], 302)
|
100
104
|
response.finish
|
101
105
|
else
|
102
|
-
response = recognize(request)
|
103
106
|
env['router'] = self
|
104
|
-
if response
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
response.
|
110
|
-
|
111
|
-
|
107
|
+
if response = recognize(request)
|
108
|
+
if response.matched? && response.route.dest && response.route.dest.respond_to?(:call)
|
109
|
+
process_params(env, response)
|
110
|
+
consume_path!(request, response) if response.partial_match?
|
111
|
+
return response.route.dest.call(env)
|
112
|
+
elsif !response.matched?
|
113
|
+
return [response.status, response.headers, []]
|
114
|
+
end
|
112
115
|
end
|
116
|
+
@default_app.call(env)
|
113
117
|
end
|
114
118
|
end
|
119
|
+
|
120
|
+
# Returns a new node
|
121
|
+
def node(*args)
|
122
|
+
Node.new(self, *args)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns a new request node
|
126
|
+
def request_node(*args)
|
127
|
+
RequestNode.new(self, *args)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns a new variable
|
131
|
+
def variable(*args)
|
132
|
+
Variable.new(self, *args)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns a new glob
|
136
|
+
def glob(*args)
|
137
|
+
Glob.new(self, *args)
|
138
|
+
end
|
115
139
|
|
116
140
|
private
|
117
141
|
|
data/spec/generate_spec.rb
CHANGED
@@ -32,21 +32,59 @@ describe "HttpRouter#generate" do
|
|
32
32
|
@router.add("/:var").name(:test).compile
|
33
33
|
@router.url(:test, 'test', :query => 'string').should == '/test?query=string'
|
34
34
|
end
|
35
|
-
|
36
|
-
it "should generate with
|
37
|
-
@router.add("
|
38
|
-
@router.url(:test, '
|
35
|
+
|
36
|
+
it "should generate with multiple dynamics" do
|
37
|
+
@router.add("/:var/:baz").name(:test).compile
|
38
|
+
@router.url(:test, 'one', 'two').should == '/one/two'
|
39
|
+
@router.url(:test, :var => 'one', :baz => 'two').should == '/one/two'
|
39
40
|
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
context "with a :format" do
|
43
|
+
it "should generate with a format" do
|
44
|
+
@router.add("/test.:format").name(:test).compile
|
45
|
+
@router.url(:test, 'html').should == '/test.html'
|
46
|
+
end
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
it "should generate with a format as a hash" do
|
49
|
+
@router.add("/test.:format").name(:test).compile
|
50
|
+
@router.url(:test, :format => 'html').should == '/test.html'
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should generate with format as a symbol" do
|
54
|
+
@router.add("/test.:format").name(:test).compile
|
55
|
+
@router.url(:test, :format => :html).should == '/test.html'
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should generate with an optional format" do
|
59
|
+
@router.add("/test(.:format)").name(:test).compile
|
60
|
+
@router.url(:test, 'html').should == '/test.html'
|
61
|
+
@router.url(:test).should == '/test'
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should generate a dynamic path and a format" do
|
65
|
+
@router.add("/:var1.:format").name(:test).compile
|
66
|
+
@router.url(:test, 'var', :format => 'html').should == '/var.html'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should generate a dynamic path and an optional format" do
|
70
|
+
@router.add("/:var1(.:format)").name(:test).compile
|
71
|
+
@router.url(:test, 'var').should == '/var'
|
72
|
+
@router.url(:test, 'var', :format => 'html').should == '/var.html'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should generate multiple dynamics and a format" do
|
76
|
+
@router.add("/:foo/:bar.:format").name(:test).compile
|
77
|
+
@router.url(:test, 'var', 'baz', 'html').should == '/var/baz.html'
|
78
|
+
@router.url(:test, :foo => 'var', :bar => 'baz', :format => 'html').should == '/var/baz.html'
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should generate multiple dynamics and an optional format" do
|
82
|
+
@router.add("/:foo/:bar(.:format)").name(:test).compile
|
83
|
+
@router.url(:test, 'var', 'baz').should == '/var/baz'
|
84
|
+
@router.url(:test, 'var', 'baz', 'html').should == '/var/baz.html'
|
85
|
+
@router.url(:test, :foo => 'var', :bar => 'baz').should == '/var/baz'
|
86
|
+
@router.url(:test, :foo => 'var', :bar => 'baz', :format => 'html').should == '/var/baz.html'
|
87
|
+
end
|
50
88
|
end
|
51
89
|
|
52
90
|
context "with optional parts" do
|
@@ -58,7 +96,38 @@ describe "HttpRouter#generate" do
|
|
58
96
|
@router.url(:test, :var1 => 'var', :var2 => 'fooz').should == '/var/fooz'
|
59
97
|
proc{@router.url(:test, :var2 => 'fooz').should == '/var/fooz'}.should raise_error(HttpRouter::UngeneratableRouteException)
|
60
98
|
end
|
99
|
+
it "should generate with a format" do
|
100
|
+
@router.add("/:var1(/:var2.:format)").name(:test).compile
|
101
|
+
@router.url(:test, 'var').should == '/var'
|
102
|
+
@router.url(:test, 'var', 'fooz', 'html').should == '/var/fooz.html'
|
103
|
+
@router.url(:test, :var1 => 'var').should == '/var'
|
104
|
+
@router.url(:test, :var1 => 'var', :var2 => 'fooz', :format => 'html').should == '/var/fooz.html'
|
105
|
+
end
|
106
|
+
it "should generate with an embeded optional" do
|
107
|
+
@router.add("/:var1(/:var2(/:var3))").name(:test).compile
|
108
|
+
@router.url(:test, 'var').should == '/var'
|
109
|
+
@router.url(:test, 'var', 'fooz').should == '/var/fooz'
|
110
|
+
@router.url(:test, 'var', 'fooz', 'baz').should == '/var/fooz/baz'
|
111
|
+
@router.url(:test, :var1 => 'var').should == '/var'
|
112
|
+
@router.url(:test, :var1 => 'var', :var2 => 'fooz', :var3 => 'baz').should == '/var/fooz/baz'
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should support optional plus optional format" do
|
116
|
+
@router.add("/:var1(/:var2)(.:format)").name(:test).compile
|
117
|
+
@router.url(:test, 'var').should == '/var'
|
118
|
+
@router.url(:test, 'var', 'fooz').should == '/var/fooz'
|
119
|
+
@router.url(:test, 'var', 'fooz', 'html').should == '/var/fooz.html'
|
120
|
+
@router.url(:test, :var1 => 'var').should == '/var'
|
121
|
+
@router.url(:test, :var1 => 'var', :var2 => 'fooz', :format => 'html').should == '/var/fooz.html'
|
122
|
+
@router.url(:test, :var1 => 'var', :format => 'html').should == '/var.html'
|
123
|
+
end
|
61
124
|
end
|
62
125
|
|
126
|
+
context "with default values" do
|
127
|
+
it "should generate with all params" do
|
128
|
+
@router.add("/:var").default(:page => 1).name(:test).compile
|
129
|
+
@router.url(:test, 123).should == "/123?page=1"
|
130
|
+
end
|
131
|
+
end
|
63
132
|
end
|
64
133
|
end
|
data/spec/misc_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
describe "HttpRouter" do
|
2
|
+
before(:each) do
|
3
|
+
@router = HttpRouter.new
|
4
|
+
end
|
5
|
+
|
6
|
+
context "route adding" do
|
7
|
+
it "should work with options too" do
|
8
|
+
route = @router.add('/:test', :conditions => {:request_method => %w{HEAD GET}, :host => 'host1'}, :default_values => {:page => 1}, :matching => {:test => /^\d+/}).to :test
|
9
|
+
@router.recognize(Rack::MockRequest.env_for('http://host2/variable', :method => 'POST')).matched?.should be_false
|
10
|
+
@router.recognize(Rack::MockRequest.env_for('http://host1/variable', :method => 'POST')).matched?.should be_false
|
11
|
+
@router.recognize(Rack::MockRequest.env_for('http://host2/123', :method => 'POST')).matched?.should be_false
|
12
|
+
@router.recognize(Rack::MockRequest.env_for('http://host1/123', :method => 'POST')).matched?.should be_false
|
13
|
+
@router.recognize(Rack::MockRequest.env_for('http://host1/123', :method => 'GET')).route.dest.should == :test
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "instance_eval block" do
|
18
|
+
HttpRouter.new {
|
19
|
+
add('/test').to :test
|
20
|
+
}.recognize(Rack::MockRequest.env_for('/test', :method => 'GET')).dest.should == :test
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
context "exceptions" do
|
25
|
+
it "should be smart about multiple optionals" do
|
26
|
+
proc {@router.add("/:var1(/:var2)(/:var3)").compile}.should raise_error(HttpRouter::AmbiguousRouteException)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should raise on identical variable name" do
|
30
|
+
proc {@router.add("/:var1(/:var1)(/:var1)").compile}.should raise_error(HttpRouter::AmbiguousVariableException)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should raise on unsupported request methods" do
|
34
|
+
proc {@router.add("/").condition(:flibberty => 'gibet').compile}.should raise_error(HttpRouter::UnsupportedRequestConditionError)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/spec/rack/dispatch_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
route_set = HttpRouter.new
|
2
2
|
route_set.extend(CallWithMockRequestMixin)
|
3
3
|
|
4
|
-
describe "
|
4
|
+
describe "HttpRouter route dispatching with redirect_on_trailing_delimiters" do
|
5
5
|
before(:each) do
|
6
6
|
@route_set = HttpRouter.new(:redirect_trailing_slash => true)
|
7
7
|
@route_set.extend(CallWithMockRequestMixin)
|
@@ -16,7 +16,7 @@ describe "Usher (for rack) route dispatching with redirect_on_trailing_delimiter
|
|
16
16
|
|
17
17
|
end
|
18
18
|
|
19
|
-
describe "
|
19
|
+
describe "HttpRouter route dispatching" do
|
20
20
|
before(:each) do
|
21
21
|
route_set.reset!
|
22
22
|
@app = MockApp.new("Hello World!")
|
data/spec/rack/generate_spec.rb
CHANGED
data/spec/recognize_spec.rb
CHANGED
@@ -29,6 +29,15 @@ describe "HttpRouter#recognize" do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
context("proc acceptance") do
|
33
|
+
it "should match optionally with a proc" do
|
34
|
+
route = @router.add("/:test").matching(:test => /^\d+/).matching(:test => proc{|env, val| val == '123' or raise}).to(:test)
|
35
|
+
response = @router.recognize(Rack::MockRequest.env_for('/123'))
|
36
|
+
response.route.should == route
|
37
|
+
response.params_as_hash[:test].should == '123'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
32
41
|
context("trailing slashes") do
|
33
42
|
it "should ignore a trailing slash" do
|
34
43
|
route = @router.add("/test").to(:test)
|
@@ -75,11 +84,17 @@ describe "HttpRouter#recognize" do
|
|
75
84
|
|
76
85
|
it "should move an endpoint to the non-specific request method when a more specific route gets added" do
|
77
86
|
@router.add("/test").name(:test_catchall).to(:test1)
|
78
|
-
@router.post("/test").
|
87
|
+
@router.post("/test").name(:test_post).to(:test2)
|
79
88
|
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'POST')).route.named.should == :test_post
|
80
89
|
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'PUT')).route.named.should == :test_catchall
|
81
90
|
end
|
82
91
|
|
92
|
+
it "should try both specific and non-specifc routes" do
|
93
|
+
@router.post("/test").host('host1').to(:post_host1)
|
94
|
+
@router.add("/test").host('host2').to(:any_post2)
|
95
|
+
@router.recognize(Rack::MockRequest.env_for('http://host2/test', :method => 'POST')).dest.should == :any_post2
|
96
|
+
end
|
97
|
+
|
83
98
|
end
|
84
99
|
|
85
100
|
context("dynamic paths") do
|
@@ -161,11 +176,19 @@ describe "HttpRouter#recognize" do
|
|
161
176
|
response.route.should == route
|
162
177
|
response.params_as_hash[:variable].should == 'value'
|
163
178
|
end
|
179
|
+
|
180
|
+
it "should recognize interstitial variables with a regex" do
|
181
|
+
route = @router.add('/one-:variable-time').matching(:variable => /^\d+/).to(:test)
|
182
|
+
@router.recognize(Rack::MockRequest.env_for('/one-value-time')).should be_nil
|
183
|
+
response = @router.recognize(Rack::MockRequest.env_for('/one-123-time'))
|
184
|
+
response.route.should == route
|
185
|
+
response.params_as_hash[:variable].should == '123'
|
186
|
+
end
|
164
187
|
end
|
165
188
|
|
166
189
|
context("dynamic greedy paths") do
|
167
190
|
it "should recognize greedy variables" do
|
168
|
-
route = @router.add('/:variable').matching(:variable
|
191
|
+
route = @router.add('/:variable').matching(:variable => /\d+/).to(:test)
|
169
192
|
response = @router.recognize(Rack::MockRequest.env_for('/123'))
|
170
193
|
response.route.should == route
|
171
194
|
response.params.should == ['123']
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "sinatra"
|
2
|
-
require "http_router/sinatra"
|
2
|
+
require "http_router/interface/sinatra"
|
3
3
|
|
4
|
-
describe "
|
4
|
+
describe "HttpRouter (for Sinatra) route recognition" do
|
5
5
|
before(:each) do
|
6
6
|
@app = Sinatra.new { register HttpRouter::Interface::Sinatra::Extension }
|
7
7
|
@app.extend(CallWithMockRequestMixin)
|