1 /**
2 	Pattern based URL router for HTTP request.
3 
4 	See `URLRouter` for more details.
5 
6 	Copyright: © 2012-2015 RejectedSoftware e.K.
7 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
8 	Authors: Sönke Ludwig
9 */
10 module vibe.http.router;
11 
12 public import vibe.http.server;
13 
14 import vibe.core.log;
15 
16 import std.functional;
17 
18 
19 /**
20 	Routes HTTP requests based on the request method and URL.
21 
22 	Routes are matched using a special URL match string that supports two forms
23 	of placeholders. See the sections below for more details.
24 
25 	Registered routes are matched according to the same sequence as initially
26 	specified using `match`, `get`, `post` etc. Matching ends as soon as a route
27 	handler writes a response using `res.writeBody()` or similar means. If no
28 	route matches or if no route handler writes a response, the router will
29 	simply not handle the request and the HTTP server will automatically
30 	generate a 404 error.
31 
32 	Match_patterns:
33 		Match patterns are character sequences that can optionally contain
34 		placeholders or raw wildcards ("*"). Raw wild cards match any character
35 		sequence, while placeholders match only sequences containing no slash
36 		("/") characters.
37 
38 		Placeholders are started using a colon (":") and are directly followed
39 		by their name. The first "/" character (or the end of the match string)
40 		denotes the end of the placeholder name. The part of the string that
41 		matches a placeholder will be stored in the `HTTPServerRequest.params`
42 		map using the placeholder name as the key.
43 
44 		Match strings are subject to the following rules:
45 		$(UL
46 			$(LI A raw wildcard ("*") may only occur at the end of the match string)
47 			$(LI At least one character must be placed between any two placeholders or wildcards)
48 			$(LI The maximum allowed number of placeholders in a single match string is 64)
49 		)
50 
51 	Match_String_Examples:
52 		$(UL
53 			$(LI `"/foo/bar"` matches only `"/foo/bar"` itself)
54 			$(LI `"/foo/*"` matches `"/foo/"`, `"/foo/bar"`, `"/foo/bar/baz"` or _any other string beginning with `"/foo/"`)
55 			$(LI `"/:x/"` matches `"/foo/"`, `"/bar/"` and similar strings (and stores `"foo"`/`"bar"` in `req.params["x"]`), but not `"/foo/bar/"`)
56 			$(LI Matching partial path entries with wildcards is possible: `"/foo:x"` matches `"/foo"`, `"/foobar"`, but not `"/foo/bar"`)
57 			$(LI Multiple placeholders and raw wildcards can be combined: `"/:x/:y/*"`)
58 		)
59 */
60 final class URLRouter : HTTPServerRequestHandler {
61 	@safe:
62 
63 	private {
64 		MatchTree!Route m_routes;
65 		string m_prefix;
66 		bool m_computeBasePath;
67 	}
68 
69 	this(string prefix = null)
70 	{
71 		m_prefix = prefix;
72 	}
73 
74 	/** Sets a common prefix for all registered routes.
75 
76 		All routes will implicitly have this prefix prepended before being
77 		matched against incoming requests.
78 	*/
79 	@property string prefix() const { return m_prefix; }
80 
81 	/** Controls the computation of the "routerRootDir" parameter.
82 
83 		This parameter is available as `req.params["routerRootDir"]` and
84 		contains the relative path to the base path of the router. The base
85 		path is determined by the `prefix` property.
86 
87 		Note that this feature currently is requires dynamic memory allocations
88 		and is opt-in for this reason.
89 	*/
90 	@property void enableRootDir(bool enable) { m_computeBasePath = enable; }
91 
92 	/// Returns a single route handle to conveniently register multiple methods.
93 	URLRoute route(string path)
94 	in { assert(path.length, "Cannot register null or empty path!"); }
95 	body { return URLRoute(this, path); }
96 
97 	///
98 	unittest {
99 		void getFoo(scope HTTPServerRequest req, scope HTTPServerResponse res) { /* ... */ }
100 		void postFoo(scope HTTPServerRequest req, scope HTTPServerResponse res) { /* ... */ }
101 		void deleteFoo(scope HTTPServerRequest req, scope HTTPServerResponse res) { /* ... */ }
102 
103 		auto r = new URLRouter;
104 
105 		// using 'with' statement
106 		with (r.route("/foo")) {
107 			get(&getFoo);
108 			post(&postFoo);
109 			delete_(&deleteFoo);
110 		}
111 
112 		// using method chaining
113 		r.route("/foo")
114 			.get(&getFoo)
115 			.post(&postFoo)
116 			.delete_(&deleteFoo);
117 
118 		// without using route()
119 		r.get("/foo", &getFoo);
120 		r.post("/foo", &postFoo);
121 		r.delete_("/foo", &deleteFoo);
122 	}
123 
124 	/// Adds a new route for requests matching the specified HTTP method and pattern.
125 	URLRouter match(Handler)(HTTPMethod method, string path, Handler handler)
126 		if (isValidHandler!Handler)
127 	{
128 		import std.algorithm;
129 		assert(path.length, "Cannot register null or empty path!");
130 		assert(count(path, ':') <= maxRouteParameters, "Too many route parameters");
131 		logDebug("add route %s %s", method, path);
132 		m_routes.addTerminal(path, Route(method, path, handlerDelegate(handler)));
133 		return this;
134 	}
135 
136 	/// ditto
137 	URLRouter match(HTTPMethod method, string path, HTTPServerRequestDelegate handler)
138 	{
139 		return match!HTTPServerRequestDelegate(method, path, handler);
140 	}
141 
142 	/// Adds a new route for GET requests matching the specified pattern.
143 	URLRouter get(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.GET, url_match, handler); }
144 	/// ditto
145 	URLRouter get(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.GET, url_match, handler); }
146 
147 	/// Adds a new route for POST requests matching the specified pattern.
148 	URLRouter post(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.POST, url_match, handler); }
149 	/// ditto
150 	URLRouter post(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.POST, url_match, handler); }
151 
152 	/// Adds a new route for PUT requests matching the specified pattern.
153 	URLRouter put(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.PUT, url_match, handler); }
154 	/// ditto
155 	URLRouter put(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.PUT, url_match, handler); }
156 
157 	/// Adds a new route for DELETE requests matching the specified pattern.
158 	URLRouter delete_(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.DELETE, url_match, handler); }
159 	/// ditto
160 	URLRouter delete_(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.DELETE, url_match, handler); }
161 
162 	/// Adds a new route for PATCH requests matching the specified pattern.
163 	URLRouter patch(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.PATCH, url_match, handler); }
164 	/// ditto
165 	URLRouter patch(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.PATCH, url_match, handler); }
166 
167 	/// Adds a new route for requests matching the specified pattern, regardless of their HTTP verb.
168 	URLRouter any(Handler)(string url_match, Handler handler)
169 	{
170 		import std.traits;
171 		static HTTPMethod[] all_methods = [EnumMembers!HTTPMethod];
172 		foreach(immutable method; all_methods)
173 			match(method, url_match, handler);
174 
175 		return this;
176 	}
177 	/// ditto
178 	URLRouter any(string url_match, HTTPServerRequestDelegate handler) { return any!HTTPServerRequestDelegate(url_match, handler); }
179 
180 
181 	/** Rebuilds the internal matching structures to account for newly added routes.
182 
183 		This should be used after a lot of routes have been added to the router, to
184 		force eager computation of the match structures. The alternative is to
185 		let the router lazily compute the structures when the first request happens,
186 		which can delay this request.
187 	*/
188 	void rebuild()
189 	{
190 		m_routes.rebuildGraph();
191 	}
192 
193 	/// Handles a HTTP request by dispatching it to the registered route handlers.
194 	void handleRequest(HTTPServerRequest req, HTTPServerResponse res)
195 	{
196 		auto method = req.method;
197 
198 		string calcBasePath()
199 		@safe {
200 			import vibe.core.path : InetPath, relativeToWeb;
201 			auto p = InetPath(prefix.length ? prefix : "/");
202 			p.endsWithSlash = true;
203 			return p.relativeToWeb(InetPath(req.path)).toString();
204 		}
205 
206 		auto path = req.path;
207 		if (path.length < m_prefix.length || path[0 .. m_prefix.length] != m_prefix) return;
208 		path = path[m_prefix.length .. $];
209 
210 		while (true) {
211 			bool done = m_routes.match(path, (ridx, scope values) @safe {
212 				auto r = () @trusted { return &m_routes.getTerminalData(ridx); } ();
213 				if (r.method != method) return false;
214 
215 				logDebugV("route match: %s -> %s %s %s", req.path, r.method, r.pattern, values);
216 				foreach (i, v; values) req.params[m_routes.getTerminalVarNames(ridx)[i]] = v;
217 				if (m_computeBasePath) req.params["routerRootDir"] = calcBasePath();
218 				r.cb(req, res);
219 				return res.headerWritten;
220 			});
221 			if (done) return;
222 
223 			if (method == HTTPMethod.HEAD) method = HTTPMethod.GET;
224 			else break;
225 		}
226 
227 		logDebug("no route match: %s %s", req.method, req.requestURL);
228 	}
229 
230 	/// Returns all registered routes as const AA
231 	const(Route)[] getAllRoutes()
232 	{
233 		auto routes = new Route[m_routes.terminalCount];
234 		foreach (i, ref r; routes)
235 			r = m_routes.getTerminalData(i);
236 		return routes;
237 	}
238 
239 	template isValidHandler(Handler) {
240 		@system {
241 			alias USDel = void delegate(HTTPServerRequest, HTTPServerResponse) @system;
242 			alias USFun = void function(HTTPServerRequest, HTTPServerResponse) @system;
243 			alias USDelS = void delegate(scope HTTPServerRequest, scope HTTPServerResponse) @system;
244 			alias USFunS = void function(scope HTTPServerRequest, scope HTTPServerResponse) @system;
245 		}
246 
247 		static if (
248 				is(Handler : HTTPServerRequestDelegate) ||
249 				is(Handler : HTTPServerRequestFunction) ||
250 				is(Handler : HTTPServerRequestHandler)
251 				//is(Handler : HTTPServerRequestDelegateS) ||
252 				//is(Handler : HTTPServerRequestFunctionS) ||
253 				//is(Handler : HTTPServerRequestHandlerS)
254 			)
255 		{
256 			enum isValidHandler = true;
257 		} else static if (
258 				is(Handler : USDel) || is(Handler : USFun) ||
259 				is(Handler : USDelS) || is(Handler : USFunS)
260 			)
261 		{
262 			enum isValidHandler = true;
263 		} else {
264 			enum isValidHandler = false;
265 		}
266 	}
267 
268 	static void delegate(HTTPServerRequest, HTTPServerResponse) @safe handlerDelegate(Handler)(Handler handler)
269 	{
270 		import std.traits : isFunctionPointer;
271 		static if (isFunctionPointer!Handler) return handlerDelegate(() @trusted { return toDelegate(handler); } ());
272 		else static if (is(Handler == class) || is(Handler == interface)) return &handler.handleRequest;
273 		else static if (__traits(compiles, () @safe { handler(HTTPServerRequest.init, HTTPServerResponse.init); } ())) return handler;
274 		else return (req, res) @trusted { handler(req, res); };
275 	}
276 
277 	unittest {
278 		static assert(isValidHandler!HTTPServerRequestFunction);
279 		static assert(isValidHandler!HTTPServerRequestDelegate);
280 		static assert(isValidHandler!HTTPServerRequestHandler);
281 		//static assert(isValidHandler!HTTPServerRequestFunctionS);
282 		//static assert(isValidHandler!HTTPServerRequestDelegateS);
283 		//static assert(isValidHandler!HTTPServerRequestHandlerS);
284 		static assert(isValidHandler!(void delegate(HTTPServerRequest req, HTTPServerResponse res) @system));
285 		static assert(isValidHandler!(void function(HTTPServerRequest req, HTTPServerResponse res) @system));
286 		static assert(isValidHandler!(void delegate(scope HTTPServerRequest req, scope HTTPServerResponse res) @system));
287 		static assert(isValidHandler!(void function(scope HTTPServerRequest req, scope HTTPServerResponse res) @system));
288 		static assert(!isValidHandler!(int delegate(HTTPServerRequest req, HTTPServerResponse res) @system));
289 		static assert(!isValidHandler!(int delegate(HTTPServerRequest req, HTTPServerResponse res) @safe));
290 		void test(H)(H h)
291 		{
292 			static assert(isValidHandler!H);
293 		}
294 		test((HTTPServerRequest req, HTTPServerResponse res) {});
295 	}
296 }
297 
298 ///
299 //@safe unittest {
300 	//import vibe.http.fileserver;
301 
302 	//void addGroup(HTTPServerRequest req, HTTPServerResponse res)
303 	//{
304 		//// Route variables are accessible via the params map
305 		//logInfo("Getting group %s for user %s.", req.params["groupname"], req.params["username"]);
306 	//}
307 
308 	//void deleteUser(HTTPServerRequest req, HTTPServerResponse res)
309 	//{
310 		//// ...
311 	//}
312 
313 	//void auth(HTTPServerRequest req, HTTPServerResponse res)
314 	//{
315 		//// TODO: check req.session to see if a user is logged in and
316 		////	   write an error page or throw an exception instead.
317 	//}
318 
319 	//void setup()
320 	//{
321 		//auto router = new URLRouter;
322 		//// Matches all GET requests for /users/groups/* and places
323 		//// the place holders in req.params as 'username' and 'groupname'.
324 		//router.get("/users/:username/groups/:groupname", &addGroup);
325 
326 		//// Matches all requests. This can be useful for authorization and
327 		//// similar tasks. The auth method will only write a response if the
328 		//// user is _not_ authorized. Otherwise, the router will fall through
329 		//// and continue with the following routes.
330 		//router.any("*", &auth);
331 
332 		//// Matches a POST request
333 		//router.post("/users/:username/delete", &deleteUser);
334 
335 		//// Matches all GET requests in /static/ such as /static/img.png or
336 		//// /static/styles/sty.css
337 		//router.get("/static/*", serveStaticFiles("public/"));
338 
339 		//// Setup a HTTP server...
340 		//auto settings = new HTTPServerSettings;
341 		//// ...
342 
343 		//// The router can be directly passed to the listenHTTP function as
344 		//// the main request handler.
345 		//listenHTTP(settings, router);
346 	//}
347 //}
348 
349 /** Using nested routers to map components to different sub paths. A component
350 	could for example be an embedded blog engine.
351 */
352 @safe unittest {
353 	// some embedded component:
354 
355 	void showComponentHome(HTTPServerRequest req, HTTPServerResponse res)
356 	{
357 		// ...
358 	}
359 
360 	void showComponentUser(HTTPServerRequest req, HTTPServerResponse res)
361 	{
362 		// ...
363 	}
364 
365 	void registerComponent(URLRouter router)
366 	{
367 		router.get("/", &showComponentHome);
368 		router.get("/users/:user", &showComponentUser);
369 	}
370 
371 	// main application:
372 
373 	void showHome(HTTPServerRequest req, HTTPServerResponse res)
374 	{
375 		// ...
376 	}
377 
378 	void setup()
379 	{
380 		auto c1router = new URLRouter("/component1");
381 		registerComponent(c1router);
382 
383 		auto mainrouter = new URLRouter;
384 		mainrouter.get("/", &showHome);
385 		// forward all unprocessed requests to the component router
386 		mainrouter.any("*", c1router);
387 
388 		// now the following routes will be matched:
389 		// / -> showHome
390 		// /component1/ -> showComponentHome
391 		// /component1/users/:user -> showComponentUser
392 
393 		// Start the HTTP server
394 		//auto settings = HTTPServerSettings;
395 		HTTPServerSettings settings;
396 		// ...
397 		//listenHTTP(settings, mainrouter);
398 
399 		listenHTTP!mainrouter(settings);
400 	}
401 }
402 
403 @safe unittest { // issue #1668
404 	auto r = new URLRouter;
405 	r.get("/", (req, res) {
406 		if ("foo" in req.headers)
407 			res.writeBody("bar");
408 	});
409 
410 	r.get("/", (scope req, scope res) {
411 		if ("foo" in req.headers)
412 			res.writeBody("bar");
413 	});
414 	r.get("/", (req, res) {});
415 	r.post("/", (req, res) {});
416 	r.put("/", (req, res) {});
417 	r.delete_("/", (req, res) {});
418 	r.patch("/", (req, res) {});
419 	r.any("/", (req, res) {});
420 }
421 
422 @safe unittest { // issue #1866
423 	auto r = new URLRouter;
424 	r.match(HTTPMethod.HEAD, "/foo", (scope req, scope res) { res.writeVoidBody; });
425 	r.match(HTTPMethod.HEAD, "/foo", (req, res) { res.writeVoidBody; });
426 	r.match(HTTPMethod.HEAD, "/foo", (scope HTTPServerRequest req, scope HTTPServerResponse res) { res.writeVoidBody; });
427 	r.match(HTTPMethod.HEAD, "/foo", (HTTPServerRequest req, HTTPServerResponse res) { res.writeVoidBody; });
428 
429 	auto r2 = new URLRouter;
430 	r.match(HTTPMethod.HEAD, "/foo", r2);
431 }
432 
433 @safe unittest {
434 	import vibe.inet.url;
435 
436 	auto router = new URLRouter;
437 	string result;
438 	void a(HTTPServerRequest req, HTTPServerResponse) { result ~= "A"; }
439 	void b(HTTPServerRequest req, HTTPServerResponse) { result ~= "B"; }
440 	void c(HTTPServerRequest req, HTTPServerResponse) { assert(req.params["test"] == "x", "Wrong variable contents: "~req.params["test"]); result ~= "C"; }
441 	void d(HTTPServerRequest req, HTTPServerResponse) { assert(req.params["test"] == "y", "Wrong variable contents: "~req.params["test"]); result ~= "D"; }
442 	router.get("/test", &a);
443 	router.post("/test", &b);
444 	router.get("/a/:test", &c);
445 	router.get("/a/:test/", &d);
446 
447 	auto res = createTestHTTPServerResponse();
448 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/")), res);
449 	assert(result == "", "Matched for non-existent / path");
450 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test")), res);
451 	assert(result == "A", "Didn't match a simple GET request");
452 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test"), HTTPMethod.POST), res);
453 	assert(result == "AB", "Didn't match a simple POST request");
454 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a/"), HTTPMethod.GET), res);
455 	assert(result == "AB", "Matched empty variable. "~result);
456 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a/x"), HTTPMethod.GET), res);
457 	assert(result == "ABC", "Didn't match a trailing 1-character var.");
458 	// currently fails due to Path not accepting "//"
459 	//router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a//"), HTTPMethod.GET), res);
460 	//assert(result == "ABC", "Matched empty string or slash as var. "~result);
461 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a/y/"), HTTPMethod.GET), res);
462 	assert(result == "ABCD", "Didn't match 1-character infix variable.");
463 }
464 
465 @safe unittest {
466 	import vibe.inet.url;
467 
468 	auto router = new URLRouter("/test");
469 
470 	string result;
471 	void a(HTTPServerRequest req, HTTPServerResponse) { result ~= "A"; }
472 	void b(HTTPServerRequest req, HTTPServerResponse) { result ~= "B"; }
473 	router.get("/x", &a);
474 	router.get("/y", &b);
475 
476 	auto res = createTestHTTPServerResponse();
477 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test")), res);
478 	assert(result == "");
479 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test/x")), res);
480 	assert(result == "A");
481 	router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test/y")), res);
482 	assert(result == "AB");
483 }
484 
485 @safe unittest {
486 	string ensureMatch(string pattern, string local_uri, string[string] expected_params = null)
487 	{
488 		import vibe.inet.url : URL;
489 		string ret = local_uri ~ " did not match " ~ pattern;
490 		auto r = new URLRouter;
491 		r.get(pattern, (req, res) {
492 			ret = null;
493 			foreach (k, v; expected_params) {
494 				if (k !in req.params) { ret = "Parameter "~k~" was not matched."; return; }
495 				if (req.params[k] != v) { ret = "Parameter "~k~" is '"~req.params[k]~"' instead of '"~v~"'."; return; }
496 			}
497 		});
498 		auto req = createTestHTTPServerRequest(URL("http://localhost"~local_uri));
499 		auto res = createTestHTTPServerResponse();
500 		r.handleRequest(req, res);
501 		return ret;
502 	}
503 
504 	assert(ensureMatch("/foo bar/", "/foo%20bar/") is null);   // normalized pattern: "/foo%20bar/"
505 	//assert(ensureMatch("/foo%20bar/", "/foo%20bar/") is null); // normalized pattern: "/foo%20bar/"
506 	assert(ensureMatch("/foo/bar/", "/foo/bar/") is null);	 // normalized pattern: "/foo/bar/"
507 	//assert(ensureMatch("/foo/bar/", "/foo%2fbar/") !is null);
508 	//assert(ensureMatch("/foo%2fbar/", "/foo%2fbar/") is null); // normalized pattern: "/foo%2Fbar/"
509 	//assert(ensureMatch("/foo%2Fbar/", "/foo%2fbar/") is null); // normalized pattern: "/foo%2Fbar/"
510 	//assert(ensureMatch("/foo%2fbar/", "/foo%2Fbar/") is null);
511 	//assert(ensureMatch("/foo%2fbar/", "/foo/bar/") !is null);
512 	//assert(ensureMatch("/:foo/", "/foo%2Fbar/", ["foo": "foo/bar"]) is null);
513 	assert(ensureMatch("/:foo/", "/foo/bar/") !is null);
514 }
515 
516 
517 /**
518 	Convenience abstraction for a single `URLRouter` route.
519 
520 	See `URLRouter.route` for a usage example.
521 */
522 struct URLRoute {
523 @safe:
524 
525 	URLRouter router;
526 	string path;
527 
528 	ref URLRoute get(Handler)(Handler h) { router.get(path, h); return this; }
529 	ref URLRoute post(Handler)(Handler h) { router.post(path, h); return this; }
530 	ref URLRoute put(Handler)(Handler h) { router.put(path, h); return this; }
531 	ref URLRoute delete_(Handler)(Handler h) { router.delete_(path, h); return this; }
532 	ref URLRoute patch(Handler)(Handler h) { router.patch(path, h); return this; }
533 	ref URLRoute any(Handler)(Handler h) { router.any(path, h); return this; }
534 	ref URLRoute match(Handler)(HTTPMethod method, Handler h) { router.match(method, path, h); return this; }
535 }
536 
537 
538 private enum maxRouteParameters = 64;
539 
540 private struct Route {
541 	HTTPMethod method;
542 	string pattern;
543 	HTTPServerRequestDelegate cb;
544 }
545 
546 private string skipPathNode(string str, ref size_t idx)
547 @safe {
548 	size_t start = idx;
549 	while( idx < str.length && str[idx] != '/' ) idx++;
550 	return str[start .. idx];
551 }
552 
553 private string skipPathNode(ref string str)
554 @safe {
555 	size_t idx = 0;
556 	auto ret = skipPathNode(str, idx);
557 	str = str[idx .. $];
558 	return ret;
559 }
560 
561 private struct MatchTree(T) {
562 @safe:
563 
564 	import std.algorithm : countUntil;
565 	import std.array : array;
566 
567 	private {
568 		alias NodeIndex = uint;
569 		alias TerminalTagIndex = uint;
570 		alias TerminalIndex = ushort;
571 		alias VarIndex = ushort;
572 
573 		struct Node {
574 			TerminalTagIndex terminalsStart; // slice into m_terminalTags
575 			TerminalTagIndex terminalsEnd;
576 			NodeIndex[ubyte.max+1] edges = NodeIndex.max; // character -> index into m_nodes
577 		}
578 		struct TerminalTag {
579 			TerminalIndex index; // index into m_terminals array
580 			VarIndex var = VarIndex.max; // index into Terminal.varNames/varValues or VarIndex.max
581 		}
582 		struct Terminal {
583 			string pattern;
584 			T data;
585 			string[] varNames;
586 			VarIndex[NodeIndex] varMap;
587 		}
588 		Node[] m_nodes; // all nodes as a single array
589 		TerminalTag[] m_terminalTags;
590 		Terminal[] m_terminals;
591 
592 		enum TerminalChar = 0;
593 		bool m_upToDate = false;
594 	}
595 
596 	@property size_t terminalCount() const { return m_terminals.length; }
597 
598 	void addTerminal(string pattern, T data)
599 	{
600 		assert(m_terminals.length < TerminalIndex.max, "Attempt to register too many routes.");
601 		m_terminals ~= Terminal(pattern, data, null, null);
602 		m_upToDate = false;
603 	}
604 
605 	bool match(string text, scope bool delegate(size_t terminal, scope string[] vars) @safe del)
606 	{
607 		// lazily update the match graph
608 		if (!m_upToDate) rebuildGraph();
609 
610 		return doMatch(text, del);
611 	}
612 
613 	const(string)[] getTerminalVarNames(size_t terminal) const { return m_terminals[terminal].varNames; }
614 	ref inout(T) getTerminalData(size_t terminal) inout { return m_terminals[terminal].data; }
615 
616 	void print()
617 	const {
618 		import std.algorithm : map;
619 		import std.array : join;
620 		import std.conv : to;
621 		import std.range : iota;
622 		import std..string : format;
623 
624 		logInfo("Nodes:");
625 		foreach (i, n; m_nodes) {
626 			logInfo("  %s %s", i, m_terminalTags[n.terminalsStart .. n.terminalsEnd]
627 				.map!(t => format("T%s%s", t.index, t.var != VarIndex.max ? "("~m_terminals[t.index].varNames[t.var]~")" : "")).join(" "));
628 			//logInfo("  %s %s-%s", i, n.terminalsStart, n.terminalsEnd);
629 
630 
631 			static string mapChar(ubyte ch) {
632 				if (ch == TerminalChar) return "$";
633 				if (ch >= '0' && ch <= '9') return to!string(cast(dchar)ch);
634 				if (ch >= 'a' && ch <= 'z') return to!string(cast(dchar)ch);
635 				if (ch >= 'A' && ch <= 'Z') return to!string(cast(dchar)ch);
636 				if (ch == '/') return "/";
637 				if (ch == '^') return "^";
638 				return ch.to!string;
639 			}
640 
641 			void printRange(uint node, ubyte from, ubyte to)
642 			{
643 				if (to - from <= 10) logInfo("	%s -> %s", iota(from, cast(uint)to+1).map!(ch => mapChar(cast(ubyte)ch)).join("|"), node);
644 				else logInfo("	%s-%s -> %s", mapChar(from), mapChar(to), node);
645 			}
646 
647 			auto last_to = NodeIndex.max;
648 			ubyte last_ch = 0;
649 			foreach (ch, e; n.edges)
650 				if (e != last_to) {
651 					if (last_to != NodeIndex.max)
652 						printRange(last_to, last_ch, cast(ubyte)(ch-1));
653 					last_ch = cast(ubyte)ch;
654 					last_to = e;
655 				}
656 			if (last_to != NodeIndex.max)
657 				printRange(last_to, last_ch, ubyte.max);
658 		}
659 	}
660 
661 	private bool doMatch(string text, scope bool delegate(size_t terminal, scope string[] vars) @safe del)
662 	const {
663 		string[maxRouteParameters] vars_buf;// = void;
664 
665 		import std.algorithm : canFind;
666 
667 		// first, determine the end node, if any
668 		auto n = matchTerminals(text);
669 		if (!n) return false;
670 
671 		// then, go through the terminals and match their variables
672 		foreach (ref t; m_terminalTags[n.terminalsStart .. n.terminalsEnd]) {
673 			auto term = &m_terminals[t.index];
674 			auto vars = vars_buf[0 .. term.varNames.length];
675 			matchVars(vars, term, text);
676 			if (vars.canFind!(v => v.length == 0)) continue; // all variables must be non-empty to match
677 			if (del(t.index, vars)) return true;
678 		}
679 		return false;
680 	}
681 
682 	private inout(Node)* matchTerminals(string text)
683 	inout {
684 		if (!m_nodes.length) return null;
685 
686 		auto n = &m_nodes[0];
687 
688 		// follow the path through the match graph
689 		foreach (i, char ch; text) {
690 			auto nidx = n.edges[cast(size_t)ch];
691 			if (nidx == NodeIndex.max) return null;
692 			n = &m_nodes[nidx];
693 		}
694 
695 		// finally, find a matching terminal node
696 		auto nidx = n.edges[TerminalChar];
697 		if (nidx == NodeIndex.max) return null;
698 		n = &m_nodes[nidx];
699 		return n;
700 	}
701 
702 	private void matchVars(string[] dst, in Terminal* term, string text)
703 	const {
704 		NodeIndex nidx = 0;
705 		VarIndex activevar = VarIndex.max;
706 		size_t activevarstart;
707 
708 		dst[] = null;
709 
710 		// folow the path throgh the match graph
711 		foreach (i, char ch; text) {
712 			auto var = term.varMap.get(nidx, VarIndex.max);
713 
714 			// detect end of variable
715 			if (var != activevar && activevar != VarIndex.max) {
716 				dst[activevar] = text[activevarstart .. i-1];
717 				activevar = VarIndex.max;
718 			}
719 
720 			// detect beginning of variable
721 			if (var != VarIndex.max && activevar == VarIndex.max) {
722 				activevar = var;
723 				activevarstart = i;
724 			}
725 
726 			nidx = m_nodes[nidx].edges[cast(ubyte)ch];
727 			assert(nidx != NodeIndex.max);
728 		}
729 
730 		// terminate any active varible with the end of the input string or with the last character
731 		auto var = term.varMap.get(nidx, VarIndex.max);
732 		if (activevar != VarIndex.max) dst[activevar] = text[activevarstart .. (var == activevar ? $ : $-1)];
733 	}
734 
735 	private void rebuildGraph()
736 	@trusted {
737 		import std.array : appender;
738 		import std.conv : to;
739 
740 		if (m_upToDate) return;
741 		m_upToDate = true;
742 
743 		m_nodes = null;
744 		m_terminalTags = null;
745 
746 		if (!m_terminals.length) return;
747 
748 		MatchGraphBuilder builder;
749 		foreach (i, ref t; m_terminals) {
750 			t.varNames = builder.insert(t.pattern, i.to!TerminalIndex);
751 			assert(t.varNames.length <= VarIndex.max, "Too many variables in route.");
752 		}
753 		//builder.print();
754 		builder.disambiguate();
755 
756 		auto nodemap = new NodeIndex[builder.m_nodes.length];
757 		nodemap[] = NodeIndex.max;
758 
759 		auto nodes = appender!(Node[]);
760 		nodes.reserve(1024);
761 		auto termtags = appender!(TerminalTag[]);
762 		termtags.reserve(1024);
763 
764 		NodeIndex process(NodeIndex n)
765 		{
766 			import std.algorithm : canFind;
767 
768 			if (nodemap[n] != NodeIndex.max) return nodemap[n];
769 			auto nmidx = cast(NodeIndex)nodes.data.length;
770 			nodemap[n] = nmidx;
771 			nodes.put(Node.init);
772 
773 			Node nn;
774 			nn.terminalsStart = termtags.data.length.to!TerminalTagIndex;
775 			foreach (t; builder.m_nodes[n].terminals) {
776 				auto var = cast(VarIndex)t.var;
777 				assert(!termtags.data[nn.terminalsStart .. $].canFind!(u => u.index == t.index && u.var == var));
778 				termtags ~= TerminalTag(cast(TerminalIndex)t.index, var);
779 				if (var != VarIndex.max)
780 					m_terminals[t.index].varMap[nmidx] = var;
781 			}
782 			nn.terminalsEnd = termtags.data.length.to!TerminalTagIndex;
783 			foreach (ch, targets; builder.m_nodes[n].edges)
784 				foreach (to; builder.m_edgeEntries.getItems(targets))
785 					nn.edges[ch] = process(to);
786 
787 			nodes.data[nmidx] = nn;
788 
789 			return nmidx;
790 		}
791 		assert(builder.m_edgeEntries.hasLength(builder.m_nodes[0].edges['^'], 1),
792 			"Graph must be disambiguated before purging.");
793 		process(builder.m_edgeEntries.getItems(builder.m_nodes[0].edges['^']).front);
794 
795 		m_nodes = nodes.data;
796 		m_terminalTags = termtags.data;
797 
798 		logDebug("Match tree has %s (of %s in the builder) nodes, %s terminals", m_nodes.length, builder.m_nodes.length, m_terminals.length);
799 	}
800 }
801 
802 unittest {
803 	import std..string : format;
804 	MatchTree!int m;
805 
806 	void testMatch(string str, size_t[] terms, string[] vars)
807 	{
808 		size_t[] mterms;
809 		string[] mvars;
810 		m.match(str, (t, scope vals) {
811 			mterms ~= t;
812 			mvars ~= vals;
813 			return false;
814 		});
815 		assert(mterms == terms, format("Mismatched terminals: %s (expected %s)", mterms, terms));
816 		assert(mvars == vars, format("Mismatched variables; %s (expected %s)", mvars, vars));
817 	}
818 
819 	m.addTerminal("a", 0);
820 	m.addTerminal("b", 0);
821 	m.addTerminal("ab", 0);
822 	m.rebuildGraph();
823 	assert(m.getTerminalVarNames(0) == []);
824 	assert(m.getTerminalVarNames(1) == []);
825 	assert(m.getTerminalVarNames(2) == []);
826 	testMatch("a", [0], []);
827 	testMatch("ab", [2], []);
828 	testMatch("abc", [], []);
829 	testMatch("b", [1], []);
830 
831 	m = MatchTree!int.init;
832 	m.addTerminal("ab", 0);
833 	m.addTerminal("a*", 0);
834 	m.rebuildGraph();
835 	assert(m.getTerminalVarNames(0) == []);
836 	assert(m.getTerminalVarNames(1) == []);
837 	testMatch("a", [1], []);
838 	testMatch("ab", [0, 1], []);
839 	testMatch("abc", [1], []);
840 
841 	m = MatchTree!int.init;
842 	m.addTerminal("ab", 0);
843 	m.addTerminal("a:var", 0);
844 	m.rebuildGraph();
845 	assert(m.getTerminalVarNames(0) == []);
846 	assert(m.getTerminalVarNames(1) == ["var"], format("%s", m.getTerminalVarNames(1)));
847 	testMatch("a", [], []); // vars may not be empty
848 	testMatch("ab", [0, 1], ["b"]);
849 	testMatch("abc", [1], ["bc"]);
850 
851 	m = MatchTree!int.init;
852 	m.addTerminal(":var1/:var2", 0);
853 	m.addTerminal("a/:var3", 0);
854 	m.addTerminal(":var4/b", 0);
855 	m.rebuildGraph();
856 	assert(m.getTerminalVarNames(0) == ["var1", "var2"]);
857 	assert(m.getTerminalVarNames(1) == ["var3"]);
858 	assert(m.getTerminalVarNames(2) == ["var4"]);
859 	testMatch("a", [], []);
860 	testMatch("a/a", [0, 1], ["a", "a", "a"]);
861 	testMatch("a/b", [0, 1, 2], ["a", "b", "b", "a"]);
862 	testMatch("a/bc", [0, 1], ["a", "bc", "bc"]);
863 	testMatch("ab/b", [0, 2], ["ab", "b", "ab"]);
864 	testMatch("ab/bc", [0], ["ab", "bc"]);
865 
866 	m = MatchTree!int.init;
867 	m.addTerminal(":var1/", 0);
868 	m.rebuildGraph();
869 	assert(m.getTerminalVarNames(0) == ["var1"]);
870 	testMatch("ab/", [0], ["ab"]);
871 	testMatch("ab", [], []);
872 	testMatch("/ab", [], []);
873 	testMatch("a/b", [], []);
874 	testMatch("ab//", [], []);
875 }
876 
877 
878 private struct MatchGraphBuilder {
879 @safe:
880 	import std.container.array : Array;
881 	import std.array : array;
882 	import std.algorithm : filter;
883 	import std..string : format;
884 
885 	alias NodeIndex = uint;
886 	alias TerminalIndex = ushort;
887 	alias VarIndex = ushort;
888 	alias NodeSet = LinkedSetBacking!NodeIndex.Handle;
889 
890 	private {
891 		enum TerminalChar = 0;
892 		struct TerminalTag {
893 			TerminalIndex index;
894 			VarIndex var = VarIndex.max;
895 		}
896 		struct Node {
897 			Array!TerminalTag terminals;
898 			NodeSet[ubyte.max+1] edges;
899 		}
900 		Array!Node m_nodes;
901 		LinkedSetBacking!NodeIndex m_edgeEntries;
902 	}
903 
904 	@disable this(this);
905 
906 	string[] insert(string pattern, TerminalIndex terminal)
907 	{
908 		import std.algorithm : canFind;
909 
910 		auto full_pattern = pattern;
911 		string[] vars;
912 		if (!m_nodes.length) addNode();
913 
914 		// create start node and connect to zero node
915 		auto nidx = addNode();
916 		addEdge(0, nidx, '^', terminal);
917 
918 		while (pattern.length) {
919 			auto ch = pattern[0];
920 			if (ch == '*') {
921 				assert(pattern.length == 1, "Asterisk is only allowed at the end of a pattern: "~full_pattern);
922 				pattern = null;
923 
924 				foreach (v; ubyte.min .. ubyte.max+1) {
925 					if (v == TerminalChar) continue;
926 					addEdge(nidx, nidx, cast(ubyte)v, terminal);
927 				}
928 			} else if (ch == ':') {
929 				pattern = pattern[1 .. $];
930 				auto name = skipPathNode(pattern);
931 				assert(name.length > 0, "Missing placeholder name: "~full_pattern);
932 				assert(!vars.canFind(name), "Duplicate placeholder name ':"~name~"': '"~full_pattern~"'");
933 				auto varidx = cast(VarIndex)vars.length;
934 				vars ~= name;
935 				assert(!pattern.length || (pattern[0] != '*' && pattern[0] != ':'),
936 					"Cannot have two placeholders directly follow each other.");
937 
938 				foreach (v; ubyte.min .. ubyte.max+1) {
939 					if (v == TerminalChar || v == '/') continue;
940 					addEdge(nidx, nidx, cast(ubyte)v, terminal, varidx);
941 				}
942 			} else {
943 				nidx = addEdge(nidx, ch, terminal);
944 				pattern = pattern[1 .. $];
945 			}
946 		}
947 
948 		addEdge(nidx, TerminalChar, terminal);
949 		return vars;
950 	}
951 
952 	void disambiguate()
953 	@trusted {
954 		import std.algorithm : map, sum;
955 		import std.array : appender, join;
956 
957 		//logInfo("Disambiguate with %s initial nodes", m_nodes.length);
958 		if (!m_nodes.length) return;
959 
960 		import vibe.utils.hashmap;
961 		HashMap!(size_t, NodeIndex) combined_nodes;
962 		Array!bool visited;
963 		visited.length = m_nodes.length * 2;
964 		Stack!NodeIndex node_stack;
965 		node_stack.reserve(m_nodes.length);
966 		node_stack.push(0);
967 		while (!node_stack.empty) {
968 			auto n = node_stack.pop();
969 
970 			while (n >= visited.length) visited.length = visited.length * 2;
971 			if (visited[n]) continue;
972 			//logInfo("Disambiguate %s (stack=%s)", n, node_stack.fill);
973 			visited[n] = true;
974 
975 			foreach (ch; ubyte.min .. ubyte.max+1) {
976 				auto chnodes = m_nodes[n].edges[ch];
977 				size_t chhash = m_edgeEntries.getHash(chnodes);
978 
979 				// handle trivial cases
980 				if (m_edgeEntries.hasMaxLength(chnodes, 1))
981 					continue;
982 
983 				// generate combined state for ambiguous edges
984 				if (auto pn = () @trusted { return chhash in combined_nodes; } ()) {
985 					m_nodes[n].edges[ch] = m_edgeEntries.create(*pn);
986 					assert(m_edgeEntries.hasLength(m_nodes[n].edges[ch], 1));
987 					continue;
988 				}
989 
990 				// for new combinations, create a new node
991 				NodeIndex ncomb = addNode();
992 				combined_nodes[chhash] = ncomb;
993 
994 				// write all edges
995 				size_t idx = 0;
996 				foreach (to_ch; ubyte.min .. ubyte.max+1) {
997 					auto e = &m_nodes[ncomb].edges[to_ch];
998 					foreach (chn; m_edgeEntries.getItems(chnodes))
999 						m_edgeEntries.insert(e, m_edgeEntries.getItems(m_nodes[chn].edges[to_ch]));
1000 				}
1001 
1002 				// add terminal indices
1003 				foreach (chn; m_edgeEntries.getItems(chnodes))
1004 					foreach (t; m_nodes[chn].terminals)
1005 						addTerminal(ncomb, t.index, t.var);
1006 				foreach (i; 1 .. m_nodes[ncomb].terminals.length)
1007 					assert(m_nodes[ncomb].terminals[0] != m_nodes[ncomb].terminals[i]);
1008 
1009 				m_nodes[n].edges[ch] = m_edgeEntries.create(ncomb);
1010 				assert(m_edgeEntries.hasLength(m_nodes[n].edges[ch], 1));
1011 			}
1012 
1013 			// process nodes recursively
1014 			foreach (ch; ubyte.min .. ubyte.max+1) {
1015 				// should only have single out-edges now
1016 				assert(m_edgeEntries.hasMaxLength(m_nodes[n].edges[ch], 1));
1017 				foreach (e; m_edgeEntries.getItems(m_nodes[n].edges[ch]))
1018 					node_stack.push(e);
1019 			}
1020 		}
1021 
1022 		import std.algorithm.sorting : sort;
1023 		foreach (ref n; m_nodes)
1024 			n.terminals[].sort!((a, b) => a.index < b.index)();
1025 
1026 		debug logDebug("Disambiguate done: %s nodes, %s max stack size", m_nodes.length, node_stack.maxSize);
1027 	}
1028 
1029 	void print()
1030 	const @trusted {
1031 		import std.algorithm : map;
1032 		import std.array : join;
1033 		import std.conv : to;
1034 		import std..string : format;
1035 
1036 		logInfo("Nodes:");
1037 		size_t i = 0;
1038 		foreach (n; m_nodes) {
1039 			string mapChar(ubyte ch) {
1040 				if (ch == TerminalChar) return "$";
1041 				if (ch >= '0' && ch <= '9') return to!string(cast(dchar)ch);
1042 				if (ch >= 'a' && ch <= 'z') return to!string(cast(dchar)ch);
1043 				if (ch >= 'A' && ch <= 'Z') return to!string(cast(dchar)ch);
1044 				if (ch == '^') return "^";
1045 				if (ch == '/') return "/";
1046 				return format("$%s", ch);
1047 			}
1048 			logInfo("  %s: %s", i, n.terminals[].map!(t => t.var != VarIndex.max ? format("T%s(%s)", t.index, t.var) : format("T%s", t.index)).join(" "));
1049 			ubyte first_char;
1050 			size_t list_hash;
1051 			NodeSet list;
1052 
1053 			void printEdges(ubyte last_char) {
1054 				if (!list.empty) {
1055 					string targets;
1056 					foreach (tn; m_edgeEntries.getItems(list))
1057 						targets ~= format(" %s", tn);
1058 					if (targets.length > 0)
1059 						logInfo("	[%s ... %s] -> %s", mapChar(first_char), mapChar(last_char), targets);
1060 				}
1061 			}
1062 			foreach (ch, tnodes; n.edges) {
1063 				auto h = m_edgeEntries.getHash(tnodes);
1064 				if (h != list_hash) {
1065 					printEdges(cast(ubyte)(ch-1));
1066 					list_hash = h;
1067 					list = tnodes;
1068 					first_char = cast(ubyte)ch;
1069 				}
1070 			}
1071 			printEdges(ubyte.max);
1072 			i++;
1073 		}
1074 	}
1075 
1076 	private void addEdge(NodeIndex from, NodeIndex to, ubyte ch, TerminalIndex terminal, VarIndex var = VarIndex.max)
1077 	@trusted {
1078 		m_edgeEntries.insert(&m_nodes[from].edges[ch], to);
1079 		addTerminal(to, terminal, var);
1080 	}
1081 
1082 	private NodeIndex addEdge(NodeIndex from, ubyte ch, TerminalIndex terminal, VarIndex var = VarIndex.max)
1083 	@trusted {
1084 		import std.algorithm : canFind;
1085 		import std..string : format;
1086 		if (!m_nodes[from].edges[ch].empty)
1087 			assert(false, format("%s is in %s", ch, m_nodes[from].edges[]));
1088 		auto nidx = addNode();
1089 		addEdge(from, nidx, ch, terminal, var);
1090 		return nidx;
1091 	}
1092 
1093 	private void addTerminal(NodeIndex node, TerminalIndex terminal, VarIndex var)
1094 	@trusted {
1095 		foreach (ref t; m_nodes[node].terminals) {
1096 			if (t.index == terminal) {
1097 				if (t.var != VarIndex.max && t.var != var)
1098 					assert(false, format("Ambiguous route var match!? %s vs %s", t.var, var));
1099 				t.var = var;
1100 				return;
1101 			}
1102 		}
1103 		m_nodes[node].terminals ~= TerminalTag(terminal, var);
1104 	}
1105 
1106 	private NodeIndex addNode()
1107 	@trusted {
1108 		assert(m_nodes.length <= 1_000_000_000, "More than 1B nodes in tree!?");
1109 		auto idx = cast(NodeIndex)m_nodes.length;
1110 		m_nodes ~= Node.init;
1111 		return idx;
1112 	}
1113 }
1114 
1115 struct LinkedSetBacking(T) {
1116 	import std.container.array : Array;
1117 	import std.range : isInputRange;
1118 
1119 	static struct Handle {
1120 		uint index = uint.max;
1121 		@property bool empty() const { return index == uint.max; }
1122 	}
1123 
1124 	private {
1125 		static struct Entry {
1126 			uint next;
1127 			T value;
1128 		}
1129 
1130 		Array!Entry m_storage;
1131 
1132 		static struct Range {
1133 			private {
1134 				Array!Entry* backing;
1135 				uint index;
1136 			}
1137 
1138 			@property bool empty() const { return index == uint.max; }
1139 			@property ref const(T) front() const { return (*backing)[index].value; }
1140 
1141 			void popFront()
1142 			{
1143 				index = (*backing)[index].next;
1144 			}
1145 		}
1146 	}
1147 
1148 	@property Handle emptySet() { return Handle.init; }
1149 
1150 	Handle create(scope T[] items...)
1151 	{
1152 		Handle ret;
1153 		foreach (i; items)
1154 			ret.index = createNode(i, ret.index);
1155 		return ret;
1156 	}
1157 
1158 	void insert(Handle* h, T value)
1159 	{
1160 		/*foreach (c; getItems(*h))
1161 			if (value == c)
1162 				return;*/
1163 		h.index = createNode(value, h.index);
1164 	}
1165 
1166 	void insert(R)(Handle* h, R items)
1167 		if (isInputRange!R)
1168 	{
1169 		foreach (itm; items)
1170 			insert(h, itm);
1171 	}
1172 
1173 	size_t getHash(Handle sh)
1174 	const {
1175 		// NOTE: the returned hash is order independent, to avoid bogus
1176 		//	   mismatches when comparing lists of different order
1177 		size_t ret = 0x72d2da6c;
1178 		while (sh != Handle.init) {
1179 			ret ^= (hashOf(m_storage[sh.index].value) ^ 0xb1bdfb8d) * 0x5dbf04a4;
1180 			sh.index = m_storage[sh.index].next;
1181 		}
1182 		return ret;
1183 	}
1184 
1185 	auto getItems(Handle sh) { return Range(&m_storage, sh.index); }
1186 	auto getItems(Handle sh) const { return Range(&(cast()this).m_storage, sh.index); }
1187 
1188 	bool hasMaxLength(Handle sh, size_t l)
1189 	const {
1190 		uint idx = sh.index;
1191 		do {
1192 			if (idx == uint.max) return true;
1193 			idx = m_storage[idx].next;
1194 		} while (l-- > 0);
1195 		return false;
1196 	}
1197 
1198 	bool hasLength(Handle sh, size_t l)
1199 	const {
1200 		uint idx = sh.index;
1201 		while (l-- > 0) {
1202 			if (idx == uint.max) return false;
1203 			idx = m_storage[idx].next;
1204 		}
1205 		return idx == uint.max;
1206 	}
1207 
1208 	private uint createNode(ref T val, uint next)
1209 	{
1210 		auto id = cast(uint)m_storage.length;
1211 		m_storage ~= Entry(next, val);
1212 		return id;
1213 	}
1214 }
1215 
1216 unittest {
1217 	import std.algorithm.comparison : equal;
1218 
1219 	LinkedSetBacking!int b;
1220 	auto s = b.emptySet;
1221 	assert(s.empty);
1222 	assert(b.getItems(s).empty);
1223 	s = b.create(3, 5, 7);
1224 	assert(b.getItems(s).equal([7, 5, 3]));
1225 	assert(!b.hasLength(s, 2));
1226 	assert(b.hasLength(s, 3));
1227 	assert(!b.hasLength(s, 4));
1228 	assert(!b.hasMaxLength(s, 2));
1229 	assert(b.hasMaxLength(s, 3));
1230 	assert(b.hasMaxLength(s, 4));
1231 
1232 	auto h = b.getHash(s);
1233 	assert(h != b.getHash(b.emptySet));
1234 	s = b.create(5, 3, 7);
1235 	assert(b.getHash(s) == h);
1236 
1237 	b.insert(&s, 11);
1238 	assert(b.hasLength(s, 4));
1239 	assert(b.getHash(s) != h);
1240 }
1241 
1242 private struct Stack(E)
1243 {
1244 	import std.range : isInputRange;
1245 
1246 	private {
1247 		E[] m_storage;
1248 		size_t m_fill;
1249 		debug size_t m_maxFill;
1250 	}
1251 
1252 	@property bool empty() const { return m_fill == 0; }
1253 
1254 	@property size_t fill() const { return m_fill; }
1255 
1256 	debug @property size_t maxSize() const { return m_maxFill; }
1257 
1258 	void reserve(size_t amt)
1259 	{
1260 		auto minsz = m_fill + amt;
1261 		if (m_storage.length < minsz) {
1262 			auto newlength = 64;
1263 			while (newlength < minsz) newlength *= 2;
1264 			m_storage.length = newlength;
1265 		}
1266 	}
1267 
1268 	void push(E el)
1269 	{
1270 		reserve(1);
1271 		m_storage[m_fill++] = el;
1272 		debug if (m_fill > m_maxFill) m_maxFill = m_fill;
1273 	}
1274 
1275 	void push(R)(R els)
1276 		if (isInputRange!R)
1277 	{
1278 		reserve(els.length);
1279 		foreach (el; els)
1280 			m_storage[m_fill++] = el;
1281 		debug if (m_fill > m_maxFill) m_maxFill = m_fill;
1282 	}
1283 
1284 	E pop()
1285 	{
1286 		assert(!empty, "Stack underflow.");
1287 		return m_storage[--m_fill];
1288 	}
1289 }