1 /**
2 	Automatic REST interface and client code generation facilities.
3 
4 	Copyright: © 2012-2016 RejectedSoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Михаил Страшун
7 */
8 module hb.web.rest;
9 
10 public import hb.web.common;
11 
12 import vibe.core.log;
13 import vibe.http.router : URLRouter;
14 import vibe.http.client : HTTPClientSettings;
15 import vibe.http.common : HTTPMethod;
16 import vibe.http.server : HTTPServerRequestDelegate;
17 import vibe.http.status : isSuccessCode;
18 import vibe.internal.meta.uda;
19 import vibe.internal.meta.funcattr;
20 import vibe.inet.url;
21 import vibe.inet.message : InetHeaderMap;
22 import hb.web.internal.rest.common : RestInterface, Route, SubInterfaceType;
23 import hb.web.auth : AuthInfo, handleAuthentication, handleAuthorization, isAuthenticated;
24 
25 import std.algorithm : startsWith, endsWith;
26 import std.range : isOutputRange;
27 import std.typecons : Nullable;
28 import std.typetuple : anySatisfy, Filter;
29 import std.traits;
30 
31 /**
32 	Registers a REST interface and connects it the the given instance.
33 
34 	Each method of the given class instance is mapped to the corresponing HTTP
35 	verb. Property methods are mapped to GET/PUT and all other methods are
36 	mapped according to their prefix verb. If the method has no known prefix,
37 	POST is used. The rest of the name is mapped to the path of the route
38 	according to the given `method_style`. Note that the prefix word must be
39 	all-lowercase and is delimited by either an upper case character, a
40 	non-alphabetic character, or the end of the string.
41 
42 	The following table lists the mappings from prefix verb to HTTP verb:
43 
44 	$(TABLE
45 		$(TR $(TH HTTP method) $(TH Recognized prefixes))
46 		$(TR $(TD GET)	  $(TD get, query))
47 		$(TR $(TD PUT)    $(TD set, put))
48 		$(TR $(TD POST)   $(TD add, create, post))
49 		$(TR $(TD DELETE) $(TD remove, erase, delete))
50 		$(TR $(TD PATCH)  $(TD update, patch))
51 	)
52 
53 	If a method has its first parameter named 'id', it will be mapped to ':id/method' and
54 	'id' is expected to be part of the URL instead of a JSON request. Parameters with default
55 	values will be optional in the corresponding JSON request.
56 
57 	Any interface that you return from a getter will be made available with the
58 	base url and its name appended.
59 
60 	Params:
61 		router = The HTTP router on which the interface will be registered
62 		instance = Class instance to use for the REST mapping - Note that TImpl
63 			must either be an interface type, or a class which derives from a
64 			single interface
65 		settings = Additional settings, such as the $(D MethodStyle), or the prefix.
66 			See $(D RestInterfaceSettings) for more details.
67 
68 	See_Also:
69 		$(D RestInterfaceClient) class for a seamless way to access such a generated API
70 
71 */
72 URLRouter registerRestInterface(TImpl)(URLRouter router, TImpl instance, RestInterfaceSettings settings = null)
73 {
74 	import std.algorithm : filter, map, all;
75 	import std.array : array;
76 	import std.range : front;
77 	import hb.web.internal.rest.common : ParameterKind;
78 
79 	auto intf = RestInterface!TImpl(settings, false);
80 
81 	foreach (i, ovrld; intf.SubInterfaceFunctions) {
82 		enum fname = __traits(identifier, intf.SubInterfaceFunctions[i]);
83 		alias R = ReturnType!ovrld;
84 
85 		static if (isInstanceOf!(Collection, R)) {
86 			auto ret = __traits(getMember, instance, fname)(R.ParentIDs.init);
87 			router.registerRestInterface!(R.Interface)(ret.m_interface, intf.subInterfaces[i].settings);
88 		} else {
89 			auto ret = __traits(getMember, instance, fname)();
90 			router.registerRestInterface!R(ret, intf.subInterfaces[i].settings);
91 		}
92 	}
93 
94 
95 	foreach (i, func; intf.RouteFunctions) {
96 		auto route = intf.routes[i];
97 
98 		// normal handler
99 		auto handler = jsonMethodHandler!(func, i)(instance, intf);
100 
101 		auto diagparams = route.parameters.filter!(p => p.kind != ParameterKind.internal).map!(p => p.fieldName).array;
102 		logDiagnostic("REST route: %s %s %s", route.method, route.fullPattern, diagparams);
103 		router.match(route.method, route.fullPattern, handler);
104 	}
105 
106 	// here we filter our already existing OPTIONS routes, so we don't overwrite whenever the user explicitly made his own OPTIONS route
107 	auto routesGroupedByPattern = intf.getRoutesGroupedByPattern.filter!(rs => rs.all!(r => r.method != HTTPMethod.OPTIONS));
108 
109 	foreach(routes; routesGroupedByPattern){
110 		auto route = routes.front;
111 		auto handler = optionsMethodHandler(routes, settings);
112 
113 		auto diagparams = route.parameters.filter!(p => p.kind != ParameterKind.internal).map!(p => p.fieldName).array;
114 		logDiagnostic("REST route: %s %s %s", HTTPMethod.OPTIONS, route.fullPattern, diagparams);
115 		router.match(HTTPMethod.OPTIONS, route.fullPattern, handler);
116 	}
117 	return router;
118 }
119 
120 /// ditto
121 URLRouter registerRestInterface(TImpl)(URLRouter router, TImpl instance, MethodStyle style)
122 {
123 	return registerRestInterface(router, instance, "/", style);
124 }
125 
126 /// ditto
127 URLRouter registerRestInterface(TImpl)(URLRouter router, TImpl instance, string url_prefix,
128 	MethodStyle style = MethodStyle.lowerUnderscored)
129 {
130 	auto settings = new RestInterfaceSettings;
131 	if (!url_prefix.startsWith("/")) url_prefix = "/"~url_prefix;
132 	settings.baseURL = URL("http://127.0.0.1"~url_prefix);
133 	settings.methodStyle = style;
134 	return registerRestInterface(router, instance, settings);
135 }
136 
137 
138 /**
139 	This is a very limited example of REST interface features. Please refer to
140 	the "rest" project in the "examples" folder for a full overview.
141 
142 	All details related to HTTP are inferred from the interface declaration.
143 */
144 @safe unittest
145 {
146 	@path("/")
147 	interface IMyAPI
148 	{
149 		@safe:
150 		// GET /api/greeting
151 		@property string greeting();
152 
153 		// PUT /api/greeting
154 		@property void greeting(string text);
155 
156 		// POST /api/users
157 		@path("/users")
158 		void addNewUser(string name);
159 
160 		// GET /api/users
161 		@property string[] users();
162 
163 		// GET /api/:id/name
164 		string getName(int id);
165 
166 		// GET /some_custom_json
167 		Json getSomeCustomJson();
168 	}
169 
170 	// vibe.d takes care of all JSON encoding/decoding
171 	// and actual API implementation can work directly
172 	// with native types
173 
174 	class API : IMyAPI
175 	{
176 		private {
177 			string m_greeting;
178 			string[] m_users;
179 		}
180 
181 		@property string greeting() { return m_greeting; }
182 		@property void greeting(string text) { m_greeting = text; }
183 
184 		void addNewUser(string name) { m_users ~= name; }
185 
186 		@property string[] users() { return m_users; }
187 
188 		string getName(int id) { return m_users[id]; }
189 
190 		Json getSomeCustomJson()
191 		{
192 			Json ret = Json.emptyObject;
193 			ret["somefield"] = "Hello, World!";
194 			return ret;
195 		}
196 	}
197 
198 	// actual usage, this is usually done in app.d module
199 	// constructor
200 
201 	void static_this()
202 	{
203 		import vibe.http.server, vibe.http.router;
204 
205 		auto router = new URLRouter;
206 		router.registerRestInterface(new API());
207 		listenHTTP(new HTTPServerSettings(), router);
208 	}
209 }
210 
211 
212 /**
213 	Returns a HTTP handler delegate that serves a JavaScript REST client.
214 */
215 HTTPServerRequestDelegate serveRestJSClient(I)(RestInterfaceSettings settings)
216 	if (is(I == interface))
217 {
218 	import std.digest.md : md5Of;
219 	import std.digest.digest : toHexString;
220 	import std.array : appender;
221 	import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
222 	import vibe.http.status : HTTPStatus;
223 
224 	auto app = appender!string();
225 	generateRestJSClient!I(app, settings);
226 	auto hash = app.data.md5Of.toHexString.idup;
227 
228 	void serve(HTTPServerRequest req, HTTPServerResponse res)
229 	{
230 		if (auto pv = "If-None-Match" in res.headers) {
231 			res.statusCode = HTTPStatus.notModified;
232 			res.writeVoidBody();
233 			return;
234 		}
235 
236 		res.headers["Etag"] = hash;
237 		res.writeBody(app.data, "application/javascript; charset=UTF-8");
238 	}
239 
240 	return &serve;
241 }
242 /// ditto
243 HTTPServerRequestDelegate serveRestJSClient(I)(URL base_url)
244 {
245 	auto settings = new RestInterfaceSettings;
246 	settings.baseURL = base_url;
247 	return serveRestJSClient!I(settings);
248 }
249 /// ditto
250 HTTPServerRequestDelegate serveRestJSClient(I)(string base_url)
251 {
252 	auto settings = new RestInterfaceSettings;
253 	settings.baseURL = URL(base_url);
254 	return serveRestJSClient!I(settings);
255 }
256 
257 ///
258 unittest {
259 	import vibe.http.server;
260 
261 	interface MyAPI {
262 		string getFoo();
263 		void postBar(string param);
264 	}
265 
266 	void test()
267 	{
268 		auto restsettings = new RestInterfaceSettings;
269 		restsettings.baseURL = URL("http://api.example.org/");
270 
271 		auto router = new URLRouter;
272 		router.get("/myapi.js", serveRestJSClient!MyAPI(restsettings));
273 		//router.get("/myapi.js", serveRestJSClient!MyAPI(URL("http://api.example.org/")));
274 		//router.get("/myapi.js", serveRestJSClient!MyAPI("http://api.example.org/"));
275 		//router.get("/", staticTemplate!"index.dt");
276 
277 		listenHTTP(new HTTPServerSettings, router);
278 	}
279 
280 	/*
281 		index.dt:
282 		html
283 			head
284 				title JS REST client test
285 				script(src="myapi.js")
286 			body
287 				button(onclick="MyAPI.postBar('hello');")
288 	*/
289 }
290 
291 
292 /**
293 	Generates JavaScript code to access a REST interface from the browser.
294 */
295 void generateRestJSClient(I, R)(ref R output, RestInterfaceSettings settings = null)
296 	if (is(I == interface) && isOutputRange!(R, char))
297 {
298 	import hb.web.internal.rest.jsclient : generateInterface, JSRestClientSettings;
299 	auto jsgenset = new JSRestClientSettings;
300 	output.generateInterface!I(settings, jsgenset, true);
301 }
302 
303 /// Writes a JavaScript REST client to a local .js file.
304 unittest {
305 	import vibe.core.file;
306 
307 	interface MyAPI {
308 		void getFoo();
309 		void postBar(string param);
310 	}
311 
312 	void generateJSClientImpl()
313 	{
314 		import std.array : appender;
315 
316 		auto app = appender!string;
317 		auto settings = new RestInterfaceSettings;
318 		settings.baseURL = URL("http://localhost/");
319 		generateRestJSClient!MyAPI(app, settings);
320 	}
321 
322 	generateJSClientImpl();
323 }
324 
325 
326 /**
327 	Implements the given interface by forwarding all public methods to a REST server.
328 
329 	The server must talk the same protocol as registerRestInterface() generates. Be sure to set
330 	the matching method style for this. The RestInterfaceClient class will derive from the
331 	interface that is passed as a template argument. It can be used as a drop-in replacement
332 	of the real implementation of the API this way.
333 */
334 class RestInterfaceClient(I) : I
335 {
336 	import vibe.inet.url : URL, PathEntry;
337 	import vibe.http.client : HTTPClientRequest;
338 	import std.typetuple : staticMap;
339 
340 	private alias Info = RestInterface!I;
341 
342 	//pragma(msg, "imports for "~I.stringof~":");
343 	//pragma(msg, generateModuleImports!(I)());
344 	mixin(generateModuleImports!I());
345 
346 	private {
347 		// storing this struct directly causes a segfault when built with
348 		// LDC 0.15.x, so we are using a pointer here:
349 		RestInterface!I* m_intf;
350 		RequestFilter m_requestFilter;
351 		staticMap!(RestInterfaceClient, Info.SubInterfaceTypes) m_subInterfaces;
352 	}
353 
354 	alias RequestFilter = void delegate(HTTPClientRequest req);
355 
356 	/**
357 		Creates a new REST client implementation of $(D I).
358 	*/
359 	this(RestInterfaceSettings settings)
360 	{
361 		m_intf = new Info(settings, true);
362 
363 		foreach (i, SI; Info.SubInterfaceTypes)
364 			m_subInterfaces[i] = new RestInterfaceClient!SI(m_intf.subInterfaces[i].settings);
365 	}
366 
367 	/// ditto
368 	this(string base_url, MethodStyle style = MethodStyle.lowerUnderscored)
369 	{
370 		this(URL(base_url), style);
371 	}
372 
373 	/// ditto
374 	this(URL base_url, MethodStyle style = MethodStyle.lowerUnderscored)
375 	{
376 		scope settings = new RestInterfaceSettings;
377 		settings.baseURL = base_url;
378 		settings.methodStyle = style;
379 		this(settings);
380 	}
381 
382 	/**
383 		An optional request filter that allows to modify each request before it is made.
384 	*/
385 	final @property RequestFilter requestFilter()
386 	{
387 		return m_requestFilter;
388 	}
389 
390 	/// ditto
391 	final @property void requestFilter(RequestFilter v)
392 	{
393 		m_requestFilter = v;
394 		foreach (i, SI; Info.SubInterfaceTypes)
395 			m_subInterfaces[i].requestFilter = v;
396 	}
397 
398 	//pragma(msg, "restinterface:");
399 	mixin(generateRestClientMethods!I());
400 
401 	protected {
402 		import vibe.data.json : Json;
403 		import vibe.textfilter.urlencode;
404 
405 		/**
406 		 * Perform a request to the interface using the given parameters.
407 		 *
408 		 * Params:
409 		 * verb = Kind of request (See $(D HTTPMethod) enum).
410 		 * name = Location to request. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
411 		 *		it will be '/rejectedsoftware/vibe.d/issues'.
412 		 * hdrs = The headers to send. Some field might be overriden (such as Content-Length). However, Content-Type will NOT be overriden.
413 		 * query = The $(B encoded) query string. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
414 		 *		it will be 'author%3ASantaClaus'.
415 		 * body_ = The body to send, as a string. If a Content-Type is present in $(D hdrs), it will be used, otherwise it will default to
416 		 *		the generic type "application/json".
417 		 * reqReturnHdrs = A map of required return headers.
418 		 *				   To avoid returning unused headers, nothing is written
419 		 *				   to this structure unless there's an (usually empty)
420 		 *				   entry (= the key exists) with the same key.
421 		 *				   If any key present in `reqReturnHdrs` is not present
422 		 *				   in the response, an Exception is thrown.
423 		 * optReturnHdrs = A map of optional return headers.
424 		 *				   This behaves almost as exactly as reqReturnHdrs,
425 		 *				   except that non-existent key in the response will
426 		 *				   not cause it to throw, but rather to set this entry
427 		 *				   to 'null'.
428 		 *
429 		 * Returns:
430 		 *     The Json object returned by the request
431 		 */
432 		Json request(HTTPMethod verb, string name,
433 					 in ref InetHeaderMap hdrs, string query, string body_,
434 					 ref InetHeaderMap reqReturnHdrs,
435 					 ref InetHeaderMap optReturnHdrs) const
436 		{
437 			auto path = URL(m_intf.baseURL).pathString;
438 
439 			if (name.length)
440 			{
441 				if (path.length && path[$ - 1] == '/' && name[0] == '/')
442 					path ~= name[1 .. $];
443 				else if (path.length && path[$ - 1] == '/' || name[0] == '/')
444 					path ~= name;
445 				else
446 					path ~= '/' ~ name;
447 			}
448 
449 			auto httpsettings = m_intf.settings.httpClientSettings;
450 
451 			return .request(URL(m_intf.baseURL), m_requestFilter, verb, path,
452 				hdrs, query, body_, reqReturnHdrs, optReturnHdrs, httpsettings);
453 		}
454 	}
455 }
456 
457 ///
458 unittest
459 {
460 	interface IMyApi
461 	{
462 		// GET /status
463 		string getStatus();
464 
465 		// GET /greeting
466 		@property string greeting();
467 		// PUT /greeting
468 		@property void greeting(string text);
469 
470 		// POST /new_user
471 		void addNewUser(string name);
472 		// GET /users
473 		@property string[] users();
474 		// GET /:id/name
475 		string getName(int id);
476 
477 		Json getSomeCustomJson();
478 	}
479 
480 	void test()
481 	{
482 		auto api = new RestInterfaceClient!IMyApi("http://127.0.0.1/api/");
483 
484 		logInfo("Status: %s", api.getStatus());
485 		api.greeting = "Hello, World!";
486 		logInfo("Greeting message: %s", api.greeting);
487 		api.addNewUser("Peter");
488 		api.addNewUser("Igor");
489 		logInfo("Users: %s", api.users);
490 		logInfo("First user name: %s", api.getName(0));
491 	}
492 }
493 
494 
495 /**
496 	Encapsulates settings used to customize the generated REST interface.
497 */
498 class RestInterfaceSettings {
499 	/** The public URL below which the REST interface is registered.
500 	*/
501 	URL baseURL;
502 
503 	/** List of allowed origins for CORS
504 
505 		Empty list is interpreted as allowing all origins (e.g. *)
506 	*/
507 	string[] allowedOrigins;
508 
509 	/** Naming convention used for the generated URLs.
510 	*/
511 	MethodStyle methodStyle = MethodStyle.lowerUnderscored;
512 
513 	/** Ignores a trailing underscore in method and function names.
514 
515 		With this setting set to $(D true), it's possible to use names in the
516 		REST interface that are reserved words in D.
517 	*/
518 	bool stripTrailingUnderscore = true;
519 
520 	/// Overrides the default HTTP client settings used by the `RestInterfaceClient`.
521 	HTTPClientSettings httpClientSettings;
522 
523 	@property RestInterfaceSettings dup()
524 	const @safe {
525 		auto ret = new RestInterfaceSettings;
526 		ret.baseURL = this.baseURL;
527 		ret.methodStyle = this.methodStyle;
528 		ret.stripTrailingUnderscore = this.stripTrailingUnderscore;
529 		ret.allowedOrigins = this.allowedOrigins.dup;
530 		return ret;
531 	}
532 }
533 
534 
535 /**
536 	Models REST collection interfaces using natural D syntax.
537 
538 	Use this type as the return value of a REST interface getter method/property
539 	to model a collection of objects. `opIndex` is used to make the individual
540 	entries accessible using the `[index]` syntax. Nested collections are
541 	supported.
542 
543 	The interface `I` needs to define a struct named `CollectionIndices`. The
544 	members of this struct denote the types and names of the indexes that lead
545 	to a particular resource. If a collection is nested within another
546 	collection, the order of these members must match the nesting order
547 	(outermost first).
548 
549 	The parameter list of all of `I`'s methods must begin with all but the last
550 	entry in `CollectionIndices`. Methods that also match the last entry will be
551 	considered methods of a collection item (`collection[index].method()`),
552 	wheres all other methods will be considered methods of the collection
553 	itself (`collection.method()`).
554 
555 	The name of the index parameters affects the default path of a method's
556 	route. Normal parameter names will be subject to the same rules as usual
557 	routes (see `registerRestInterface`) and will be mapped to query or form
558 	parameters at the protocol level. Names starting with an underscore will
559 	instead be mapped to path placeholders. For example,
560 	`void getName(int __item_id)` will be mapped to a GET request to the
561 	path `":item_id/name"`.
562 */
563 struct Collection(I)
564 	if (is(I == interface))
565 {
566 	import std.typetuple;
567 
568 	static assert(is(I.CollectionIndices == struct), "Collection interfaces must define a CollectionIndices struct.");
569 
570 	alias Interface = I;
571 	alias AllIDs = TypeTuple!(typeof(I.CollectionIndices.tupleof));
572 	alias AllIDNames = FieldNameTuple!(I.CollectionIndices);
573 	static assert(AllIDs.length >= 1, I.stringof~".CollectionIndices must define at least one member.");
574 	static assert(AllIDNames.length == AllIDs.length);
575 	alias ItemID = AllIDs[$-1];
576 	alias ParentIDs = AllIDs[0 .. $-1];
577 	alias ParentIDNames = AllIDNames[0 .. $-1];
578 
579 	private {
580 		I m_interface;
581 		ParentIDs m_parentIDs;
582 	}
583 
584 	/** Constructs a new collection instance that is tied to a particular
585 		parent collection entry.
586 
587 		Params:
588 			api = The target interface imstance to be mapped as a collection
589 			pids = The indexes of all collections in which this collection is
590 				nested (if any)
591 	*/
592 	this(I api, ParentIDs pids)
593 	{
594 		m_interface = api;
595 		m_parentIDs = pids;
596 	}
597 
598 	static struct Item {
599 		private {
600 			I m_interface;
601 			AllIDs m_id;
602 		}
603 
604 		this(I api, AllIDs id)
605 		{
606 			m_interface = api;
607 			m_id = id;
608 		}
609 
610 		// forward all item methods
611 		mixin(() {
612 			string ret;
613 			foreach (m; __traits(allMembers, I)) {
614 				foreach (ovrld; MemberFunctionsTuple!(I, m)) {
615 					alias PT = ParameterTypeTuple!ovrld;
616 					static if (matchesAllIDs!ovrld)
617 						ret ~= "auto "~m~"(ARGS...)(ARGS args) { return m_interface."~m~"(m_id, args); }\n";
618 				}
619 			}
620 			return ret;
621 		} ());
622 	}
623 
624 	// Note: the example causes a recursive template instantiation if done as a documented unit test:
625 	/** Accesses a single collection entry.
626 
627 		Example:
628 		---
629 		interface IMain {
630 			@property Collection!IItem items();
631 		}
632 
633 		interface IItem {
634 			struct CollectionIndices {
635 				int _itemID;
636 			}
637 
638 			@method(HTTPMethod.GET)
639 			string name(int _itemID);
640 		}
641 
642 		void test(IMain main)
643 		{
644 			auto item_name = main.items[23].name; // equivalent to IItem.name(23)
645 		}
646 		---
647 	*/
648 	Item opIndex(ItemID id)
649 	{
650 		return Item(m_interface, m_parentIDs, id);
651 	}
652 
653 	// forward all non-item methods
654 	mixin(() {
655 		string ret;
656 		foreach (m; __traits(allMembers, I)) {
657 			foreach (ovrld; MemberFunctionsTuple!(I, m)) {
658 				alias PT = ParameterTypeTuple!ovrld;
659 				static if (!matchesAllIDs!ovrld) {
660 					static assert(matchesParentIDs!ovrld,
661 						"Collection methods must take all parent IDs as the first parameters."~PT.stringof~"   "~ParentIDs.stringof);
662 					ret ~= "auto "~m~"(ARGS...)(ARGS args) { return m_interface."~m~"(m_parentIDs, args); }\n";
663 				}
664 			}
665 		}
666 		return ret;
667 	} ());
668 
669 	private template matchesParentIDs(alias func) {
670 		static if (is(ParameterTypeTuple!func[0 .. ParentIDs.length] == ParentIDs)) {
671 			static if (ParentIDNames.length == 0) enum matchesParentIDs = true;
672 			else static if (ParameterIdentifierTuple!func[0 .. ParentIDNames.length] == ParentIDNames)
673 				enum matchesParentIDs = true;
674 			else enum matchesParentIDs = false;
675 		} else enum matchesParentIDs = false;
676 	}
677 
678 	private template matchesAllIDs(alias func) {
679 		static if (is(ParameterTypeTuple!func[0 .. AllIDs.length] == AllIDs)) {
680 			static if (ParameterIdentifierTuple!func[0 .. AllIDNames.length] == AllIDNames)
681 				enum matchesAllIDs = true;
682 			else enum matchesAllIDs = false;
683 		} else enum matchesAllIDs = false;
684 	}
685 }
686 
687 /// Model two nested collections using path based indexes
688 unittest {
689 	//
690 	// API definition
691 	//
692 	interface SubItemAPI {
693 		// Define the index path that leads to a sub item
694 		struct CollectionIndices {
695 			// The ID of the base item. This must match the definition in
696 			// ItemAPI.CollectionIndices
697 			string _item;
698 			// The index if the sub item
699 			int _index;
700 		}
701 
702 		// GET /items/:item/subItems/length
703 		@property int length(string _item);
704 
705 		// GET /items/:item/subItems/:index/squared_position
706 		int getSquaredPosition(string _item, int _index);
707 	}
708 
709 	interface ItemAPI {
710 		// Define the index that identifies an item
711 		struct CollectionIndices {
712 			string _item;
713 		}
714 
715 		// base path /items/:item/subItems
716 		Collection!SubItemAPI subItems(string _item);
717 
718 		// GET /items/:item/name
719 		@property string name(string _item);
720 	}
721 
722 	interface API {
723 		// a collection of items at the base path /items/
724 		Collection!ItemAPI items();
725 	}
726 
727 	//
728 	// Local API implementation
729 	//
730 	class SubItemAPIImpl : SubItemAPI {
731 		@property int length(string _item) { return 10; }
732 
733 		int getSquaredPosition(string _item, int _index) { return _index ^^ 2; }
734 	}
735 
736 	class ItemAPIImpl : ItemAPI {
737 		private SubItemAPIImpl m_subItems;
738 
739 		this() { m_subItems = new SubItemAPIImpl; }
740 
741 		Collection!SubItemAPI subItems(string _item) { return Collection!SubItemAPI(m_subItems, _item); }
742 
743 		string name(string _item) { return _item; }
744 	}
745 
746 	class APIImpl : API {
747 		private ItemAPIImpl m_items;
748 
749 		this() { m_items = new ItemAPIImpl; }
750 
751 		Collection!ItemAPI items() { return Collection!ItemAPI(m_items); }
752 	}
753 
754 	//
755 	// Resulting API usage
756 	//
757 	API api = new APIImpl; // A RestInterfaceClient!API would work just as well
758 	assert(api.items["foo"].name == "foo");
759 	assert(api.items["foo"].subItems.length == 10);
760 	assert(api.items["foo"].subItems[2].getSquaredPosition() == 4);
761 }
762 
763 unittest {
764 	interface I {
765 		struct CollectionIndices {
766 			int id1;
767 			string id2;
768 		}
769 
770 		void a(int id1, string id2);
771 		void b(int id1, int id2);
772 		void c(int id1, string p);
773 		void d(int id1, string id2, int p);
774 		void e(int id1, int id2, int p);
775 		void f(int id1, string p, int q);
776 	}
777 
778 	Collection!I coll;
779 	static assert(is(typeof(coll["x"].a()) == void));
780 	static assert(is(typeof(coll.b(42)) == void));
781 	static assert(is(typeof(coll.c("foo")) == void));
782 	static assert(is(typeof(coll["x"].d(42)) == void));
783 	static assert(is(typeof(coll.e(42, 42)) == void));
784 	static assert(is(typeof(coll.f("foo", 42)) == void));
785 }
786 
787 /// Model two nested collections using normal query parameters as indexes
788 unittest {
789 	//
790 	// API definition
791 	//
792 	interface SubItemAPI {
793 		// Define the index path that leads to a sub item
794 		struct CollectionIndices {
795 			// The ID of the base item. This must match the definition in
796 			// ItemAPI.CollectionIndices
797 			string item;
798 			// The index if the sub item
799 			int index;
800 		}
801 
802 		// GET /items/subItems/length?item=...
803 		@property int length(string item);
804 
805 		// GET /items/subItems/squared_position?item=...&index=...
806 		int getSquaredPosition(string item, int index);
807 	}
808 
809 	interface ItemAPI {
810 		// Define the index that identifies an item
811 		struct CollectionIndices {
812 			string item;
813 		}
814 
815 		// base path /items/subItems?item=...
816 		Collection!SubItemAPI subItems(string item);
817 
818 		// GET /items/name?item=...
819 		@property string name(string item);
820 	}
821 
822 	interface API {
823 		// a collection of items at the base path /items/
824 		Collection!ItemAPI items();
825 	}
826 
827 	//
828 	// Local API implementation
829 	//
830 	class SubItemAPIImpl : SubItemAPI {
831 		@property int length(string item) { return 10; }
832 
833 		int getSquaredPosition(string item, int index) { return index ^^ 2; }
834 	}
835 
836 	class ItemAPIImpl : ItemAPI {
837 		private SubItemAPIImpl m_subItems;
838 
839 		this() { m_subItems = new SubItemAPIImpl; }
840 
841 		Collection!SubItemAPI subItems(string item) { return Collection!SubItemAPI(m_subItems, item); }
842 
843 		string name(string item) { return item; }
844 	}
845 
846 	class APIImpl : API {
847 		private ItemAPIImpl m_items;
848 
849 		this() { m_items = new ItemAPIImpl; }
850 
851 		Collection!ItemAPI items() { return Collection!ItemAPI(m_items); }
852 	}
853 
854 	//
855 	// Resulting API usage
856 	//
857 	API api = new APIImpl; // A RestInterfaceClient!API would work just as well
858 	assert(api.items["foo"].name == "foo");
859 	assert(api.items["foo"].subItems.length == 10);
860 	assert(api.items["foo"].subItems[2].getSquaredPosition() == 4);
861 }
862 
863 unittest {
864 	interface C {
865 		struct CollectionIndices {
866 			int _ax;
867 			int _b;
868 		}
869 		void testB(int _ax, int _b);
870 	}
871 
872 	interface B {
873 		struct CollectionIndices {
874 			int _a;
875 		}
876 		Collection!C c();
877 		void testA(int _a);
878 	}
879 
880 	interface A {
881 		Collection!B b();
882 	}
883 
884 	static assert (!is(typeof(A.init.b[1].c[2].testB())));
885 }
886 
887 /** Allows processing the server request/response before the handler method is called.
888 
889 	Note that this attribute is only used by `registerRestInterface`, but not
890 	by the client generators. This attribute expects the name of a parameter that
891 	will receive its return value.
892 
893 	Writing to the response body from within the specified hander function
894 	causes any further processing of the request to be skipped. In particular,
895 	the route handler method will not be called.
896 
897 	Note:
898 		The example shows the drawback of this attribute. It generally is a
899 		leaky abstraction that propagates to the base interface. For this
900 		reason the use of this attribute is not recommended, unless there is
901 		no suitable alternative.
902 */
903 alias before = vibe.internal.meta.funcattr.before;
904 
905 ///
906 unittest {
907 	import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
908 
909 	interface MyService {
910 		long getHeaderCount(size_t foo = 0);
911 	}
912 
913 	size_t handler(HTTPServerRequest req, HTTPServerResponse res)
914 	{
915 		return req.headers.length;
916 	}
917 
918 	class MyServiceImpl : MyService {
919 		// the "foo" parameter will receive the number of request headers
920 		@before!handler("foo")
921 		long getHeaderCount(size_t foo)
922 		{
923 			return foo;
924 		}
925 	}
926 
927 	void test(URLRouter router)
928 	{
929 		router.registerRestInterface(new MyServiceImpl);
930 	}
931 }
932 
933 
934 /** Allows processing the return value of a handler method and the request/response objects.
935 
936 	The value returned by the REST API will be the value returned by the last
937 	`@after` handler, which allows to post process the results of the handler
938 	method.
939 
940 	Writing to the response body from within the specified handler function
941 	causes any further processing of the request ot be skipped, including
942 	any other `@after` annotations and writing the result value.
943 */
944 alias after = vibe.internal.meta.funcattr.after;
945 
946 ///
947 unittest {
948 	import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
949 
950 	interface MyService {
951 		long getMagic();
952 	}
953 
954 	long handler(long ret, HTTPServerRequest req, HTTPServerResponse res)
955 	{
956 		return ret * 2;
957 	}
958 
959 	class MyServiceImpl : MyService{
960 		// the result reported by the REST API will be 42
961 		@after!handler
962 		long getMagic()
963 		{
964 			return 21;
965 		}
966 	}
967 
968 	void test(URLRouter router)
969 	{
970 		router.registerRestInterface(new MyServiceImpl);
971 	}
972 }
973 
974 /**
975  * Generate an handler that will wrap the server's method
976  *
977  * This function returns an handler, generated at compile time, that
978  * will deserialize the parameters, pass them to the function implemented
979  * by the user, and return what it needs to return, be it header parameters
980  * or body, which is at the moment either a pure string or a Json object.
981  *
982  * One thing that makes this method more complex that it needs be is the
983  * inability for D to attach UDA to parameters. This means we have to roll
984  * our own implementation, which tries to be as easy to use as possible.
985  * We'll require the user to give the name of the parameter as a string to
986  * our UDA. Hopefully, we're also able to detect at compile time if the user
987  * made a typo of any kind (see $(D genInterfaceValidationError)).
988  *
989  * Note:
990  * Lots of abbreviations are used to ease the code, such as
991  * PTT (ParameterTypeTuple), WPAT (WebParamAttributeTuple)
992  * and PWPAT (ParameterWebParamAttributeTuple).
993  *
994  * Params:
995  *	T = type of the object which represent the REST server (user implemented).
996  *	Func = An alias to the function of $(D T) to wrap.
997  *
998  *	inst = REST server on which to call our $(D Func).
999  *	settings = REST server configuration.
1000  *
1001  * Returns:
1002  *	A delegate suitable to use as an handler for an HTTP request.
1003  */
1004 private HTTPServerRequestDelegate jsonMethodHandler(alias Func, size_t ridx, T)(T inst, ref RestInterface!T intf)
1005 {
1006 	import std.string : format;
1007 	import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
1008 	import vibe.http.common : HTTPStatusException, HTTPStatus, enforceBadRequest;
1009 	import vibe.utils.string : sanitizeUTF8;
1010 	import hb.web.internal.rest.common : ParameterKind;
1011 	import vibe.internal.meta.funcattr : IsAttributedParameter, computeAttributedParameterCtx;
1012 	import vibe.textfilter.urlencode : urlDecode;
1013 
1014 	enum Method = __traits(identifier, Func);
1015 	alias PTypes = ParameterTypeTuple!Func;
1016 	alias PDefaults = ParameterDefaultValueTuple!Func;
1017 	alias RT = ReturnType!(FunctionTypeOf!Func);
1018 	static const sroute = RestInterface!T.staticRoutes[ridx];
1019 	auto route = intf.routes[ridx];
1020 	auto settings = intf.settings;
1021 
1022 	void handler(HTTPServerRequest req, HTTPServerResponse res)
1023 	@safe {
1024 		if (route.bodyParameters.length) {
1025 			logDebug("BODYPARAMS: %s %s", Method, route.bodyParameters.length);
1026 			/*enforceBadRequest(req.contentType == "application/json",
1027 				"The Content-Type header needs to be set to application/json.");*/
1028 			enforceBadRequest(req.json.type != Json.Type.undefined,
1029 				"The request body does not contain a valid JSON value.");
1030 			enforceBadRequest(req.json.type == Json.Type.object,
1031 				"The request body must contain a JSON object with an entry for each parameter.");
1032 		}
1033 
1034 		static if (isAuthenticated!(T, Func)) {
1035 			auto auth_info = handleAuthentication!Func(inst, req, res);
1036 			if (res.headerWritten) return;
1037 		}
1038 
1039 		PTypes params;
1040 
1041 		enum bool hasSingleBodyParam = {
1042 			int i;
1043 			foreach (j, PT; PTypes)
1044 				if (sroute.parameters[j].kind == ParameterKind.body_)
1045 					i++;
1046 
1047 			return i == 1;
1048 		}();
1049 
1050 		foreach (i, PT; PTypes) {
1051 			enum sparam = sroute.parameters[i];
1052 			enum pname = sparam.name;
1053 			auto fieldname = route.parameters[i].fieldName;
1054 			static if (isInstanceOf!(Nullable, PT)) PT v;
1055 			else Nullable!PT v;
1056 
1057 			static if (sparam.kind == ParameterKind.auth) {
1058 				v = auth_info;
1059 			} else static if (sparam.kind == ParameterKind.query) {
1060 				if (auto pv = fieldname in req.query)
1061 					v = fromRestString!PT(*pv);
1062 			} else static if (sparam.kind == ParameterKind.body_) {
1063 				try {
1064 					// for @bodyParam("s") and by default the entire body should be serialized
1065 					if (sparam.fieldName == bodyParamWholeName || hasSingleBodyParam && sparam.fieldName.length == 0)
1066 						v = deserializeJson!PT(req.json);
1067 					else if (auto pv = fieldname in req.json)
1068 						v = deserializeJson!PT(*pv);
1069 				} catch (JSONException e)
1070 					enforceBadRequest(false, e.msg);
1071 			} else static if (sparam.kind == ParameterKind.header) {
1072 				if (auto pv = fieldname in req.headers)
1073 					v = fromRestString!PT(*pv);
1074 			} else static if (sparam.kind == ParameterKind.attributed) {
1075 				static if (!__traits(compiles, () @safe { computeAttributedParameterCtx!(Func, pname)(inst, req, res); } ()))
1076 					pragma(msg, "Non-@safe @before evaluators are deprecated - annotate evaluator function for parameter "~pname~" of "~T.stringof~"."~Method~" as @safe.");
1077 				v = () @trusted { return computeAttributedParameterCtx!(Func, pname)(inst, req, res); } ();
1078 			} else static if (sparam.kind == ParameterKind.internal) {
1079 				if (auto pv = fieldname in req.params)
1080 					v = fromRestString!PT(urlDecode(*pv));
1081 			} else static assert(false, "Unhandled parameter kind.");
1082 
1083 			static if (isInstanceOf!(Nullable, PT)) params[i] = v;
1084 			else if (v.isNull()) {
1085 				static if (!is(PDefaults[i] == void)) params[i] = PDefaults[i];
1086 				else enforceBadRequest(false, "Missing non-optional "~sparam.kind.to!string~" parameter '"~(fieldname.length?fieldname:sparam.name)~"'.");
1087 			} else params[i] = v;
1088 		}
1089 
1090 		static if (isAuthenticated!(T, Func))
1091 			handleAuthorization!(T, Func, params)(auth_info);
1092 
1093 		void handleCors()
1094 		{
1095 			import std.algorithm : any;
1096 			import std.uni : sicmp;
1097 
1098 			if (req.method == HTTPMethod.OPTIONS)
1099 				return;
1100 			auto origin = "Origin" in req.headers;
1101 			if (origin is null)
1102 				return;
1103 
1104 			if (settings.allowedOrigins.length != 0 &&
1105 				!settings.allowedOrigins.any!(org => org.sicmp((*origin)) == 0))
1106 				return;
1107 
1108 			res.headers["Access-Control-Allow-Origin"] = *origin;
1109 			res.headers["Access-Control-Allow-Credentials"] = "true";
1110 		}
1111 		// Anti copy-paste
1112 		void returnHeaders()
1113 		{
1114 			handleCors();
1115 			foreach (i, P; PTypes) {
1116 				static if (sroute.parameters[i].isOut) {
1117 					static assert (sroute.parameters[i].kind == ParameterKind.header);
1118 					static if (isInstanceOf!(Nullable, typeof(params[i]))) {
1119 						if (!params[i].isNull)
1120 							res.headers[route.parameters[i].fieldName] = to!string(params[i]);
1121 					} else {
1122 						res.headers[route.parameters[i].fieldName] = to!string(params[i]);
1123 					}
1124 				}
1125 			}
1126 		}
1127 
1128 		try {
1129 			import vibe.internal.meta.funcattr;
1130 
1131 			static if (!__traits(compiles, () @safe { __traits(getMember, inst, Method)(params); }))
1132 				pragma(msg, "Non-@safe methods are deprecated in REST interfaces - Mark "~T.stringof~"."~Method~" as @safe.");
1133 
1134 			static if (is(RT == void)) {
1135 				() @trusted { __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period
1136 				returnHeaders();
1137 				res.writeVoidBody();
1138 			} else {
1139 				auto ret = () @trusted { return __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period
1140 
1141 				static if (!__traits(compiles, () @safe { evaluateOutputModifiers!Func(ret, req, res); } ()))
1142 					pragma(msg, "Non-@safe @after evaluators are deprecated - annotate @after evaluator function for "~T.stringof~"."~Method~" as @safe.");
1143 
1144 				ret = () @trusted { return evaluateOutputModifiers!Func(ret, req, res); } ();
1145 				returnHeaders();
1146 				debug res.writePrettyJsonBody(ret);
1147 				else res.writeJsonBody(ret);
1148 			}
1149 		} catch (HTTPStatusException e) {
1150 			if (res.headerWritten)
1151 				logDebug("Response already started when a HTTPStatusException was thrown. Client will not receive the proper error code (%s)!", e.status);
1152 			else {
1153 				returnHeaders();
1154 				res.writeJsonBody([ "statusMessage": e.msg ], e.status);
1155 			}
1156 		} catch (Exception e) {
1157 			// TODO: better error description!
1158 			logDebug("REST handler exception: %s", () @trusted { return e.toString(); } ());
1159 			if (res.headerWritten) logDebug("Response already started. Client will not receive an error code!");
1160 			else
1161 			{
1162 				returnHeaders();
1163 				debug res.writeJsonBody(
1164 						[ "statusMessage": e.msg, "statusDebugMessage": () @trusted { return sanitizeUTF8(cast(ubyte[])e.toString()); } () ],
1165 						HTTPStatus.internalServerError
1166 					);
1167 				else res.writeJsonBody(["statusMessage": e.msg], HTTPStatus.internalServerError);
1168 			}
1169 		}
1170 	}
1171 
1172 	return &handler;
1173 }
1174 
1175 /**
1176  * Generate an handler that will wrap the server's method
1177  *
1178  * This function returns an handler that handles the http OPTIONS method.
1179  *
1180  * It will return the ALLOW header with all the methods on this resource
1181  * And it will handle Preflight CORS.
1182  *
1183  * Params:
1184  *	routes = a range of Routes were each route has the same resource/URI
1185  *				just different method.
1186  *	settings = REST server configuration.
1187  *
1188  * Returns:
1189  *	A delegate suitable to use as an handler for an HTTP request.
1190  */
1191 private HTTPServerRequestDelegate optionsMethodHandler(RouteRange)(RouteRange routes, RestInterfaceSettings settings = null)
1192 {
1193 	import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
1194 	import std.algorithm : map, joiner, any;
1195 	import std.conv : text;
1196 	import std.array : array;
1197 	import vibe.http.common : httpMethodString, httpMethodFromString;
1198 	// NOTE: don't know what is better, to keep this in memory, or generate on each request
1199 	auto allow = routes.map!(r => r.method.httpMethodString).joiner(",").text();
1200 	auto methods = routes.map!(r => r.method).array();
1201 
1202 	void handlePreflightedCors(HTTPServerRequest req, HTTPServerResponse res, ref HTTPMethod[] methods, RestInterfaceSettings settings = null)
1203 	{
1204 		import std.algorithm : among;
1205 		import std.uni : sicmp;
1206 
1207 		auto origin = "Origin" in req.headers;
1208 		if (origin is null)
1209 			return;
1210 
1211 		if (settings !is null &&
1212 			settings.allowedOrigins.length != 0 &&
1213 			!settings.allowedOrigins.any!(org => org.sicmp((*origin)) == 0))
1214 			return;
1215 
1216 		auto method = "Access-Control-Request-Method" in req.headers;
1217 		if (method is null)
1218 			return;
1219 
1220 		auto httpMethod = httpMethodFromString(*method);
1221 
1222 		if (!methods.any!(m => m == httpMethod))
1223 			return;
1224 
1225 		res.headers["Access-Control-Allow-Origin"] = *origin;
1226 
1227 		// there is no way to know if the specific resource supports credentials
1228 		// (either cookies, HTTP authentication, or client-side SSL certificates),
1229 		// so we always assume it does
1230 		res.headers["Access-Control-Allow-Credentials"] = "true";
1231 		res.headers["Access-Control-Max-Age"] = "1728000";
1232 		res.headers["Access-Control-Allow-Methods"] = *method;
1233 
1234 		// we have no way to reliably determine what headers the resource allows
1235 		// so we simply copy whatever the client requested
1236 		if (auto headers = "Access-Control-Request-Headers" in req.headers)
1237 			res.headers["Access-Control-Allow-Headers"] = *headers;
1238 	}
1239 
1240 	void handler(HTTPServerRequest req, HTTPServerResponse res)
1241 	{
1242 		// since this is a OPTIONS request, we have to return the ALLOW headers to tell which methods we have
1243 		res.headers["Allow"] = allow;
1244 
1245 		// handle CORS preflighted requests
1246 		handlePreflightedCors(req,res,methods,settings);
1247 
1248 		// NOTE: besides just returning the allowed methods and handling CORS preflighted requests,
1249 		// this would be a nice place to describe what kind of resources are on this route,
1250 		// the params each accepts, the headers, etc... think WSDL but then for REST.
1251 		res.writeBody("");
1252 	}
1253 	return &handler;
1254 }
1255 
1256 private string generateRestClientMethods(I)()
1257 {
1258 	import std.array : join;
1259 	import std.string : format;
1260 	import std.traits : fullyQualifiedName, isInstanceOf;
1261 
1262 	alias Info = RestInterface!I;
1263 
1264 	string ret = q{
1265 		import vibe.internal.meta.codegen : CloneFunction;
1266 	};
1267 
1268 	// generate sub interface methods
1269 	foreach (i, SI; Info.SubInterfaceTypes) {
1270 		alias F = Info.SubInterfaceFunctions[i];
1271 		alias RT = ReturnType!F;
1272 		alias ParamNames = ParameterIdentifierTuple!F;
1273 		static if (ParamNames.length == 0) enum pnames = "";
1274 		else enum pnames = ", " ~ [ParamNames].join(", ");
1275 		static if (isInstanceOf!(Collection, RT)) {
1276 			ret ~= q{
1277 					mixin CloneFunction!(Info.SubInterfaceFunctions[%1$s], q{
1278 						return Collection!(%2$s)(m_subInterfaces[%1$s]%3$s);
1279 					});
1280 				}.format(i, fullyQualifiedName!SI, pnames);
1281 		} else {
1282 			ret ~= q{
1283 					mixin CloneFunction!(Info.SubInterfaceFunctions[%1$s], q{
1284 						return m_subInterfaces[%1$s];
1285 					});
1286 				}.format(i);
1287 		}
1288 	}
1289 
1290 	// generate route methods
1291 	foreach (i, F; Info.RouteFunctions) {
1292 		alias ParamNames = ParameterIdentifierTuple!F;
1293 		static if (ParamNames.length == 0) enum pnames = "";
1294 		else enum pnames = ", " ~ [ParamNames].join(", ");
1295 
1296 		ret ~= q{
1297 				mixin CloneFunction!(Info.RouteFunctions[%1$s], q{
1298 					return executeClientMethod!(I, %1$s%2$s)(*m_intf, m_requestFilter);
1299 				});
1300 			}.format(i, pnames);
1301 	}
1302 
1303 	return ret;
1304 }
1305 
1306 
1307 private auto executeClientMethod(I, size_t ridx, ARGS...)
1308 	(in ref RestInterface!I intf, void delegate(HTTPClientRequest) request_filter)
1309 {
1310 	import hb.web.internal.rest.common : ParameterKind;
1311 	import vibe.textfilter.urlencode : filterURLEncode, urlEncode;
1312 	import std.array : appender;
1313 
1314 	alias Info = RestInterface!I;
1315 	alias Func = Info.RouteFunctions[ridx];
1316 	alias RT = ReturnType!Func;
1317 	alias PTT = ParameterTypeTuple!Func;
1318 	enum sroute = Info.staticRoutes[ridx];
1319 	auto route = intf.routes[ridx];
1320 
1321 	InetHeaderMap headers;
1322 	InetHeaderMap reqhdrs;
1323 	InetHeaderMap opthdrs;
1324 
1325 	string url_prefix;
1326 
1327 	auto query = appender!string();
1328 	auto jsonBody = Json.emptyObject;
1329 	string body_;
1330 
1331 	enum bool hasSingleBodyParam = {
1332 		int i;
1333 		foreach (j, PT; PTT)
1334 			if (sroute.parameters[j].kind == ParameterKind.body_)
1335 				i++;
1336 
1337 		return i == 1;
1338 	}();
1339 
1340 	void addQueryParam(size_t i)(string name)
1341 	{
1342 		if (query.data.length) query.put('&');
1343 		query.filterURLEncode(name);
1344 		query.put("=");
1345 		static if (is(PT == Json))
1346 			query.filterURLEncode(ARGS[i].toString());
1347 		else // Note: CTFE triggers compiler bug here (think we are returning Json, not string).
1348 			query.filterURLEncode(toRestString(serializeToJson(ARGS[i])));
1349 	}
1350 
1351 	foreach (i, PT; PTT) {
1352 		enum sparam = sroute.parameters[i];
1353 		auto fieldname = route.parameters[i].fieldName;
1354 		static if (sparam.kind == ParameterKind.query) {
1355 			addQueryParam!i(fieldname);
1356 		} else static if (sparam.kind == ParameterKind.body_) {
1357 			// for @bodyParam("s") and by default the entire body should be serialized
1358 			if (sparam.fieldName == bodyParamWholeName || hasSingleBodyParam && sparam.fieldName.length == 0)
1359 				jsonBody = serializeToJson(ARGS[i]);
1360 			else
1361 				jsonBody[fieldname] = serializeToJson(ARGS[i]);
1362 		} else static if (sparam.kind == ParameterKind.header) {
1363 			// Don't send 'out' parameter, as they should be default init anyway and it might confuse some server
1364 			static if (sparam.isIn) {
1365 				static if (isInstanceOf!(Nullable, PT)) {
1366 					if (!ARGS[i].isNull)
1367 						headers[fieldname] = to!string(ARGS[i]);
1368 				} else headers[fieldname] = to!string(ARGS[i]);
1369 			}
1370 			static if (sparam.isOut) {
1371 				// Optional parameter
1372 				static if (isInstanceOf!(Nullable, PT)) {
1373 					opthdrs[fieldname] = null;
1374 				} else {
1375 					reqhdrs[fieldname] = null;
1376 				}
1377 			}
1378 		}
1379 	}
1380 
1381 	static if (sroute.method == HTTPMethod.GET) {
1382 		assert(jsonBody == Json.emptyObject, "GET request trying to send body parameters.");
1383 	} else {
1384 		debug body_ = jsonBody.toPrettyString();
1385 		else body_ = jsonBody.toString();
1386 	}
1387 
1388 	string url;
1389 	foreach (i, p; route.fullPathParts) {
1390 		if (p.isParameter) {
1391 			switch (p.text) {
1392 				foreach (j, PT; PTT) {
1393 					case sroute.parameters[j].name:
1394 						url ~= urlEncode(toRestString(serializeToJson(ARGS[j])));
1395 						goto sbrk;
1396 				}
1397 				default: url ~= ":" ~ p.text; break;
1398 			}
1399 			sbrk:;
1400 		} else url ~= p.text;
1401 	}
1402 
1403 	scope (exit) {
1404 		foreach (i, PT; PTT) {
1405 			enum sparam = sroute.parameters[i];
1406 			auto fieldname = route.parameters[i].fieldName;
1407 			static if (sparam.kind == ParameterKind.header) {
1408 				static if (sparam.isOut) {
1409 					static if (isInstanceOf!(Nullable, PT)) {
1410 						ARGS[i] = to!(TemplateArgsOf!PT)(
1411 							opthdrs.get(fieldname, null));
1412 					} else {
1413 						if (auto ptr = fieldname in reqhdrs)
1414 							ARGS[i] = to!PT(*ptr);
1415 					}
1416 				}
1417 			}
1418 		}
1419 	}
1420 
1421 	auto jret = request(URL(intf.baseURL), request_filter, sroute.method, url, headers, query.data, body_, reqhdrs, opthdrs, intf.settings.httpClientSettings);
1422 
1423 	static if (!is(RT == void))
1424 		return deserializeJson!RT(jret);
1425 }
1426 
1427 
1428 import vibe.http.client : HTTPClientRequest;
1429 /**
1430  * Perform a request to the interface using the given parameters.
1431  *
1432  * Params:
1433  * verb = Kind of request (See $(D HTTPMethod) enum).
1434  * name = Location to request. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
1435  *		it will be '/rejectedsoftware/vibe.d/issues'.
1436  * hdrs = The headers to send. Some field might be overriden (such as Content-Length). However, Content-Type will NOT be overriden.
1437  * query = The $(B encoded) query string. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
1438  *		it will be 'author%3ASantaClaus'.
1439  * body_ = The body to send, as a string. If a Content-Type is present in $(D hdrs), it will be used, otherwise it will default to
1440  *		the generic type "application/json".
1441  * reqReturnHdrs = A map of required return headers.
1442  *				   To avoid returning unused headers, nothing is written
1443  *				   to this structure unless there's an (usually empty)
1444  *				   entry (= the key exists) with the same key.
1445  *				   If any key present in `reqReturnHdrs` is not present
1446  *				   in the response, an Exception is thrown.
1447  * optReturnHdrs = A map of optional return headers.
1448  *				   This behaves almost as exactly as reqReturnHdrs,
1449  *				   except that non-existent key in the response will
1450  *				   not cause it to throw, but rather to set this entry
1451  *				   to 'null'.
1452  *
1453  * Returns:
1454  *     The Json object returned by the request
1455  */
1456 private Json request(URL base_url,
1457 	void delegate(HTTPClientRequest) request_filter, HTTPMethod verb,
1458 	string path, in ref InetHeaderMap hdrs, string query, string body_,
1459 	ref InetHeaderMap reqReturnHdrs, ref InetHeaderMap optReturnHdrs,
1460 	in HTTPClientSettings http_settings)
1461 {
1462 	import vibe.http.client : HTTPClientRequest, HTTPClientResponse, requestHTTP;
1463 	import vibe.http.common : HTTPStatusException, HTTPStatus, httpMethodString, httpStatusText;
1464 	import vibe.inet.url : Path;
1465 
1466 	URL url = base_url;
1467 	url.pathString = path;
1468 
1469 	if (query.length) url.queryString = query;
1470 
1471 	Json ret;
1472 
1473 	auto reqdg = (scope HTTPClientRequest req) {
1474 		req.method = verb;
1475 		foreach (k, v; hdrs)
1476 			req.headers[k] = v;
1477 
1478 		if (request_filter) request_filter(req);
1479 
1480 		if (body_ != "")
1481 			req.writeBody(cast(ubyte[])body_, hdrs.get("Content-Type", "application/json"));
1482 	};
1483 
1484 	auto resdg = (scope HTTPClientResponse res) {
1485 		if (!res.bodyReader.empty)
1486 			ret = res.readJson();
1487 
1488 		logDebug(
1489 			 "REST call: %s %s -> %d, %s",
1490 			 httpMethodString(verb),
1491 			 url.toString(),
1492 			 res.statusCode,
1493 			 ret.toString()
1494 			 );
1495 
1496 		// Get required headers - Don't throw yet
1497 		string[] missingKeys;
1498 		foreach (k, ref v; reqReturnHdrs)
1499 			if (auto ptr = k in res.headers)
1500 				v = (*ptr).idup;
1501 			else
1502 				missingKeys ~= k;
1503 
1504 		// Get optional headers
1505 		foreach (k, ref v; optReturnHdrs)
1506 			if (auto ptr = k in res.headers)
1507 				v = (*ptr).idup;
1508 			else
1509 				v = null;
1510 
1511 		if (missingKeys.length)
1512 			throw new Exception(
1513 				"REST interface mismatch: Missing required header field(s): "
1514 				~ missingKeys.to!string);
1515 
1516 
1517 		if (!isSuccessCode(cast(HTTPStatus)res.statusCode))
1518 			throw new RestException(res.statusCode, ret);
1519 	};
1520 
1521 	if (http_settings) requestHTTP(url, reqdg, resdg, http_settings);
1522 	else requestHTTP(url, reqdg, resdg);
1523 
1524 	return ret;
1525 }
1526 
1527 private {
1528 	import vibe.data.json;
1529 	import std.conv : to;
1530 
1531 	string toRestString(Json value)
1532 	{
1533 		switch (value.type) {
1534 			default: return value.toString();
1535 			case Json.Type.Bool: return value.get!bool ? "true" : "false";
1536 			case Json.Type.Int: return to!string(value.get!long);
1537 			case Json.Type.Float: return to!string(value.get!double);
1538 			case Json.Type.String: return value.get!string;
1539 		}
1540 	}
1541 
1542 	T fromRestString(T)(string value)
1543 	{
1544 		import std.conv : ConvException;
1545 		import hb.web.common : HTTPStatusException, HTTPStatus;
1546 		try {
1547 			static if (isInstanceOf!(Nullable, T)) return T(fromRestString!(typeof(T.init.get()))(value));
1548 			else static if (is(T == bool)) return value == "true";
1549 			else static if (is(T : int)) return to!T(value);
1550 			else static if (is(T : double)) return to!T(value); // FIXME: formattedWrite(dst, "%.16g", json.get!double);
1551 			else static if (is(string : T)) return value;
1552 			else static if (__traits(compiles, T.fromISOExtString("hello"))) return T.fromISOExtString(value);
1553 			else static if (__traits(compiles, T.fromString("hello"))) return T.fromString(value);
1554 			else return deserializeJson!T(parseJson(value));
1555 		} catch (ConvException e) {
1556 			throw new HTTPStatusException(HTTPStatus.badRequest, e.msg);
1557 		} catch (JSONException e) {
1558 			throw new HTTPStatusException(HTTPStatus.badRequest, e.msg);
1559 		}
1560 	}
1561 
1562 	// Converting from invalid JSON string to aggregate should throw bad request
1563 	unittest {
1564 		import hb.web.common : HTTPStatusException, HTTPStatus;
1565 
1566 		void assertHTTPStatus(E)(lazy E expression, HTTPStatus expectedStatus,
1567 			string file = __FILE__, size_t line = __LINE__)
1568 		{
1569 			import core.exception : AssertError;
1570 			import std.format : format;
1571 
1572 			try
1573 				expression();
1574 			catch (HTTPStatusException e)
1575 			{
1576 				if (e.status != expectedStatus)
1577 					throw new AssertError(format("assertHTTPStatus failed: " ~
1578 						"status expected %d but was %d", expectedStatus, e.status),
1579 						file, line);
1580 
1581 				return;
1582 			}
1583 
1584 			throw new AssertError("assertHTTPStatus failed: No " ~
1585 				"'HTTPStatusException' exception was thrown", file, line);
1586 		}
1587 
1588 		struct Foo { int bar; }
1589 		assertHTTPStatus(fromRestString!(Foo)("foo"), HTTPStatus.badRequest);
1590 	}
1591 }
1592 
1593 private string generateModuleImports(I)()
1594 {
1595 	if (!__ctfe)
1596 		assert (false);
1597 
1598 	import vibe.internal.meta.codegen : getRequiredImports;
1599 	import std.algorithm : map;
1600 	import std.array : join;
1601 
1602 	auto modules = getRequiredImports!I();
1603 	return join(map!(a => "static import " ~ a ~ ";")(modules), "\n");
1604 }
1605 
1606 version(unittest)
1607 {
1608 	private struct Aggregate { }
1609 	private interface Interface
1610 	{
1611 		Aggregate[] foo();
1612 	}
1613 }
1614 
1615 unittest
1616 {
1617 	enum imports = generateModuleImports!Interface;
1618 	static assert (imports == "static import hb.web.rest;");
1619 }
1620 
1621 // Check that the interface is valid. Every checks on the correctness of the
1622 // interface should be put in checkRestInterface, which allows to have consistent
1623 // errors in the server and client.
1624 package string getInterfaceValidationError(I)()
1625 out (result) { assert((result is null) == !result.length); }
1626 body {
1627 	import hb.web.internal.rest.common : ParameterKind;
1628 	import std.typetuple : TypeTuple;
1629 	import std.algorithm : strip;
1630 
1631 	// The hack parameter is to kill "Statement is not reachable" warnings.
1632 	string validateMethod(alias Func)(bool hack = true) {
1633 		import vibe.internal.meta.uda;
1634 		import std.string : format;
1635 
1636 		static assert(is(FunctionTypeOf!Func), "Internal error");
1637 
1638 		if (!__ctfe)
1639 			assert(false, "Internal error");
1640 
1641 		enum FuncId = (fullyQualifiedName!I~ "." ~ __traits(identifier, Func));
1642 		alias PT = ParameterTypeTuple!Func;
1643 		static if (!__traits(compiles, ParameterIdentifierTuple!Func)) {
1644 			if (hack) return "%s: A parameter has no name.".format(FuncId);
1645 			alias PN = TypeTuple!("-DummyInvalid-");
1646 		} else
1647 			alias PN = ParameterIdentifierTuple!Func;
1648 		alias WPAT = UDATuple!(WebParamAttribute, Func);
1649 
1650 		// Check if there is no orphan UDATuple (e.g. typo while writing the name of the parameter).
1651 		foreach (i, uda; WPAT) {
1652 			// Note: static foreach gets unrolled, generating multiple nested sub-scope.
1653 			// The spec / DMD doesn't like when you have the same symbol in those,
1654 			// leading to wrong codegen / wrong template being reused.
1655 			// That's why those templates need different names.
1656 			// See DMD bug #9748.
1657 			mixin(GenOrphan!(i).Decl);
1658 			// template CmpOrphan(string name) { enum CmpOrphan = (uda.identifier == name); }
1659 			static if (!anySatisfy!(mixin(GenOrphan!(i).Name), PN)) {
1660 				if (hack) return "%s: No parameter '%s' (referenced by attribute @%sParam)"
1661 					.format(FuncId, uda.identifier, uda.origin);
1662 			}
1663 		}
1664 
1665 		foreach (i, P; PT) {
1666 			static if (!PN[i].length)
1667 				if (hack) return "%s: Parameter %d has no name."
1668 					.format(FuncId, i);
1669 			// Check for multiple origins
1670 			static if (WPAT.length) {
1671 				// It's okay to reuse GenCmp, as the order of params won't change.
1672 				// It should/might not be reinstantiated by the compiler.
1673 				mixin(GenCmp!("Loop", i, PN[i]).Decl);
1674 				alias WPA = Filter!(mixin(GenCmp!("Loop", i, PN[i]).Name), WPAT);
1675 				static if (WPA.length > 1)
1676 					if (hack) return "%s: Parameter '%s' has multiple @*Param attributes on it."
1677 						.format(FuncId, PN[i]);
1678 			}
1679 		}
1680 
1681 		// Check for misplaced ref / out
1682 		alias PSC = ParameterStorageClass;
1683 		foreach (i, SC; ParameterStorageClassTuple!Func) {
1684 			static if (SC & PSC.out_ || SC & PSC.ref_) {
1685 				mixin(GenCmp!("Loop", i, PN[i]).Decl);
1686 				alias Attr
1687 					= Filter!(mixin(GenCmp!("Loop", i, PN[i]).Name), WPAT);
1688 				static if (Attr.length != 1) {
1689 					if (hack) return "%s: Parameter '%s' cannot be %s"
1690 						.format(FuncId, PN[i], SC & PSC.out_ ? "out" : "ref");
1691 				} else static if (Attr[0].origin != ParameterKind.header) {
1692 					if (hack) return "%s: %s parameter '%s' cannot be %s"
1693 						.format(FuncId, Attr[0].origin, PN[i],
1694 							SC & PSC.out_ ? "out" : "ref");
1695 				}
1696 			}
1697 		}
1698 
1699 		// Check for @path(":name")
1700 		enum pathAttr = findFirstUDA!(PathAttribute, Func);
1701 		static if (pathAttr.found) {
1702 			static if (!pathAttr.value.length) {
1703 				if (hack)
1704 					return "%s: Path is null or empty".format(FuncId);
1705 			} else {
1706 				import std.algorithm : canFind, splitter;
1707 				// splitter doesn't work with alias this ?
1708 				auto str = pathAttr.value.data;
1709 				if (str.canFind("//")) return "%s: Path '%s' contains empty entries.".format(FuncId, pathAttr.value);
1710 				str = str.strip('/');
1711 				if (!str.length) return null;
1712 				foreach (elem; str.splitter('/')) {
1713 					assert(elem.length, "Empty path entry not caught yet!?");
1714 
1715 					if (elem[0] == ':') {
1716 						// typeof(PN) is void when length is 0.
1717 						static if (!PN.length) {
1718 							if (hack)
1719 								return "%s: Path contains '%s', but no parameter '_%s' defined."
1720 									.format(FuncId, elem, elem[1..$]);
1721 						} else {
1722 							if (![PN].canFind("_"~elem[1..$]))
1723 								if (hack) return "%s: Path contains '%s', but no parameter '_%s' defined."
1724 									.format(FuncId, elem, elem[1..$]);
1725 							elem = elem[1..$];
1726 						}
1727 					}
1728 				}
1729 				// TODO: Check for validity of the subpath.
1730 			}
1731 		}
1732 		return null;
1733 	}
1734 
1735 	if (!__ctfe)
1736 		assert(false, "Internal error");
1737 	bool hack = true;
1738 	foreach (method; __traits(allMembers, I)) {
1739 		// WORKAROUND #1045 / @@BUG14375@@
1740 		static if (method.length != 0)
1741 			foreach (overload; MemberFunctionsTuple!(I, method)) {
1742 				static if (validateMethod!(overload)())
1743 					if (hack) return validateMethod!(overload)();
1744 			}
1745 	}
1746 	return null;
1747 }
1748 
1749 // Test detection of user typos (e.g., if the attribute is on a parameter that doesn't exist).
1750 unittest {
1751 	enum msg = "No parameter 'ath' (referenced by attribute @headerParam)";
1752 
1753 	interface ITypo {
1754 		@headerParam("ath", "Authorization") // mistyped parameter name
1755 		string getResponse(string auth);
1756 	}
1757 	enum err = getInterfaceValidationError!ITypo;
1758 	static assert(err !is null && stripTestIdent(err) == msg,
1759 		"Expected validation error for getResponse, got: "~stripTestIdent(err));
1760 }
1761 
1762 // Multiple origin for a parameter
1763 unittest {
1764 	enum msg = "Parameter 'arg1' has multiple @*Param attributes on it.";
1765 
1766 	interface IMultipleOrigin {
1767 		@headerParam("arg1", "Authorization") @bodyParam("arg1", "Authorization")
1768 		string getResponse(string arg1, int arg2);
1769 	}
1770 	enum err = getInterfaceValidationError!IMultipleOrigin;
1771 	static assert(err !is null && stripTestIdent(err) == msg, err);
1772 }
1773 
1774 // Missing parameter name
1775 unittest {
1776 	enum msg = "Parameter 0 has no name.";
1777 
1778 	interface IMissingName1 {
1779 		string getResponse(string = "troublemaker");
1780 	}
1781 	interface IMissingName2 {
1782 		string getResponse(string);
1783 	}
1784 	enum err1 = getInterfaceValidationError!IMissingName1;
1785 	static assert(err1 !is null && stripTestIdent(err1) == msg, err1);
1786 	enum err2 = getInterfaceValidationError!IMissingName2;
1787 	static assert(err2 !is null && stripTestIdent(err2) == msg, err2);
1788 }
1789 
1790 // Issue 949
1791 unittest {
1792 	enum msg = "Path contains ':owner', but no parameter '_owner' defined.";
1793 
1794 	@path("/repos/")
1795 	interface IGithubPR {
1796 		@path(":owner/:repo/pulls")
1797 		string getPullRequests(string owner, string repo);
1798 	}
1799 	enum err = getInterfaceValidationError!IGithubPR;
1800 	static assert(err !is null && stripTestIdent(err) == msg, err);
1801 }
1802 
1803 // Issue 1017
1804 unittest {
1805 	interface TestSuccess { @path("/") void test(); }
1806 	interface TestSuccess2 { @path("/test/") void test(); }
1807 	interface TestFail { @path("//") void test(); }
1808 	interface TestFail2 { @path("/test//it/") void test(); }
1809 	static assert(getInterfaceValidationError!TestSuccess is null);
1810 	static assert(getInterfaceValidationError!TestSuccess2 is null);
1811 	static assert(stripTestIdent(getInterfaceValidationError!TestFail)
1812 		== "Path '//' contains empty entries.");
1813 	static assert(stripTestIdent(getInterfaceValidationError!TestFail2)
1814 		== "Path '/test//it/' contains empty entries.");
1815 }
1816 
1817 unittest {
1818 	interface NullPath  { @path(null) void test(); }
1819 	interface ExplicitlyEmptyPath { @path("") void test(); }
1820 	static assert(stripTestIdent(getInterfaceValidationError!NullPath)
1821 				  == "Path is null or empty");
1822 	static assert(stripTestIdent(getInterfaceValidationError!ExplicitlyEmptyPath)
1823 				  == "Path is null or empty");
1824 
1825 	// Note: Implicitly empty path are valid:
1826 	// interface ImplicitlyEmptyPath { void get(); }
1827 }
1828 
1829 // Accept @headerParam ref / out parameters
1830 unittest {
1831 	interface HeaderRef {
1832 		@headerParam("auth", "auth")
1833 		string getData(ref string auth);
1834 	}
1835 	static assert(getInterfaceValidationError!HeaderRef is null,
1836 		      stripTestIdent(getInterfaceValidationError!HeaderRef));
1837 
1838 	interface HeaderOut {
1839 		@headerParam("auth", "auth")
1840 		void getData(out string auth);
1841 	}
1842 	static assert(getInterfaceValidationError!HeaderOut is null,
1843 		      stripTestIdent(getInterfaceValidationError!HeaderOut));
1844 }
1845 
1846 // Reject unattributed / @queryParam or @bodyParam ref / out parameters
1847 unittest {
1848 	interface QueryRef {
1849 		@queryParam("auth", "auth")
1850 		string getData(ref string auth);
1851 	}
1852 	static assert(stripTestIdent(getInterfaceValidationError!QueryRef)
1853 		== "query parameter 'auth' cannot be ref");
1854 
1855 	interface QueryOut {
1856 		@queryParam("auth", "auth")
1857 		void getData(out string auth);
1858 	}
1859 	static assert(stripTestIdent(getInterfaceValidationError!QueryOut)
1860 		== "query parameter 'auth' cannot be out");
1861 
1862 	interface BodyRef {
1863 		@bodyParam("auth", "auth")
1864 		string getData(ref string auth);
1865 	}
1866 	static assert(stripTestIdent(getInterfaceValidationError!BodyRef)
1867 		== "body_ parameter 'auth' cannot be ref");
1868 
1869 	interface BodyOut {
1870 		@bodyParam("auth", "auth")
1871 		void getData(out string auth);
1872 	}
1873 	static assert(stripTestIdent(getInterfaceValidationError!BodyOut)
1874 		== "body_ parameter 'auth' cannot be out");
1875 
1876 	// There's also the possibility of someone using an out unnamed
1877 	// parameter (don't ask me why), but this is catched as unnamed
1878 	// parameter, so we don't need to check it here.
1879 }
1880 
1881 private string stripTestIdent(string msg)
1882 @safe {
1883 	import std.string;
1884 	auto idx = msg.indexOf(": ");
1885 	return idx >= 0 ? msg[idx+2 .. $] : msg;
1886 }
1887 
1888 // Small helper for client code generation
1889 private string paramCTMap(string[string] params)
1890 @safe {
1891 	import std.array : appender, join;
1892 	if (!__ctfe)
1893 		assert (false, "This helper is only supposed to be called for codegen in RestClientInterface.");
1894 	auto app = appender!(string[]);
1895 	foreach (key, val; params) {
1896 		app ~= "\""~key~"\"";
1897 		app ~= val;
1898 	}
1899 	return app.data.join(", ");
1900 }
1901 
1902 package string stripTUnderscore(string name, RestInterfaceSettings settings)
1903 @safe {
1904 	if ((settings is null || settings.stripTrailingUnderscore)
1905 	    && name.endsWith("_"))
1906 		return name[0 .. $-1];
1907 	else return name;
1908 }
1909 
1910 // Workarounds @@DMD:9748@@, and maybe more
1911 package template GenCmp(string name, int id, string cmpTo) {
1912 	import std.string : format;
1913 	import std.conv : to;
1914 	enum Decl = q{
1915 		template %1$s(alias uda) {
1916 			enum %1$s = (uda.identifier == "%2$s");
1917 		}
1918 	}.format(Name, cmpTo);
1919 	enum Name = name~to!string(id);
1920 }
1921 
1922 // Ditto
1923 private template GenOrphan(int id) {
1924 	import std.string : format;
1925 	import std.conv : to;
1926 	enum Decl = q{
1927 		template %1$s(string name) {
1928 			enum %1$s = (uda.identifier == name);
1929 		}
1930 	}.format(Name);
1931 	enum Name = "OrphanCheck"~to!string(id);
1932 }
1933 
1934 // Workaround for issue #1045 / DMD bug 14375
1935 // Also, an example of policy-based design using this module.
1936 unittest {
1937 	import std.traits, std.typetuple;
1938 	import vibe.internal.meta.codegen;
1939 	import vibe.internal.meta.typetuple;
1940 	import hb.web.internal.rest.common : ParameterKind;
1941 
1942 	interface Policies {
1943 		@headerParam("auth", "Authorization")
1944 		string BasicAuth(string auth, ulong expiry);
1945 	}
1946 
1947 	@path("/keys/")
1948 	interface IKeys(alias AuthenticationPolicy = Policies.BasicAuth) {
1949 		static assert(is(FunctionTypeOf!AuthenticationPolicy == function),
1950 			      "Policies needs to be functions");
1951 		@path("/") @method(HTTPMethod.POST)
1952 		mixin CloneFunctionDecl!(AuthenticationPolicy, true, "create");
1953 	}
1954 
1955 	class KeysImpl : IKeys!() {
1956 	override:
1957 		string create(string auth, ulong expiry) {
1958 			return "4242-4242";
1959 		}
1960 	}
1961 
1962 	// Some sanity checks
1963         // Note: order is most likely implementation dependent.
1964 	// Good thing we only have one frontend...
1965 	alias WPA = WebParamAttribute;
1966 	static assert(Compare!(
1967 			      Group!(__traits(getAttributes, IKeys!().create)),
1968 			      Group!(PathAttribute("/"),
1969 				     MethodAttribute(HTTPMethod.POST),
1970 				     WPA(ParameterKind.header, "auth", "Authorization"))));
1971 
1972 	void register() {
1973 		auto router = new URLRouter();
1974 		router.registerRestInterface(new KeysImpl());
1975 	}
1976 
1977 	void query() {
1978 		auto client = new RestInterfaceClient!(IKeys!())("http://127.0.0.1:8080");
1979 		assert(client.create("Hello", 0) == "4242-4242");
1980 	}
1981 }