http_router 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/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)
|