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