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 	Serialization:
152 		By default the return values of the interface methods are serialized
153 		as a JSON text and sent back to the REST client. To override this, you
154 		can use the @resultSerializer attribute
155 
156 		---
157 		struct TestStruct {int i;}
158 
159 		interface IService {
160 		@safe:
161 			@resultSerializer!(
162 				// output_stream implements OutputRange
163 				function (output_stream, test_struct) {
164 					output_stream ~= serializeToJsonString(test_struct);
165 				},
166 				// input_stream implements InputStream
167 				function (input_stream) {
168 					return deserializeJson!TestStruct(input_stream.readAllUTF8());
169 				},
170 				"application/json")()
171 			@resultSerializer!(
172 				// output_stream implements OutputRange
173 				function (output_stream, test_struct) {
174 					output_stream ~= test_struct.i.to!string();
175 				},
176 				// input_stream implements InputStream
177 				function (input_stream) {
178 					TestStruct test_struct;
179 					test_struct.i = input_stream.readAllUTF8().to!int();
180 					return test_struct;
181 				},
182 				"plain/text")()
183 			TestStruct getTest();
184 		}
185 
186 		class Service : IService {
187 		@safe:
188 			TestStruct getTest() {
189 				TestStruct test_struct = {42};
190 				return test_struct;
191 			}
192 		}
193 		---
194 
195 	Serialization_policies:
196 		You can customize the serialization of any type used by an interface
197 		by using serialization policies. The following example is using
198 		the `Base64ArrayPolicy`, which means if `X` contains any ubyte arrays,
199 		they will be serialized to their base64 encoding instead of
200 		their normal string representation (e.g. `"[1, 2, 255]"`).
201 
202 		---
203 		@serializationPolicy!(Base64ArrayPolicy)
204 		interface ITestBase64
205 		{
206 			@safe X getTest();
207 		}
208 		---
209 
210 	Parameters:
211 		Function parameters may be populated from the route, query string,
212 		request body, or headers. They may optionally affect the route URL itself.
213 
214 		By default, parameters are passed differently depending on the type of
215 		request (i.e., HTTP method). For GET and PUT, parameters are passed
216 		via the query string (`<route>?paramA=valueA[?paramB=...]`),
217 		while for POST and PATCH, they are passed via the request body
218 		as a JSON object.
219 
220 		The default behavior can be overridden using one of the following
221 		annotations, put as UDA on the relevant parameter:
222 
223 		$(UL
224 			$(LI `@viaHeader("field")`: Will	source the parameter on which it is
225 				applied from the request headers named "field". If the parameter
226 				is `ref`, it will also be set as a response header. Parameters
227 				declared as `out` will $(I only) be set as a response header.)
228 			$(LI `@viaQuery("field")`: Will source the parameter on which it is
229 				applied from a field named "field" of the query string.)
230 			$(LI `@viaBody("field")`: Will source the parameter on which it is
231 				applied from a field named "field" of the request body
232 				in JSON format, or, if no field is passed, will represent the
233 				whole body. Note that in the later case, there can be no other
234 				`viaBody` parameters.)
235 		)
236 
237 		----
238 		@path("/api/")
239 		interface APIRoot {
240 			// GET /api/header with 'Authorization' set
241 			string getHeader(@viaBody("Authorization") string param);
242 
243 			// GET /api/foo?param=...
244 			string getFoo(@viaQuery("param") int param);
245 
246 			// GET /api/body with body set to { "myFoo": {...} }
247 			string getBody(@viaBody("parameter") FooType myFoo);
248 
249 			// GET /api/full_body with body set to {...}
250 			string getFullBody(@viaBody() FooType myFoo);
251 		}
252 		----
253 
254 		Further, how function parameters are named may affect the route:
255 
256 		$(UL
257 			$(LI $(P Parameters with leading underscores (e.g. `_slug`) are also
258 				interpreted as a route component, but only in the presence of
259 				a `@path` UDA annotation. See Manual endpoint specification above.))
260 			$(LI $(P Other function parameters do not affect or come from the path
261 				 portion of the URL, and are are passed according to the default
262 				 rules above: query string for GET and PUT; request body JSON
263 				 for POST and PATCH.))
264 			$(LI $(P $(B Deprecated:) If the first parameter is named `id`, this is
265 				interpreted as a leading route component. For example,
266 				`getName(int id)` becomes `/:id/name`.)
267 				$(P Note that this style of parameter-based URL routing is
268 				different than in many other web frameworks, where instead
269 				this example would be routed as `/name/:id`.)
270 				$(P See `Collection` for the preferred way to represent object
271 				collections in REST interfaces))
272 		)
273 
274 
275 	Default_values:
276 		Parameters with default values behave as optional parameters. If one is
277 		set in the interface declaration of a method, the client can omit a
278 		value for the corresponding field in the request and the default value
279 		is used instead.
280 
281 		Note that if default parameters are not evaluable by CTFE, compilation
282 		may fail due to DMD bug #14369 (Vibe.d tracking issue: #1043).
283 
284 	Aggregates:
285 		When passing aggregates as parameters, those are serialized differently
286 		depending on the way they are passed, which may be especially important
287 		when interfacing with an existing RESTful API:
288 
289 		$(UL
290 			$(LI If the parameter is passed via the headers or the query, either
291 				implicitly or explicitly, the aggregate is serialized to JSON.
292 				If the JSON representation is a single string, the string value
293 				will be used verbatim. Otherwise the JSON representation will be
294 				used)
295 			$(LI If the parameter is passed via the body, the datastructure is
296 				serialized to JSON and set as a field of the main JSON object
297 				that is expected in the request body. Its field name equals the
298 				parameter name, unless an explicit `@bodyParam` annotation is
299 				used.)
300 		)
301 
302 	See_Also:
303 		To see how to implement the server side in detail, jump to
304 		`registerRestInterface`.
305 
306 		To see how to implement the client side in detail, jump to
307 		the `RestInterfaceClient` documentation.
308 
309 	Copyright: © 2012-2018 Sönke Ludwig
310 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
311 	Authors: Sönke Ludwig, Михаил Страшун, Mathias 'Geod24' Lang
312 */
313 module vibe.web.rest;
314 
315 public import vibe.web.common;
316 
317 import vibe.core.log;
318 import vibe.core.stream : InputStream, isInputStream, pipe;
319 import vibe.http.router : URLRouter;
320 import vibe.http.client : HTTPClientRequest, HTTPClientResponse, HTTPClientSettings;
321 import vibe.http.common : HTTPMethod, HTTPStatusException;
322 import vibe.http.server : HTTPServerRequestDelegate, HTTPServerRequest, HTTPServerResponse;
323 import vibe.http.status : HTTPStatus, isSuccessCode;
324 import vibe.internal.meta.uda;
325 import vibe.internal.meta.funcattr;
326 import vibe.inet.url;
327 import vibe.inet.message : InetHeaderMap;
328 import vibe.web.internal.rest.common : RestInterface, Route, SubInterfaceType;
329 import vibe.web.auth : AuthInfo, handleAuthentication, handleAuthorization, isAuthenticated;
330 
331 import std.algorithm : count, startsWith, endsWith, sort, splitter;
332 import std.array : appender, split;
333 import std.meta : AliasSeq;
334 import std.range : isOutputRange;
335 import std.string : strip, indexOf, toLower;
336 import std.typecons : No, Nullable, Yes;
337 import std.typetuple : anySatisfy, Filter;
338 import std.traits;
339 
340 /** Registers a server matching a certain REST interface.
341 
342 	Servers are implementation of the D interface that defines the RESTful API.
343 	The methods of this class are invoked by the code that is generated for
344 	each endpoint of the API, with parameters and return values being translated
345 	according to the rules documented in the `vibe.web.rest` module
346 	documentation.
347 
348 	A basic 'hello world' API can be defined as follows:
349 	----
350 	@path("/api/")
351 	interface APIRoot {
352 	    string get();
353 	}
354 
355 	class API : APIRoot {
356 	    override string get() { return "Hello, World"; }
357 	}
358 
359 	void main()
360 	{
361 	    // -- Where the magic happens --
362 	    router.registerRestInterface(new API());
363 	    // GET http://127.0.0.1:8080/api/ and 'Hello, World' will be replied
364 	    listenHTTP("127.0.0.1:8080", router);
365 
366 	    runApplication();
367 	}
368 	----
369 
370 	As can be seen here, the RESTful logic can be written inside the class
371 	without any concern for the actual HTTP representation.
372 
373 	Return_value:
374 		By default, all methods that return a value send a 200 (OK) status code,
375 		or 204 if no value is being returned for the body.
376 
377 	Non-success:
378 		In the cases where an error code should be signaled to the user, a
379 		`HTTPStatusException` can be thrown from within the method. It will be
380 		turned into a JSON object that has a `statusMessage` field with the
381 		exception message. In case of other exception types being thrown, the
382 		status code will be set to 500 (internal server error), the
383 		`statusMessage` field will again contain the exception's message, and,
384 		in debug mode, an additional `statusDebugMessage` field will be set to
385 		the complete string representation of the exception
386 		(`Exception.toString`), which usually contains a stack trace useful for
387 		debugging.
388 
389 	Returning_data:
390 		To return data, it is possible to either use the return value, which
391 		will be sent as the response body, or individual `ref`/`out` parameters
392 		can be used. The way they are represented in the response can be
393 		customized by adding `@viaBody`/`@viaHeader` annotations on the
394 		parameter declaration of the method within the interface.
395 
396 		In case of errors, any `@viaHeader` parameters are guaranteed to
397 		be set in the response, so that applications such as HTTP basic
398 		authentication can be implemented.
399 
400 	Template_Params:
401 	    TImpl = Either an interface type, or a class that derives from an
402 			      interface. If the class derives from multiple interfaces,
403 	            the first one will be assumed to be the API description
404 	            and a warning will be issued.
405 
406 	Params:
407 	    router   = The HTTP router on which the interface will be registered
408 	    instance = Server instance to use
409 	    settings = Additional settings, such as the `MethodStyle` or the prefix
410 
411 	See_Also:
412 		`RestInterfaceClient` class for an automated way to generate the
413 		matching client-side implementation.
414 */
415 URLRouter registerRestInterface(TImpl)(URLRouter router, TImpl instance, RestInterfaceSettings settings = null)
416 {
417 	import std.algorithm : filter, map, all;
418 	import std.array : array;
419 	import std.range : front;
420 	import vibe.web.internal.rest.common : ParameterKind;
421 
422 	auto intf = RestInterface!TImpl(settings, false);
423 
424 	foreach (i, ovrld; intf.SubInterfaceFunctions) {
425 		enum fname = __traits(identifier, intf.SubInterfaceFunctions[i]);
426 		alias R = ReturnType!ovrld;
427 
428 		static if (isInstanceOf!(Collection, R)) {
429 			auto ret = __traits(getMember, instance, fname)(R.ParentIDs.init);
430 			router.registerRestInterface!(R.Interface)(ret.m_interface, intf.subInterfaces[i].settings);
431 		} else {
432 			auto ret = __traits(getMember, instance, fname)();
433 			router.registerRestInterface!R(ret, intf.subInterfaces[i].settings);
434 		}
435 	}
436 
437 
438 	foreach (i, func; intf.RouteFunctions) {
439 		auto route = intf.routes[i];
440 
441 		// normal handler
442 		auto handler = jsonMethodHandler!(func, i)(instance, intf);
443 
444 		auto diagparams = route.parameters.filter!(p => p.kind != ParameterKind.internal).map!(p => p.fieldName).array;
445 		logDiagnostic("REST route: %s %s %s", route.method, route.fullPattern, diagparams);
446 		router.match(route.method, route.fullPattern, handler);
447 	}
448 
449 	// here we filter our already existing OPTIONS routes, so we don't overwrite whenever the user explicitly made his own OPTIONS route
450 	auto routesGroupedByPattern = intf.getRoutesGroupedByPattern.filter!(rs => rs.all!(r => r.method != HTTPMethod.OPTIONS));
451 
452 	foreach(routes; routesGroupedByPattern){
453 		auto route = routes.front;
454 		auto handler = optionsMethodHandler(routes, settings);
455 
456 		auto diagparams = route.parameters.filter!(p => p.kind != ParameterKind.internal).map!(p => p.fieldName).array;
457 		logDiagnostic("REST route: %s %s %s", HTTPMethod.OPTIONS, route.fullPattern, diagparams);
458 		router.match(HTTPMethod.OPTIONS, route.fullPattern, handler);
459 	}
460 	return router;
461 }
462 
463 /// ditto
464 URLRouter registerRestInterface(TImpl)(URLRouter router, TImpl instance, MethodStyle style)
465 {
466 	return registerRestInterface(router, instance, "/", style);
467 }
468 
469 /// ditto
470 URLRouter registerRestInterface(TImpl)(URLRouter router, TImpl instance, string url_prefix,
471 	MethodStyle style = MethodStyle.lowerUnderscored)
472 {
473 	auto settings = new RestInterfaceSettings;
474 	if (!url_prefix.startsWith("/")) url_prefix = "/"~url_prefix;
475 	settings.baseURL = URL("http://127.0.0.1"~url_prefix);
476 	settings.methodStyle = style;
477 	return registerRestInterface(router, instance, settings);
478 }
479 
480 
481 /**
482 	This is a very limited example of REST interface features. Please refer to
483 	the "rest" project in the "examples" folder for a full overview.
484 
485 	All details related to HTTP are inferred from the interface declaration.
486 */
487 @safe unittest
488 {
489 	@path("/")
490 	interface IMyAPI
491 	{
492 		@safe:
493 		// GET /api/greeting
494 		@property string greeting();
495 
496 		// PUT /api/greeting
497 		@property void greeting(string text);
498 
499 		// POST /api/users
500 		@path("/users")
501 		void addNewUser(string name);
502 
503 		// GET /api/users
504 		@property string[] users();
505 
506 		// GET /api/:id/name
507 		string getName(int id);
508 
509 		// GET /some_custom_json
510 		Json getSomeCustomJson();
511 	}
512 
513 	// vibe.d takes care of all JSON encoding/decoding
514 	// and actual API implementation can work directly
515 	// with native types
516 
517 	class API : IMyAPI
518 	{
519 		private {
520 			string m_greeting;
521 			string[] m_users;
522 		}
523 
524 		@property string greeting() { return m_greeting; }
525 		@property void greeting(string text) { m_greeting = text; }
526 
527 		void addNewUser(string name) { m_users ~= name; }
528 
529 		@property string[] users() { return m_users; }
530 
531 		string getName(int id) { return m_users[id]; }
532 
533 		Json getSomeCustomJson()
534 		{
535 			Json ret = Json.emptyObject;
536 			ret["somefield"] = "Hello, World!";
537 			return ret;
538 		}
539 	}
540 
541 	// actual usage, this is usually done in app.d module
542 	// constructor
543 
544 	void static_this()
545 	{
546 		import vibe.http.server, vibe.http.router;
547 
548 		auto router = new URLRouter;
549 		router.registerRestInterface(new API());
550 		listenHTTP(new HTTPServerSettings(), router);
551 	}
552 }
553 
554 
555 /**
556 	Returns a HTTP handler delegate that serves a JavaScript REST client.
557 */
558 HTTPServerRequestDelegate serveRestJSClient(I)(RestInterfaceSettings settings)
559 	if (is(I == interface))
560 {
561 	import std.datetime.systime : SysTime;
562 	import std.array : appender;
563 
564 	import vibe.http.fileserver : ETag, handleCache;
565 
566 	auto app = appender!string();
567 	generateRestJSClient!I(app, settings);
568 	ETag tag = ETag.md5(No.weak, app.data);
569 
570 	void serve(HTTPServerRequest req, HTTPServerResponse res)
571 	{
572 		if (handleCache(req, res, tag, SysTime.init, "public"))
573 			return;
574 
575 		res.writeBody(app.data, "application/javascript; charset=UTF-8");
576 	}
577 
578 	return &serve;
579 }
580 /// ditto
581 HTTPServerRequestDelegate serveRestJSClient(I)(URL base_url)
582 {
583 	auto settings = new RestInterfaceSettings;
584 	settings.baseURL = base_url;
585 	return serveRestJSClient!I(settings);
586 }
587 /// ditto
588 HTTPServerRequestDelegate serveRestJSClient(I)(string base_url)
589 {
590 	auto settings = new RestInterfaceSettings;
591 	settings.baseURL = URL(base_url);
592 	return serveRestJSClient!I(settings);
593 }
594 /// ditto
595 HTTPServerRequestDelegate serveRestJSClient(I)()
596 {
597 	auto settings = new RestInterfaceSettings;
598 	return serveRestJSClient!I(settings);
599 }
600 
601 ///
602 unittest {
603 	import vibe.http.server;
604 
605 	interface MyAPI {
606 		string getFoo();
607 		void postBar(string param);
608 	}
609 
610 	void test()
611 	{
612 		auto restsettings = new RestInterfaceSettings;
613 		restsettings.baseURL = URL("http://api.example.org/");
614 
615 		auto router = new URLRouter;
616 		router.get("/myapi.js", serveRestJSClient!MyAPI(restsettings));
617 		//router.get("/myapi.js", serveRestJSClient!MyAPI(URL("http://api.example.org/")));
618 		//router.get("/myapi.js", serveRestJSClient!MyAPI("http://api.example.org/"));
619 		//router.get("/myapi.js", serveRestJSClient!MyAPI()); // if want to request to self server
620 		//router.get("/", staticTemplate!"index.dt");
621 
622 		listenHTTP(new HTTPServerSettings, router);
623 	}
624 
625 	/*
626 		index.dt:
627 		html
628 			head
629 				title JS REST client test
630 				script(src="myapi.js")
631 			body
632 				button(onclick="MyAPI.postBar('hello');")
633 	*/
634 }
635 
636 
637 /**
638 	Generates JavaScript code to access a REST interface from the browser.
639 */
640 void generateRestJSClient(I, R)(ref R output, RestInterfaceSettings settings = null)
641 	if (is(I == interface) && isOutputRange!(R, char))
642 {
643 	import vibe.web.internal.rest.jsclient : generateInterface, JSRestClientSettings;
644 	auto jsgenset = new JSRestClientSettings;
645 	output.generateInterface!I(settings, jsgenset, true);
646 }
647 
648 /// Writes a JavaScript REST client to a local .js file.
649 unittest {
650 	import vibe.core.file;
651 
652 	interface MyAPI {
653 		void getFoo();
654 		void postBar(string param);
655 	}
656 
657 	void generateJSClientImpl()
658 	{
659 		import std.array : appender;
660 
661 		auto app = appender!string;
662 		auto settings = new RestInterfaceSettings;
663 		settings.baseURL = URL("http://localhost/");
664 		generateRestJSClient!MyAPI(app, settings);
665 	}
666 
667 	generateJSClientImpl();
668 }
669 
670 
671 /**
672 	Implements the given interface by forwarding all public methods to a REST server.
673 
674 	The server must talk the same protocol as registerRestInterface() generates. Be sure to set
675 	the matching method style for this. The RestInterfaceClient class will derive from the
676 	interface that is passed as a template argument. It can be used as a drop-in replacement
677 	of the real implementation of the API this way.
678 
679 	Non-success:
680 		If a request failed, timed out, or the server returned an non-success status code,
681 		an `vibe.web.common.RestException` will be thrown.
682 */
683 class RestInterfaceClient(I) : I
684 {
685 	import std.typetuple : staticMap;
686 
687 	private alias Info = RestInterface!I;
688 
689 	//pragma(msg, "imports for "~I.stringof~":");
690 	//pragma(msg, generateModuleImports!(I)());
691 	mixin(generateModuleImports!I());
692 
693 	private {
694 		// storing this struct directly causes a segfault when built with
695 		// LDC 0.15.x, so we are using a pointer here:
696 		RestInterface!I* m_intf;
697 		RequestFilter m_requestFilter;
698 		RequestBodyFilter m_requestBodyFilter;
699 		staticMap!(RestInterfaceClient, Info.SubInterfaceTypes) m_subInterfaces;
700 	}
701 
702 	alias RequestFilter = void delegate(HTTPClientRequest req) @safe;
703 
704 	alias RequestBodyFilter = void delegate(HTTPClientRequest req, scope InputStream body_contents) @safe;
705 
706 	/**
707 		Creates a new REST client implementation of $(D I).
708 	*/
709 	this(RestInterfaceSettings settings)
710 	{
711 		m_intf = new Info(settings, true);
712 
713 		foreach (i, SI; Info.SubInterfaceTypes)
714 			m_subInterfaces[i] = new RestInterfaceClient!SI(m_intf.subInterfaces[i].settings);
715 	}
716 
717 	/// ditto
718 	this(string base_url, MethodStyle style = MethodStyle.lowerUnderscored)
719 	{
720 		this(URL(base_url), style);
721 	}
722 
723 	/// ditto
724 	this(URL base_url, MethodStyle style = MethodStyle.lowerUnderscored)
725 	{
726 		scope settings = new RestInterfaceSettings;
727 		settings.baseURL = base_url;
728 		settings.methodStyle = style;
729 		this(settings);
730 	}
731 
732 	/**
733 		An optional request filter that allows to modify each request before it is made.
734 	*/
735 	final @property RequestFilter requestFilter()
736 	{
737 		return m_requestFilter;
738 	}
739 	/// ditto
740 	final @property void requestFilter(RequestFilter v)
741 	{
742 		m_requestFilter = v;
743 		foreach (i, SI; Info.SubInterfaceTypes)
744 			m_subInterfaces[i].requestFilter = v;
745 	}
746 	/// ditto
747 	final @property void requestFilter(void delegate(HTTPClientRequest req) v)
748 	{
749 		this.requestFilter = cast(RequestFilter)v;
750 	}
751 
752 	/** Optional request filter with access to the request body.
753 
754 		This callback allows to modify the request headers depending on the
755 		contents of the body.
756 	*/
757 	final @property void requestBodyFilter(RequestBodyFilter del)
758 	{
759 		m_requestBodyFilter = del;
760 	}
761 	/// ditto
762 	final @property RequestBodyFilter requestBodyFilter()
763 	{
764 		return m_requestBodyFilter;
765 	}
766 
767 	//pragma(msg, "restinterface:");
768 	mixin(generateRestClientMethods!I());
769 
770 	protected {
771 		import vibe.data.json : Json;
772 		import vibe.textfilter.urlencode;
773 
774 		/**
775 		 * Perform a request to the interface using the given parameters.
776 		 *
777 		 * Params:
778 		 * verb = Kind of request (See $(D HTTPMethod) enum).
779 		 * name = Location to request. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
780 		 *		it will be '/rejectedsoftware/vibe.d/issues'.
781 		 * hdrs = The headers to send. Some field might be overriden (such as Content-Length). However, Content-Type will NOT be overriden.
782 		 * query = The $(B encoded) query string. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
783 		 *		it will be 'author%3ASantaClaus'.
784 		 * 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
785 		 *		the generic type "application/json".
786 		 * reqReturnHdrs = A map of required return headers.
787 		 *				   To avoid returning unused headers, nothing is written
788 		 *				   to this structure unless there's an (usually empty)
789 		 *				   entry (= the key exists) with the same key.
790 		 *				   If any key present in `reqReturnHdrs` is not present
791 		 *				   in the response, an Exception is thrown.
792 		 * optReturnHdrs = A map of optional return headers.
793 		 *				   This behaves almost as exactly as reqReturnHdrs,
794 		 *				   except that non-existent key in the response will
795 		 *				   not cause it to throw, but rather to set this entry
796 		 *				   to 'null'.
797 		 *
798 		 * Returns:
799 		 *     The Json object returned by the request
800 		 */
801 		Json request(HTTPMethod verb, string name,
802 					 const scope ref InetHeaderMap hdrs, string query, string body_,
803 					 ref InetHeaderMap reqReturnHdrs,
804 					 ref InetHeaderMap optReturnHdrs) const
805 		{
806 			auto path = URL(m_intf.baseURL).pathString;
807 
808 			if (name.length)
809 			{
810 				if (path.length && path[$ - 1] == '/' && name[0] == '/')
811 					path ~= name[1 .. $];
812 				else if (path.length && path[$ - 1] == '/' || name[0] == '/')
813 					path ~= name;
814 				else
815 					path ~= '/' ~ name;
816 			}
817 
818 			auto httpsettings = m_intf.settings.httpClientSettings;
819 
820 			auto http_resp = .request(URL(m_intf.baseURL), m_requestFilter,
821 				m_requestBodyFilter, verb, path,
822 				hdrs, query, body_, reqReturnHdrs, optReturnHdrs, httpsettings);
823 			scope(exit) http_resp.dropBody();
824 
825 			return http_resp.readJson();
826 		}
827 	}
828 }
829 
830 ///
831 unittest
832 {
833 	interface IMyApi
834 	{
835 		// GET /status
836 		string getStatus();
837 
838 		// GET /greeting
839 		@property string greeting();
840 		// PUT /greeting
841 		@property void greeting(string text);
842 
843 		// POST /new_user
844 		void addNewUser(string name);
845 		// GET /users
846 		@property string[] users();
847 		// GET /:id/name
848 		string getName(int id);
849 
850 		Json getSomeCustomJson();
851 	}
852 
853 	void test()
854 	{
855 		auto api = new RestInterfaceClient!IMyApi("http://127.0.0.1/api/");
856 
857 		logInfo("Status: %s", api.getStatus());
858 		api.greeting = "Hello, World!";
859 		logInfo("Greeting message: %s", api.greeting);
860 		api.addNewUser("Peter");
861 		api.addNewUser("Igor");
862 		logInfo("Users: %s", api.users);
863 		logInfo("First user name: %s", api.getName(0));
864 	}
865 }
866 
867 
868 /**
869 	Encapsulates settings used to customize the generated REST interface.
870 */
871 class RestInterfaceSettings {
872 	/** The public URL below which the REST interface is registered.
873 	*/
874 	URL baseURL;
875 
876 	/** List of allowed origins for CORS
877 
878 		Empty list is interpreted as allowing all origins (e.g. *)
879 	*/
880 	string[] allowedOrigins;
881 
882 	/** Naming convention used for the generated URLs.
883 	*/
884 	MethodStyle methodStyle = MethodStyle.lowerUnderscored;
885 
886     /** The content type the client would like to receive the data back
887 	*/
888 	string content_type = "application/json";
889 
890 	/** Ignores a trailing underscore in method and function names.
891 
892 		With this setting set to $(D true), it's possible to use names in the
893 		REST interface that are reserved words in D.
894 	*/
895 	bool stripTrailingUnderscore = true;
896 
897 	/// Overrides the default HTTP client settings used by the `RestInterfaceClient`.
898 	HTTPClientSettings httpClientSettings;
899 
900 	/** Optional handler used to render custom replies in case of errors.
901 
902 		The handler needs to set the response status code to the provided
903 		`RestErrorInformation.statusCode` value and can then write a custom
904 		response body.
905 
906 		Note that the REST interface generator by default handles any exceptions thrown
907 		during request handling and sents a JSON response with the error message. The
908 		low level `HTTPServerSettings.errorPageHandler` is not invoked.
909 
910 		If `errorHandler` is not set, a JSON object with a single field "statusMessage"
911 		will be sent. In debug builds, there may also be an additional
912 		"statusDebugMessage" field that contains the full exception text, including a
913 		possible stack trace.
914 	*/
915 	RestErrorHandler errorHandler;
916 
917 	@property RestInterfaceSettings dup()
918 	const @safe {
919 		auto ret = new RestInterfaceSettings;
920 		ret.baseURL = this.baseURL;
921 		ret.methodStyle = this.methodStyle;
922 		ret.stripTrailingUnderscore = this.stripTrailingUnderscore;
923 		ret.allowedOrigins = this.allowedOrigins.dup;
924 		ret.content_type = this.content_type.dup;
925 		ret.errorHandler = this.errorHandler;
926 		if (this.httpClientSettings) {
927 			ret.httpClientSettings = this.httpClientSettings.dup;
928 		}
929 		return ret;
930 	}
931 }
932 
933 /** Type of the optional handler used to render custom replies in case of errors.
934 
935 	The error handler for a REST interface can be set via
936 	`RestInterfaceSettings.errorHandler`.
937  */
938 alias RestErrorHandler = void delegate(HTTPServerRequest, HTTPServerResponse, RestErrorInformation error) @safe;
939 
940 /** Contains detailed informations about the error
941 
942 	Used by the error handler (`RestInterfaceSettings.errorHandler`) to return
943 	the correct information(s) to clients.
944  */
945 struct RestErrorInformation {
946 	/// The status code that the handler should send in the reply
947 	HTTPStatus statusCode;
948 
949 	/** If triggered by an exception, this contains the catched exception
950 		object.
951 	*/
952 	Exception exception;
953 
954 	private this(Exception e, HTTPStatus default_status)
955 	@safe {
956 		this.exception = e;
957 
958 		if (auto he = cast(HTTPStatusException)e) {
959 			this.statusCode = cast(HTTPStatus)he.status;
960 		} else {
961 			this.statusCode = default_status;
962 		}
963 	}
964 }
965 
966 
967 /**
968 	Models REST collection interfaces using natural D syntax.
969 
970 	Use this type as the return value of a REST interface getter method/property
971 	to model a collection of objects. `opIndex` is used to make the individual
972 	entries accessible using the `[index]` syntax. Nested collections are
973 	supported.
974 
975 	The interface `I` needs to define a struct named `CollectionIndices`. The
976 	members of this struct denote the types and names of the indexes that lead
977 	to a particular resource. If a collection is nested within another
978 	collection, the order of these members must match the nesting order
979 	(outermost first).
980 
981 	The parameter list of all of `I`'s methods must begin with all but the last
982 	entry in `CollectionIndices`. Methods that also match the last entry will be
983 	considered methods of a collection item (`collection[index].method()`),
984 	wheres all other methods will be considered methods of the collection
985 	itself (`collection.method()`).
986 
987 	The name of the index parameters affects the default path of a method's
988 	route. Normal parameter names will be subject to the same rules as usual
989 	routes (see `registerRestInterface`) and will be mapped to query or form
990 	parameters at the protocol level. Names starting with an underscore will
991 	instead be mapped to path placeholders. For example,
992 	`void getName(int __item_id)` will be mapped to a GET request to the
993 	path `":item_id/name"`.
994 */
995 struct Collection(I)
996 	if (is(I == interface))
997 {
998 	static assert(is(I.CollectionIndices == struct), "Collection interfaces must define a CollectionIndices struct.");
999 
1000 	alias Interface = I;
1001 	alias AllIDs = AliasSeq!(typeof(I.CollectionIndices.tupleof));
1002 	alias AllIDNames = FieldNameTuple!(I.CollectionIndices);
1003 	static assert(AllIDs.length >= 1, I.stringof~".CollectionIndices must define at least one member.");
1004 	static assert(AllIDNames.length == AllIDs.length);
1005 	alias ItemID = AllIDs[$-1];
1006 	alias ParentIDs = AllIDs[0 .. $-1];
1007 	alias ParentIDNames = AllIDNames[0 .. $-1];
1008 
1009 	private {
1010 		I m_interface;
1011 		ParentIDs m_parentIDs;
1012 	}
1013 
1014 	/** Constructs a new collection instance that is tied to a particular
1015 		parent collection entry.
1016 
1017 		Params:
1018 			api = The target interface imstance to be mapped as a collection
1019 			pids = The indexes of all collections in which this collection is
1020 				nested (if any)
1021 	*/
1022 	this(I api, ParentIDs pids)
1023 	{
1024 		m_interface = api;
1025 		m_parentIDs = pids;
1026 	}
1027 
1028 	static struct Item {
1029 		private {
1030 			I m_interface;
1031 			AllIDs m_id;
1032 		}
1033 
1034 		this(I api, AllIDs id)
1035 		{
1036 			m_interface = api;
1037 			m_id = id;
1038 		}
1039 
1040 		// forward all item methods
1041 		mixin(() {
1042 			string ret;
1043 			foreach (m; __traits(allMembers, I)) {
1044 				foreach (ovrld; MemberFunctionsTuple!(I, m)) {
1045 					alias PT = ParameterTypeTuple!ovrld;
1046 					static if (matchesAllIDs!ovrld)
1047 						ret ~= "auto "~m~"(ARGS...)(ARGS args) { return m_interface."~m~"(m_id, args); }\n";
1048 				}
1049 			}
1050 			return ret;
1051 		} ());
1052 	}
1053 
1054 	// Note: the example causes a recursive template instantiation if done as a documented unit test:
1055 	/** Accesses a single collection entry.
1056 
1057 		Example:
1058 		---
1059 		interface IMain {
1060 			@property Collection!IItem items();
1061 		}
1062 
1063 		interface IItem {
1064 			struct CollectionIndices {
1065 				int _itemID;
1066 			}
1067 
1068 			@method(HTTPMethod.GET)
1069 			string name(int _itemID);
1070 		}
1071 
1072 		void test(IMain main)
1073 		{
1074 			auto item_name = main.items[23].name; // equivalent to IItem.name(23)
1075 		}
1076 		---
1077 	*/
1078 	Item opIndex(ItemID id)
1079 	{
1080 		return Item(m_interface, m_parentIDs, id);
1081 	}
1082 
1083 	// forward all non-item methods
1084 	mixin(() {
1085 		string ret;
1086 		foreach (m; __traits(allMembers, I)) {
1087 			foreach (ovrld; MemberFunctionsTuple!(I, m)) {
1088 				alias PT = ParameterTypeTuple!ovrld;
1089 				static if (!matchesAllIDs!ovrld && !hasUDA!(ovrld, NoRouteAttribute)) {
1090 					static assert(matchesParentIDs!ovrld,
1091 						"Collection methods must take all parent IDs as the first parameters."~PT.stringof~"   "~ParentIDs.stringof);
1092 					ret ~= "auto "~m~"(ARGS...)(ARGS args) { return m_interface."~m~"(m_parentIDs, args); }\n";
1093 				}
1094 			}
1095 		}
1096 		return ret;
1097 	} ());
1098 
1099 	private template matchesParentIDs(alias func) {
1100 		static if (is(ParameterTypeTuple!func[0 .. ParentIDs.length] == ParentIDs)) {
1101 			static if (ParentIDNames.length == 0) enum matchesParentIDs = true;
1102 			else static if (ParameterIdentifierTuple!func[0 .. ParentIDNames.length] == ParentIDNames)
1103 				enum matchesParentIDs = true;
1104 			else enum matchesParentIDs = false;
1105 		} else enum matchesParentIDs = false;
1106 	}
1107 
1108 	private template matchesAllIDs(alias func) {
1109 		static if (is(ParameterTypeTuple!func[0 .. AllIDs.length] == AllIDs)) {
1110 			static if (ParameterIdentifierTuple!func[0 .. AllIDNames.length] == AllIDNames)
1111 				enum matchesAllIDs = true;
1112 			else enum matchesAllIDs = false;
1113 		} else enum matchesAllIDs = false;
1114 	}
1115 }
1116 
1117 /// Model two nested collections using path based indexes
1118 unittest {
1119 	//
1120 	// API definition
1121 	//
1122 	interface SubItemAPI {
1123 		// Define the index path that leads to a sub item
1124 		struct CollectionIndices {
1125 			// The ID of the base item. This must match the definition in
1126 			// ItemAPI.CollectionIndices
1127 			string _item;
1128 			// The index if the sub item
1129 			int _index;
1130 		}
1131 
1132 		// GET /items/:item/subItems/length
1133 		@property int length(string _item);
1134 
1135 		// GET /items/:item/subItems/:index/squared_position
1136 		int getSquaredPosition(string _item, int _index);
1137 	}
1138 
1139 	interface ItemAPI {
1140 		// Define the index that identifies an item
1141 		struct CollectionIndices {
1142 			string _item;
1143 		}
1144 
1145 		// base path /items/:item/subItems
1146 		Collection!SubItemAPI subItems(string _item);
1147 
1148 		// GET /items/:item/name
1149 		@property string name(string _item);
1150 	}
1151 
1152 	interface API {
1153 		// a collection of items at the base path /items/
1154 		Collection!ItemAPI items();
1155 	}
1156 
1157 	//
1158 	// Local API implementation
1159 	//
1160 	class SubItemAPIImpl : SubItemAPI {
1161 		@property int length(string _item) { return 10; }
1162 
1163 		int getSquaredPosition(string _item, int _index) { return _index ^^ 2; }
1164 	}
1165 
1166 	class ItemAPIImpl : ItemAPI {
1167 		private SubItemAPIImpl m_subItems;
1168 
1169 		this() { m_subItems = new SubItemAPIImpl; }
1170 
1171 		Collection!SubItemAPI subItems(string _item) { return Collection!SubItemAPI(m_subItems, _item); }
1172 
1173 		string name(string _item) { return _item; }
1174 	}
1175 
1176 	class APIImpl : API {
1177 		private ItemAPIImpl m_items;
1178 
1179 		this() { m_items = new ItemAPIImpl; }
1180 
1181 		Collection!ItemAPI items() { return Collection!ItemAPI(m_items); }
1182 	}
1183 
1184 	//
1185 	// Resulting API usage
1186 	//
1187 	API api = new APIImpl; // A RestInterfaceClient!API would work just as well
1188 
1189 	// GET /items/foo/name
1190 	assert(api.items["foo"].name == "foo");
1191 	// GET /items/foo/sub_items/length
1192 	assert(api.items["foo"].subItems.length == 10);
1193 	// GET /items/foo/sub_items/2/squared_position
1194 	assert(api.items["foo"].subItems[2].getSquaredPosition() == 4);
1195 }
1196 
1197 unittest {
1198 	interface I {
1199 		struct CollectionIndices {
1200 			int id1;
1201 			string id2;
1202 		}
1203 
1204 		void a(int id1, string id2);
1205 		void b(int id1, int id2);
1206 		void c(int id1, string p);
1207 		void d(int id1, string id2, int p);
1208 		void e(int id1, int id2, int p);
1209 		void f(int id1, string p, int q);
1210 	}
1211 
1212 	Collection!I coll;
1213 	static assert(is(typeof(coll["x"].a()) == void));
1214 	static assert(is(typeof(coll.b(42)) == void));
1215 	static assert(is(typeof(coll.c("foo")) == void));
1216 	static assert(is(typeof(coll["x"].d(42)) == void));
1217 	static assert(is(typeof(coll.e(42, 42)) == void));
1218 	static assert(is(typeof(coll.f("foo", 42)) == void));
1219 }
1220 
1221 /// Model two nested collections using normal query parameters as indexes
1222 unittest {
1223 	//
1224 	// API definition
1225 	//
1226 	interface SubItemAPI {
1227 		// Define the index path that leads to a sub item
1228 		struct CollectionIndices {
1229 			// The ID of the base item. This must match the definition in
1230 			// ItemAPI.CollectionIndices
1231 			string item;
1232 			// The index if the sub item
1233 			int index;
1234 		}
1235 
1236 		// GET /items/subItems/length?item=...
1237 		@property int length(string item);
1238 
1239 		// GET /items/subItems/squared_position?item=...&index=...
1240 		int getSquaredPosition(string item, int index);
1241 	}
1242 
1243 	interface ItemAPI {
1244 		// Define the index that identifies an item
1245 		struct CollectionIndices {
1246 			string item;
1247 		}
1248 
1249 		// base path /items/subItems?item=...
1250 		Collection!SubItemAPI subItems(string item);
1251 
1252 		// GET /items/name?item=...
1253 		@property string name(string item);
1254 	}
1255 
1256 	interface API {
1257 		// a collection of items at the base path /items/
1258 		Collection!ItemAPI items();
1259 	}
1260 
1261 	//
1262 	// Local API implementation
1263 	//
1264 	class SubItemAPIImpl : SubItemAPI {
1265 		@property int length(string item) { return 10; }
1266 
1267 		int getSquaredPosition(string item, int index) { return index ^^ 2; }
1268 	}
1269 
1270 	class ItemAPIImpl : ItemAPI {
1271 		private SubItemAPIImpl m_subItems;
1272 
1273 		this() { m_subItems = new SubItemAPIImpl; }
1274 
1275 		Collection!SubItemAPI subItems(string item) { return Collection!SubItemAPI(m_subItems, item); }
1276 
1277 		string name(string item) { return item; }
1278 	}
1279 
1280 	class APIImpl : API {
1281 		private ItemAPIImpl m_items;
1282 
1283 		this() { m_items = new ItemAPIImpl; }
1284 
1285 		Collection!ItemAPI items() { return Collection!ItemAPI(m_items); }
1286 	}
1287 
1288 	//
1289 	// Resulting API usage
1290 	//
1291 	API api = new APIImpl; // A RestInterfaceClient!API would work just as well
1292 
1293 	// GET /items/name?item=foo
1294 	assert(api.items["foo"].name == "foo");
1295 	// GET /items/subitems/length?item=foo
1296 	assert(api.items["foo"].subItems.length == 10);
1297 	// GET /items/subitems/squared_position?item=foo&index=2
1298 	assert(api.items["foo"].subItems[2].getSquaredPosition() == 4);
1299 }
1300 
1301 unittest {
1302 	interface C {
1303 		struct CollectionIndices {
1304 			int _ax;
1305 			int _b;
1306 		}
1307 		void testB(int _ax, int _b);
1308 	}
1309 
1310 	interface B {
1311 		struct CollectionIndices {
1312 			int _a;
1313 		}
1314 		Collection!C c();
1315 		void testA(int _a);
1316 	}
1317 
1318 	interface A {
1319 		Collection!B b();
1320 	}
1321 
1322 	static assert (!is(typeof(A.init.b[1].c[2].testB())));
1323 }
1324 
1325 /** Allows processing the server request/response before the handler method is called.
1326 
1327 	Note that this attribute is only used by `registerRestInterface`, but not
1328 	by the client generators. This attribute expects the name of a parameter that
1329 	will receive its return value.
1330 
1331 	Writing to the response body from within the specified hander function
1332 	causes any further processing of the request to be skipped. In particular,
1333 	the route handler method will not be called.
1334 
1335 	Note:
1336 		The example shows the drawback of this attribute. It generally is a
1337 		leaky abstraction that propagates to the base interface. For this
1338 		reason the use of this attribute is not recommended, unless there is
1339 		no suitable alternative.
1340 */
1341 alias before = vibe.internal.meta.funcattr.before;
1342 
1343 ///
1344 @safe unittest {
1345     import vibe.http.router;
1346 	import vibe.http.server;
1347     import vibe.web.rest;
1348 
1349 	interface MyService {
1350 		long getHeaderCount(size_t foo = 0) @safe;
1351 	}
1352 
1353 	static size_t handler(scope HTTPServerRequest req, scope HTTPServerResponse res)
1354 	{
1355 		return req.headers.length;
1356 	}
1357 
1358 	class MyServiceImpl : MyService {
1359 		// the "foo" parameter will receive the number of request headers
1360 		@before!handler("foo")
1361 		long getHeaderCount(size_t foo)
1362 		{
1363 			return foo;
1364 		}
1365 	}
1366 
1367 	void test(URLRouter router)
1368 	@safe {
1369 		router.registerRestInterface(new MyServiceImpl);
1370 	}
1371 }
1372 
1373 
1374 /** Allows processing the return value of a handler method and the request/response objects.
1375 
1376 	The value returned by the REST API will be the value returned by the last
1377 	`@after` handler, which allows to post process the results of the handler
1378 	method.
1379 
1380 	Writing to the response body from within the specified handler function
1381 	causes any further processing of the request ot be skipped, including
1382 	any other `@after` annotations and writing the result value.
1383 */
1384 alias after = vibe.internal.meta.funcattr.after;
1385 
1386 ///
1387 @safe unittest {
1388     import vibe.http.router;
1389 	import vibe.http.server;
1390     import vibe.web.rest;
1391 
1392 	interface MyService {
1393 		long getMagic() @safe;
1394 	}
1395 
1396 	static long handler(long ret, HTTPServerRequest req, HTTPServerResponse res)
1397 	@safe {
1398 		return ret * 2;
1399 	}
1400 
1401 	class MyServiceImpl : MyService{
1402 		// the result reported by the REST API will be 42
1403 		@after!handler
1404 		long getMagic()
1405 		{
1406 			return 21;
1407 		}
1408 	}
1409 
1410 	void test(URLRouter router)
1411 	@safe {
1412 		router.registerRestInterface(new MyServiceImpl);
1413 	}
1414 }
1415 
1416 /**
1417  * Generate an handler that will wrap the server's method
1418  *
1419  * This function returns an handler, generated at compile time, that
1420  * will deserialize the parameters, pass them to the function implemented
1421  * by the user, and return what it needs to return, be it header parameters
1422  * or body, which is at the moment either a pure string or a Json object.
1423  *
1424  * One thing that makes this method more complex that it needs be is the
1425  * inability for D to attach UDA to parameters. This means we have to roll
1426  * our own implementation, which tries to be as easy to use as possible.
1427  * We'll require the user to give the name of the parameter as a string to
1428  * our UDA. Hopefully, we're also able to detect at compile time if the user
1429  * made a typo of any kind (see $(D genInterfaceValidationError)).
1430  *
1431  * Note:
1432  * Lots of abbreviations are used to ease the code, such as
1433  * PTT (ParameterTypeTuple), WPAT (WebParamAttributeTuple)
1434  * and PWPAT (ParameterWebParamAttributeTuple).
1435  *
1436  * Params:
1437  *	T = type of the object which represent the REST server (user implemented).
1438  *	Func = An alias to the function of $(D T) to wrap.
1439  *
1440  *	inst = REST server on which to call our $(D Func).
1441  *	settings = REST server configuration.
1442  *
1443  * Returns:
1444  *	A delegate suitable to use as an handler for an HTTP request.
1445  */
1446 private HTTPServerRequestDelegate jsonMethodHandler(alias Func, size_t ridx, T)(T inst, ref RestInterface!T intf)
1447 {
1448 	import std.encoding : sanitize;
1449 	import std.string : format;
1450 	import std.traits : Unqual;
1451 	import vibe.http.common : enforceBadRequest;
1452 	import vibe.web.internal.rest.common : ParameterKind;
1453 	import vibe.internal.meta.funcattr : IsAttributedParameter, computeAttributedParameterCtx;
1454 	import vibe.internal.meta.traits : derivedMethod;
1455 	import vibe.textfilter.urlencode : urlDecode;
1456 
1457 	enum Method = __traits(identifier, Func);
1458 	// We need mutable types for deserialization
1459 	alias PTypes = staticMap!(Unqual, ParameterTypeTuple!Func);
1460 	alias PDefaults = ParameterDefaultValueTuple!Func;
1461 	alias CFuncRaw = derivedMethod!(T, Func);
1462 	static if (AliasSeq!(CFuncRaw).length > 0) alias CFunc = CFuncRaw;
1463 	else alias CFunc = Func;
1464 	alias RT = ReturnType!(FunctionTypeOf!Func);
1465 	static const sroute = RestInterface!T.staticRoutes[ridx];
1466 	auto route = intf.routes[ridx];
1467 	auto settings = intf.settings;
1468     alias SerPolicyType = SerPolicyT!(RestInterface!T.I).PolicyTemplate;
1469 
1470 	void handler(HTTPServerRequest req, HTTPServerResponse res)
1471 	@safe {
1472 		if (route.bodyParameters.length) {
1473 			/*enforceBadRequest(req.contentType == "application/json",
1474 				"The Content-Type header needs to be set to application/json.");*/
1475 			enforceBadRequest(req.json.type != Json.Type.undefined,
1476 				"The request body does not contain a valid JSON value.");
1477 			enforceBadRequest(req.json.type == Json.Type.object,
1478 				"The request body must contain a JSON object.");
1479 		}
1480 
1481 		void handleException(Exception e, HTTPStatus default_status)
1482 		@safe {
1483 			logDebug("REST handler exception: %s", () @trusted { return e.toString(); } ());
1484 			if (res.headerWritten) {
1485 				logDebug("Response already started. Client will not receive an error code!");
1486 				return;
1487 			}
1488 
1489 			if (settings.errorHandler) {
1490 				settings.errorHandler(req, res, RestErrorInformation(e, default_status));
1491 			} else {
1492 				import std.algorithm : among;
1493 				debug string debugMsg;
1494 
1495 				if (auto se = cast(HTTPStatusException)e)
1496 					res.statusCode = se.status;
1497 				else debug {
1498 					res.statusCode = HTTPStatus.internalServerError;
1499 					debugMsg = e.toString().sanitize();
1500 				}
1501 				else
1502 					res.statusCode = default_status;
1503 
1504 				// All 1xx(informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body.
1505 				// See: https://tools.ietf.org/html/rfc2616#section-4.3
1506 				if (res.statusCode < 200 || res.statusCode.among(204, 304)) {
1507 					res.writeVoidBody();
1508 					return;
1509 				}
1510 
1511 				debug {
1512 					if (debugMsg) {
1513 						res.writeJsonBody(["statusMessage": e.msg, "statusDebugMessage": debugMsg]);
1514 						return;
1515 					}
1516 				}
1517 				res.writeJsonBody(["statusMessage": e.msg]);
1518 			}
1519 		}
1520 
1521 		static if (isAuthenticated!(T, Func)) {
1522 			typeof(handleAuthentication!Func(inst, req, res)) auth_info;
1523 
1524 			try auth_info = handleAuthentication!Func(inst, req, res);
1525 			catch (Exception e) {
1526 				handleException(e, HTTPStatus.unauthorized);
1527 				return;
1528 			}
1529 
1530 			if (res.headerWritten) return;
1531 		}
1532 
1533 
1534 		PTypes params;
1535 
1536 		try {
1537 			foreach (i, PT; PTypes) {
1538 				enum sparam = sroute.parameters[i];
1539 
1540 				static if (sparam.isIn) {
1541 					enum pname = sparam.name;
1542 					auto fieldname = route.parameters[i].fieldName;
1543 					static if (isInstanceOf!(Nullable, PT)) PT v;
1544 					else Nullable!PT v;
1545 
1546 					static if (sparam.kind == ParameterKind.auth) {
1547 						v = auth_info;
1548 					} else static if (sparam.kind == ParameterKind.query) {
1549 						if (auto pv = fieldname in req.query)
1550 							v = fromRestString!(PT, SerPolicyType)(*pv);
1551 					} else static if (sparam.kind == ParameterKind.wholeBody) {
1552 						try v = deserializeWithPolicy!(JsonSerializer, SerPolicyType, PT)(req.json);
1553 						catch (JSONException e) enforceBadRequest(false, e.msg);
1554 					} else static if (sparam.kind == ParameterKind.body_) {
1555 						try {
1556 							if (auto pv = fieldname in req.json)
1557 								v = deserializeWithPolicy!(JsonSerializer, SerPolicyType, PT)(*pv);
1558 						} catch (JSONException e)
1559 							enforceBadRequest(false, e.msg);
1560 					} else static if (sparam.kind == ParameterKind.header) {
1561 						if (auto pv = fieldname in req.headers)
1562 							v = fromRestString!(PT, SerPolicyType)(*pv);
1563 					} else static if (sparam.kind == ParameterKind.attributed) {
1564 						static if (!__traits(compiles, () @safe { computeAttributedParameterCtx!(CFunc, pname)(inst, req, res); } ()))
1565 							static assert(false, "`@before` evaluator for REST interface method `" ~ fullyQualifiedName!T ~ "." ~ Method ~ "` must be marked `@safe`.");
1566 						v = computeAttributedParameterCtx!(CFunc, pname)(inst, req, res);
1567 					} else static if (sparam.kind == ParameterKind.internal) {
1568 						if (auto pv = fieldname in req.params)
1569 							v = fromRestString!(PT, DefaultPolicy)(urlDecode(*pv));
1570 					} else static assert(false, "Unhandled parameter kind.");
1571 
1572 					static if (isInstanceOf!(Nullable, PT)) params[i] = v;
1573 					else if (v.isNull()) {
1574 						static if (!is(PDefaults[i] == void)) params[i] = PDefaults[i];
1575 						else enforceBadRequest(false, "Missing non-optional "~sparam.kind.to!string~" parameter '"~(fieldname.length?fieldname:sparam.name)~"'.");
1576 					} else params[i] = v.get;
1577 				}
1578 			}
1579 		} catch (Exception e) {
1580 			handleException(e, HTTPStatus.badRequest);
1581 			return;
1582 		}
1583 
1584 		static if (isAuthenticated!(T, Func)) {
1585 			try handleAuthorization!(T, Func, params)(auth_info);
1586 			catch (Exception e) {
1587 				handleException(e, HTTPStatus.forbidden);
1588 				return;
1589 			}
1590 		}
1591 
1592 		void handleCors()
1593 		{
1594 			import std.algorithm : any;
1595 			import std.uni : sicmp;
1596 
1597 			if (req.method == HTTPMethod.OPTIONS)
1598 				return;
1599 			auto origin = "Origin" in req.headers;
1600 			if (origin is null)
1601 				return;
1602 
1603 			if (settings.allowedOrigins.length != 0 &&
1604 				!settings.allowedOrigins.any!(org => org.sicmp((*origin)) == 0))
1605 				return;
1606 
1607 			res.headers["Access-Control-Allow-Origin"] = *origin;
1608 			res.headers["Access-Control-Allow-Credentials"] = "true";
1609 		}
1610 		// Anti copy-paste
1611 		void returnHeaders()
1612 		{
1613 			handleCors();
1614 			foreach (i, P; PTypes) {
1615 				static if (sroute.parameters[i].isOut) {
1616 					static assert (sroute.parameters[i].kind == ParameterKind.header);
1617 					static if (isInstanceOf!(Nullable, typeof(params[i]))) {
1618 						if (!params[i].isNull)
1619 							res.headers[route.parameters[i].fieldName] = to!string(params[i]);
1620 					} else {
1621 						res.headers[route.parameters[i].fieldName] = to!string(params[i]);
1622 					}
1623 				}
1624 			}
1625 		}
1626 
1627 		try {
1628 			import vibe.internal.meta.funcattr;
1629 
1630 			static if (!__traits(compiles, () @safe { __traits(getMember, inst, Method)(params); }))
1631 				static assert(false, "REST interface method `" ~ fullyQualifiedName!T ~ "." ~ Method ~ "` must be marked `@safe`.");
1632 
1633 			static if (is(RT == void)) {
1634 				// TODO: remove after deprecation period
1635 				__traits(getMember, inst, Method)(params);
1636 				returnHeaders();
1637 				res.writeBody(cast(ubyte[])null);
1638 			} else static if (isInputStream!RT) {
1639 				returnHeaders();
1640 				auto ret = evaluateOutputModifiers!CFunc(
1641 						__traits(getMember, inst, Method)(params), req, res);
1642 				res.headers["Content-Type"] = "application/octet-stream";
1643 				ret.pipe(res.bodyWriter);
1644 			} else {
1645 				// TODO: remove after deprecation period
1646 				static if (!__traits(compiles, () @safe { evaluateOutputModifiers!Func(RT.init, req, res); } ()))
1647 					static assert(false, "`@after` evaluator for REST interface method `" ~ fullyQualifiedName!T ~ "." ~ Method ~ "` must be marked `@safe`.");
1648 
1649 				auto ret = evaluateOutputModifiers!CFunc(
1650 						__traits(getMember, inst, Method)(params), req, res);
1651 				returnHeaders();
1652 
1653 				string accept_str;
1654 				if (const accept_header = "Accept" in req.headers)
1655 					accept_str = *accept_header;
1656 				alias result_serializers = ResultSerializersT!Func;
1657 				immutable serializer_ind = get_matching_content_type!(result_serializers)(accept_str);
1658 				foreach (i, serializer; result_serializers)
1659 					if (serializer_ind == i) {
1660 						auto serialized_output = appender!(ubyte[]);
1661 						static if (
1662 							__traits(compiles, () @trusted {
1663 								serializer.serialize!(SerPolicyT!(RestInterface!T.I).PolicyTemplate)(serialized_output, ret);
1664 							})
1665 							&& !__traits(compiles, () @safe {
1666 								serializer.serialize!(SerPolicyT!(RestInterface!T.I).PolicyTemplate)(serialized_output, ret);
1667 							}))
1668 						{
1669 							static assert(false, "Serialization of return type `"~RT.stringof~"` of REST interface method `" ~ fullyQualifiedName!T ~ "." ~ Method ~ "` must be `@safe`.");
1670 						}
1671 						serializer.serialize!(SerPolicyT!(RestInterface!T.I).PolicyTemplate)(serialized_output, ret);
1672 						res.writeBody(serialized_output.data, serializer.contentType);
1673 					}
1674 				res.statusCode = HTTPStatus.notAcceptable; // will trigger RestException on the client side
1675 				res.writeBody(cast(ubyte[])null);
1676 			}
1677 		} catch (Exception e) {
1678 			returnHeaders();
1679 			handleException(e, HTTPStatus.internalServerError);
1680 		}
1681 	}
1682 
1683 	return &handler;
1684 }
1685 
1686 /**
1687  * Generate an handler that will wrap the server's method
1688  *
1689  * This function returns an handler that handles the http OPTIONS method.
1690  *
1691  * It will return the ALLOW header with all the methods on this resource
1692  * And it will handle Preflight CORS.
1693  *
1694  * Params:
1695  *	routes = a range of Routes were each route has the same resource/URI
1696  *				just different method.
1697  *	settings = REST server configuration.
1698  *
1699  * Returns:
1700  *	A delegate suitable to use as an handler for an HTTP request.
1701  */
1702 private HTTPServerRequestDelegate optionsMethodHandler(RouteRange)(RouteRange routes, RestInterfaceSettings settings = null)
1703 {
1704 	import std.algorithm : map, joiner, any;
1705 	import std.array : array;
1706 	import std.conv : text;
1707 	import vibe.http.common : httpMethodString, httpMethodFromString;
1708 	// NOTE: don't know what is better, to keep this in memory, or generate on each request
1709 	auto allow = routes.map!(r => r.method.httpMethodString).joiner(",").text();
1710 	auto methods = routes.map!(r => r.method).array();
1711 
1712 	void handlePreflightedCors(HTTPServerRequest req, HTTPServerResponse res, ref HTTPMethod[] methods, RestInterfaceSettings settings = null)
1713 	{
1714 		import std.algorithm : among;
1715 		import std.uni : sicmp;
1716 
1717 		auto origin = "Origin" in req.headers;
1718 		if (origin is null)
1719 			return;
1720 
1721 		if (settings !is null &&
1722 			settings.allowedOrigins.length != 0 &&
1723 			!settings.allowedOrigins.any!(org => org.sicmp((*origin)) == 0))
1724 			return;
1725 
1726 		auto method = "Access-Control-Request-Method" in req.headers;
1727 		if (method is null)
1728 			return;
1729 
1730 		auto httpMethod = httpMethodFromString(*method);
1731 
1732 		if (!methods.any!(m => m == httpMethod))
1733 			return;
1734 
1735 		res.headers["Access-Control-Allow-Origin"] = *origin;
1736 
1737 		// there is no way to know if the specific resource supports credentials
1738 		// (either cookies, HTTP authentication, or client-side SSL certificates),
1739 		// so we always assume it does
1740 		res.headers["Access-Control-Allow-Credentials"] = "true";
1741 		res.headers["Access-Control-Max-Age"] = "1728000";
1742 		res.headers["Access-Control-Allow-Methods"] = *method;
1743 
1744 		// we have no way to reliably determine what headers the resource allows
1745 		// so we simply copy whatever the client requested
1746 		if (auto headers = "Access-Control-Request-Headers" in req.headers)
1747 			res.headers["Access-Control-Allow-Headers"] = *headers;
1748 	}
1749 
1750 	void handler(HTTPServerRequest req, HTTPServerResponse res)
1751 	{
1752 		// since this is a OPTIONS request, we have to return the ALLOW headers to tell which methods we have
1753 		res.headers["Allow"] = allow;
1754 
1755 		// handle CORS preflighted requests
1756 		handlePreflightedCors(req,res,methods,settings);
1757 
1758 		// NOTE: besides just returning the allowed methods and handling CORS preflighted requests,
1759 		// this would be a nice place to describe what kind of resources are on this route,
1760 		// the params each accepts, the headers, etc... think WSDL but then for REST.
1761 		res.writeBody("");
1762 	}
1763 	return &handler;
1764 }
1765 
1766 private string generateRestClientMethods(I)()
1767 {
1768 	import std.array : join;
1769 	import std.string : format;
1770 	import std.traits : fullyQualifiedName, isInstanceOf;
1771 
1772 	alias Info = RestInterface!I;
1773 
1774 	string ret = q{
1775 		import vibe.internal.meta.codegen : CloneFunction;
1776 	};
1777 
1778 	// generate sub interface methods
1779 	foreach (i, SI; Info.SubInterfaceTypes) {
1780 		alias F = Info.SubInterfaceFunctions[i];
1781 		alias RT = ReturnType!F;
1782 		alias ParamNames = ParameterIdentifierTuple!F;
1783 		static if (ParamNames.length == 0) enum pnames = "";
1784 		else enum pnames = ", " ~ [ParamNames].join(", ");
1785 		static if (isInstanceOf!(Collection, RT)) {
1786 			ret ~= q{
1787 					mixin CloneFunction!(Info.SubInterfaceFunctions[%1$s], q{
1788 						return Collection!(%2$s)(m_subInterfaces[%1$s]%3$s);
1789 					});
1790 				}.format(i, fullyQualifiedName!SI, pnames);
1791 		} else {
1792 			ret ~= q{
1793 					mixin CloneFunction!(Info.SubInterfaceFunctions[%1$s], q{
1794 						return m_subInterfaces[%1$s];
1795 					});
1796 				}.format(i);
1797 		}
1798 	}
1799 
1800 	// generate route methods
1801 	foreach (i, F; Info.RouteFunctions) {
1802 		alias ParamNames = ParameterIdentifierTuple!F;
1803 		static if (ParamNames.length == 0) enum pnames = "";
1804 		else enum pnames = ", " ~ [ParamNames].join(", ");
1805 
1806 		ret ~= q{
1807 				mixin CloneFunction!(Info.RouteFunctions[%1$s], q{
1808 					return executeClientMethod!(I, %1$s%2$s)(*m_intf, m_requestFilter, m_requestBodyFilter);
1809 				});
1810 			}.format(i, pnames);
1811 	}
1812 
1813 	// generate stubs for non-route functions
1814 	static foreach (m; __traits(allMembers, I))
1815 		foreach (i, fun; MemberFunctionsTuple!(I, m))
1816 			static if (hasUDA!(fun, NoRouteAttribute))
1817 				ret ~= q{
1818 					mixin CloneFunction!(MemberFunctionsTuple!(I, "%s")[%s], q{
1819 						assert(false);
1820 					});
1821 				}.format(m, i);
1822 
1823 	return ret;
1824 }
1825 
1826 
1827 private auto executeClientMethod(I, size_t ridx, ARGS...)
1828 	(const ref RestInterface!I intf, scope void delegate(HTTPClientRequest) @safe request_filter,
1829 		scope void delegate(HTTPClientRequest, scope InputStream) @safe request_body_filter)
1830 {
1831 	import vibe.internal.interfaceproxy : asInterface;
1832 	import vibe.web.internal.rest.common : ParameterKind;
1833 	import vibe.stream.operations : readAll;
1834 	import vibe.textfilter.urlencode : filterURLEncode, urlEncode;
1835 
1836 	alias Info = RestInterface!I;
1837 	alias Func = Info.RouteFunctions[ridx];
1838 	alias RT = ReturnType!Func;
1839 	alias PTT = ParameterTypeTuple!Func;
1840 	alias SerPolicyType = SerPolicyT!I.PolicyTemplate;
1841 	enum sroute = Info.staticRoutes[ridx];
1842 	auto route = intf.routes[ridx];
1843 	auto settings = intf.settings;
1844 
1845 	InetHeaderMap headers;
1846 	InetHeaderMap reqhdrs;
1847 	InetHeaderMap opthdrs;
1848 
1849 	string url_prefix;
1850 
1851 	auto query = appender!string();
1852 	auto jsonBody = Json.emptyObject;
1853 	string body_;
1854 
1855 	void addQueryParam(size_t i)(string name)
1856 	{
1857 		if (query.data.length) query.put('&');
1858 		query.filterURLEncode(name);
1859 		query.put("=");
1860 		static if (is(PT == Json))
1861 			query.filterURLEncode(ARGS[i].toString());
1862 		else
1863 		// Note: CTFE triggers compiler bug here (think we are returning Json, not string).
1864 				query.filterURLEncode(toRestString!SerPolicyType(ARGS[i]));
1865 	}
1866 
1867 	foreach (i, PT; PTT) {
1868 		enum sparam = sroute.parameters[i];
1869 		auto fieldname = route.parameters[i].fieldName;
1870 		static if (sparam.kind == ParameterKind.query) {
1871 			addQueryParam!i(fieldname);
1872 		} else static if (sparam.kind == ParameterKind.wholeBody) {
1873 			jsonBody = serializeWithPolicy!(JsonSerializer, SerPolicyType)(ARGS[i]);
1874 		} else static if (sparam.kind == ParameterKind.body_) {
1875 			jsonBody[fieldname] = serializeWithPolicy!(JsonSerializer, SerPolicyType)(ARGS[i]);
1876 		} else static if (sparam.kind == ParameterKind.header) {
1877 			// Don't send 'out' parameter, as they should be default init anyway and it might confuse some server
1878 			static if (sparam.isIn) {
1879 				static if (isInstanceOf!(Nullable, PT)) {
1880 					if (!ARGS[i].isNull)
1881 						headers[fieldname] = to!string(ARGS[i]);
1882 				} else headers[fieldname] = to!string(ARGS[i]);
1883 			}
1884 			static if (sparam.isOut) {
1885 				// Optional parameter
1886 				static if (isInstanceOf!(Nullable, PT)) {
1887 					opthdrs[fieldname] = null;
1888 				} else {
1889 					reqhdrs[fieldname] = null;
1890 				}
1891 			}
1892 		}
1893 	}
1894 
1895 	static if (sroute.method == HTTPMethod.GET) {
1896 		assert(jsonBody == Json.emptyObject, "GET request trying to send body parameters.");
1897 	} else {
1898 		debug body_ = jsonBody.toPrettyString();
1899 		else body_ = jsonBody.toString();
1900 	}
1901 
1902 	string url;
1903 	foreach (i, p; route.fullPathParts) {
1904 		if (p.isParameter) {
1905 			switch (p.text) {
1906 				foreach (j, PT; PTT) {
1907 					static if (sroute.parameters[j].name[0] == '_' || sroute.parameters[j].name == "id") {
1908 						case sroute.parameters[j].name:
1909 							url ~= urlEncode(toRestString(ARGS[j]));
1910 							goto sbrk;
1911 					}
1912 				}
1913 				default: url ~= ":" ~ p.text; break;
1914 			}
1915 			sbrk:;
1916 		} else url ~= p.text;
1917 	}
1918 
1919 	scope (exit) {
1920 		foreach (i, PT; PTT) {
1921 			enum sparam = sroute.parameters[i];
1922 			auto fieldname = route.parameters[i].fieldName;
1923 			static if (sparam.kind == ParameterKind.header) {
1924 				static if (sparam.isOut) {
1925 					static if (isInstanceOf!(Nullable, PT)) {
1926 						ARGS[i] = to!(TemplateArgsOf!PT)(
1927 							opthdrs.get(fieldname, null));
1928 					} else {
1929 						if (auto ptr = fieldname in reqhdrs)
1930 							ARGS[i] = to!PT(*ptr);
1931 					}
1932 				}
1933 			}
1934 		}
1935 	}
1936 
1937 	static if (!is(RT == void)) {{
1938 		alias result_serializers = ResultSerializersT!Func;
1939 		static if (is(result_serializers == AliasSeq!(DefaultSerializerT)))
1940 			headers["Accept"] = settings.content_type;
1941 		else
1942 			headers["Accept"] = result_serializers[0].contentType;
1943 	}} else {
1944 		headers["Accept"] = settings.content_type;
1945 	}
1946 
1947 	// Do not require a `Content-Type` header if no response is expected
1948 	// https://github.com/vibe-d/vibe.d/issues/2521
1949 	static if (!is(RT == void))
1950 		// Don't override it if set from the parameter
1951 		if ("Content-Type" !in opthdrs)
1952 			opthdrs["Content-Type"] = null;
1953 
1954 	auto ret = request(URL(intf.baseURL), request_filter, request_body_filter,
1955 		sroute.method, url, headers, query.data, body_, reqhdrs, opthdrs,
1956 		intf.settings.httpClientSettings);
1957 
1958 	static if (is(RT == InputStream)) {
1959 		return ret.bodyReader.asInterface!InputStream;
1960 	} else static if (isInputStream!RT) {
1961 		return RT(ret.bodyReader);
1962 	} else static if (!is(RT == void)) {
1963 		scope(exit) ret.dropBody();
1964 
1965 		string content_type;
1966 		if (const hdr = "Content-Type" in opthdrs)
1967 			content_type = *hdr;
1968 		if (!content_type.length)
1969 			content_type = "application/octet-stream";
1970 
1971 		alias result_serializers = ResultSerializersT!Func;
1972 		immutable serializer_ind = get_matching_content_type!(result_serializers)(content_type);
1973 		foreach (i, serializer; result_serializers)
1974 			if (serializer_ind == i) {
1975 				// TODO: The JSON deserialiation code requires a forward range,
1976 				//       but streamInputRange is currently just a bare input
1977 				//       range, so for now we need to read everything into a
1978 				//       buffer instead.
1979 				//import vibe.stream.wrapper : streamInputRange;
1980 				//auto rng = streamInputRange(ret.bodyReader);
1981 				auto rng = ret.bodyReader.readAll();
1982 				return serializer.deserialize!(SerPolicyT!I.PolicyTemplate, RT)(rng);
1983 			}
1984 
1985 		throw new Exception("Unrecognized content type: " ~ content_type);
1986 	} else ret.dropBody();
1987 }
1988 
1989 /**
1990  * Perform a request to the interface using the given parameters.
1991  *
1992  * Params:
1993  * verb = Kind of request (See $(D HTTPMethod) enum).
1994  * name = Location to request. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
1995  *		it will be '/rejectedsoftware/vibe.d/issues'.
1996  * hdrs = The headers to send. Some field might be overriden (such as Content-Length). However, Content-Type will NOT be overriden.
1997  * query = The $(B encoded) query string. For a request on https://github.com/rejectedsoftware/vibe.d/issues?q=author%3ASantaClaus,
1998  *		it will be 'author%3ASantaClaus'.
1999  * 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
2000  *		the generic type "application/json".
2001  * reqReturnHdrs = A map of required return headers.
2002  *				   To avoid returning unused headers, nothing is written
2003  *				   to this structure unless there's an (usually empty)
2004  *				   entry (= the key exists) with the same key.
2005  *				   If any key present in `reqReturnHdrs` is not present
2006  *				   in the response, an Exception is thrown.
2007  * optReturnHdrs = A map of optional return headers.
2008  *				   This behaves almost as exactly as reqReturnHdrs,
2009  *				   except that non-existent key in the response will
2010  *				   not cause it to throw, but rather to set this entry
2011  *				   to 'null'.
2012  *
2013  * Returns:
2014  *     The Json object returned by the request
2015  */
2016 private HTTPClientResponse request(URL base_url,
2017 	scope void delegate(HTTPClientRequest) @safe request_filter,
2018 	scope void delegate(HTTPClientRequest, scope InputStream) @safe request_body_filter,
2019 	HTTPMethod verb, string path, const scope ref InetHeaderMap hdrs, string query,
2020 	string body_, ref InetHeaderMap reqReturnHdrs,
2021 	ref InetHeaderMap optReturnHdrs, in HTTPClientSettings http_settings)
2022 @safe {
2023 	import std.uni : sicmp;
2024 	import vibe.http.client : requestHTTP;
2025 	import vibe.http.common : HTTPStatus, httpMethodString, httpStatusText;
2026 	import vibe.stream.memory : createMemoryStream;
2027 	import vibe.stream.operations;
2028 
2029 	URL url = base_url;
2030 	url.pathString = path;
2031 
2032 	if (query.length) url.queryString = query;
2033 
2034     scope reqdg = (scope HTTPClientRequest req) {
2035 		req.method = verb;
2036 		foreach (k, v; hdrs.byKeyValue)
2037 			req.headers[k] = v;
2038 
2039 		if (request_body_filter) {
2040 			scope str = createMemoryStream(() @trusted { return cast(ubyte[])body_; } (), false);
2041 			request_body_filter(req, str);
2042 		}
2043 
2044 		if (request_filter) request_filter(req);
2045 
2046 		if (body_ != "")
2047 			req.writeBody(cast(const(ubyte)[])body_, hdrs.get("Content-Type", "application/json; charset=UTF-8"));
2048 	};
2049 
2050 	HTTPClientResponse client_res;
2051 	if (http_settings) client_res = requestHTTP(url, reqdg, http_settings);
2052 	else client_res = requestHTTP(url, reqdg);
2053 
2054 	logDebug(
2055 			"REST call: %s %s -> %d, %s",
2056 			httpMethodString(verb),
2057 			url.toString(),
2058 			client_res.statusCode,
2059 			client_res.statusPhrase
2060 			);
2061 
2062 	// Get required headers - Don't throw yet
2063 	string[] missingKeys;
2064 	foreach (k, ref v; reqReturnHdrs.byKeyValue)
2065 		if (auto ptr = k in client_res.headers)
2066 			v = (*ptr).idup;
2067 		else
2068 			missingKeys ~= k;
2069 
2070 	// Get optional headers
2071 	foreach (k, ref v; optReturnHdrs.byKeyValue)
2072 		if (auto ptr = k in client_res.headers)
2073 			v = (*ptr).idup;
2074 		else
2075 			v = null;
2076 	if (missingKeys.length)
2077 		throw new Exception(
2078 			"REST interface mismatch: Missing required header field(s): "
2079 			~ missingKeys.to!string);
2080 
2081 	if (!isSuccessCode(cast(HTTPStatus)client_res.statusCode))
2082 	{
2083 		Json msg = Json(["statusMessage": Json(client_res.statusPhrase)]);
2084 		if (client_res.contentType.length)
2085 			if (client_res.contentType.splitter(";").front.strip.sicmp("application/json") == 0)
2086 				msg = client_res.readJson();
2087 		client_res.dropBody();
2088 		throw new RestException(client_res.statusCode, msg);
2089 	}
2090 
2091 	return client_res;
2092 }
2093 
2094 private {
2095 	import vibe.data.json;
2096 	import std.conv : to;
2097 
2098 	string toRestString(alias SerPolicyType = DefaultPolicy, T)(T value)
2099 	@safe {
2100 		import std.array : Appender, appender;
2101 		import std.uuid : UUID;
2102 		static if (isInstanceOf!(Nullable, T)) return T(fromRestString!(typeof(T.init.get()))(value));
2103 		else static if (is(T == bool)) return value ? "true" : "false";
2104 		else static if (is(T : int)) return to!string(value);
2105 		else static if (is(T : double)) return to!string(value); // FIXME: formattedWrite(dst, "%.16g", json.get!double);
2106 		else static if (is(string : T)) return value;
2107 		else static if (__traits(compiles, value.toISOExtString)) return value.toISOExtString;
2108 		else static if (__traits(compiles, value.toString)) return value.toString;
2109 		else static if (is(T == UUID)) return value.toString();
2110 		else {
2111 			auto ret = appender!string;
2112 			serializeWithPolicy!(JsonStringSerializer!(Appender!string), SerPolicyType, T)(value, ret);
2113 			return ret.data;
2114 		}
2115 	}
2116 
2117 	T fromRestString(T, alias SerPolicyType = DefaultPolicy)(string value)
2118 	{
2119 		import std.conv : ConvException;
2120 		import std.uuid : UUID, UUIDParsingException;
2121 		import vibe.http.status : HTTPStatus;
2122 		try {
2123 			static if (isInstanceOf!(Nullable, T)) return T(fromRestString!(typeof(T.init.get()))(value));
2124 			else static if (is(T == bool)) return value == "1" || value.to!T;
2125 			else static if (is(T : int)) return to!T(value);
2126 			else static if (is(T : double)) return to!T(value); // FIXME: formattedWrite(dst, "%.16g", json.get!double);
2127 			else static if (is(string : T)) return value;
2128 			else static if (__traits(compiles, T.fromISOExtString("hello"))) return T.fromISOExtString(value);
2129 			else static if (__traits(compiles, T.fromString("hello"))) return T.fromString(value);
2130 			else static if (is(T == UUID)) return UUID(value);
2131 			else return deserializeWithPolicy!(JsonStringSerializer!string, SerPolicyType, T)(value);
2132 		} catch (ConvException e) {
2133 			throw new HTTPStatusException(HTTPStatus.badRequest, e.msg);
2134 		} catch (JSONException e) {
2135 			throw new HTTPStatusException(HTTPStatus.badRequest, e.msg);
2136 		} catch (UUIDParsingException e) {
2137 			throw new HTTPStatusException(HTTPStatus.badRequest, e.msg);
2138 		}
2139 	}
2140 
2141 	// Converting from invalid JSON string to aggregate should throw bad request
2142 	unittest {
2143 		void assertHTTPStatus(E)(lazy E expression, HTTPStatus expectedStatus,
2144 			string file = __FILE__, size_t line = __LINE__)
2145 		{
2146 			import core.exception : AssertError;
2147 			import std.format : format;
2148 
2149 			try
2150 				expression();
2151 			catch (HTTPStatusException e)
2152 			{
2153 				if (e.status != expectedStatus)
2154 					throw new AssertError(format("assertHTTPStatus failed: " ~
2155 						"status expected %d but was %d", expectedStatus, e.status),
2156 						file, line);
2157 
2158 				return;
2159 			}
2160 
2161 			throw new AssertError("assertHTTPStatus failed: No " ~
2162 				"'HTTPStatusException' exception was thrown", file, line);
2163 		}
2164 
2165 		struct Foo { int bar; }
2166 		assertHTTPStatus(fromRestString!(Foo)("foo"), HTTPStatus.badRequest);
2167 
2168 		enum Bar { foo, bar }
2169 		assert(fromRestString!(Bar)("bar") == Bar.bar);
2170 		assertHTTPStatus(fromRestString!(Bar)("foobarbaz"), HTTPStatus.badRequest);
2171 	}
2172 }
2173 
2174 private string generateModuleImports(I)()
2175 {
2176 	if (!__ctfe)
2177 		assert (false);
2178 
2179 	import vibe.internal.meta.codegen : getRequiredImports;
2180 	import std.algorithm : map;
2181 	import std.array : join;
2182 
2183 	auto modules = getRequiredImports!I();
2184 	return join(map!(a => "static import " ~ a ~ ";")(modules), "\n");
2185 }
2186 
2187 /***************************************************************************
2188 
2189 	The client sends the list of allowed content types in the 'Allow' http header
2190 	and the response will contain a 'Content-Type' header. This function will
2191 	try to find the best matching @SerializationResult UDA based on the allowed
2192 	content types.
2193 
2194 	Note:
2195 
2196 	Comment 1: if the request doesn't specify any allowed content types, then * / *
2197 	is assumed
2198 
2199 	Comment 2: if there are no UDA's matching the client's allowed content types, -1
2200 	is returned
2201 
2202 	Comment 3: if there are more than 1 matching UDA, for ONE specific client's allowed
2203 	content type(and their priority is the same - see below),
2204 	then the one specified earlier in the code gets chosen
2205 
2206 	Comment 4: accept-params(quality factor) and accept-extensions are ignored
2207 	https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
2208 
2209 	Comment 5: matching the most specific content type without any wildcard has priority
2210 
2211 	Comment 6: the request's content type can be in the format of
2212 
2213 	$(UL
2214 		$(LI major type / minor type)
2215 		$(LI major type / *)
2216 		$(LI * / *)
2217 	)
2218 
2219 	Params:
2220 		T = compile time known ResultSerializer classes
2221 		req_content_types_str = list of allowed content types for example:
2222 		text/*;q=0.3, text/html;q=0.7, text/html;level=1
2223 
2224 	Returns:
2225 		index of the result serializers in the T... AliasSeq if matching found,
2226 		-1 otherwise
2227 
2228 ***************************************************************************/
2229 
2230 package int get_matching_content_type (T...)(string req_content_types_str) pure @safe
2231 {
2232 	if (!req_content_types_str.strip().length)
2233 		req_content_types_str = "*/*";
2234 	struct ContentType
2235 	{
2236 		this (string major_type, string minor_type)
2237 		{
2238 			this.major_type = major_type;
2239 			this.minor_type = minor_type;
2240 			this.full_type = major_type ~ "/" ~ minor_type;
2241 			this.star_num = this.full_type.count('*'); // serves as priority
2242 		}
2243 		string major_type;
2244 		string minor_type;
2245 		string full_type;
2246 		ulong star_num;
2247 	}
2248 
2249 	// processing ResultSerializers
2250 	alias packed_UDAs = AliasSeq!(T);
2251 	ContentType[] UDA_content_types;
2252 	foreach (UDA; packed_UDAs)
2253 	{
2254 		auto ctype = UDA.contentType.splitter(';').front;
2255 		auto content_type_split = ctype.toLower().split("/");
2256 		assert(content_type_split.length == 2);
2257 		UDA_content_types ~= ContentType(content_type_split[0].strip(), content_type_split[1].strip());
2258 	}
2259 
2260 	// processing request content typess
2261 	ContentType[] req_content_types;
2262 	foreach (content_type; req_content_types_str.toLower().split(","))
2263 	{
2264 		immutable semicolon_pos = content_type.indexOf(';');
2265 		if (semicolon_pos != -1)
2266 			content_type = content_type[0 .. semicolon_pos]; // quality factor ignored
2267 		auto content_type_split = content_type.split("/");
2268 		if (content_type_split.length == 2)
2269 			req_content_types ~= ContentType(content_type_split[0].strip(), content_type_split[1].strip());
2270 	}
2271 	// sorting content types by matching preference
2272 	req_content_types.sort!(( x, y) => (x.star_num < y.star_num));
2273 
2274 	int res = -1;
2275 	ulong min_star_num = ulong.max;
2276 	foreach (UDA_ind, UDA_content_type; UDA_content_types)
2277 		foreach (const ref content_type; req_content_types)
2278 			if (
2279 					(
2280 						(
2281 							UDA_content_type.major_type == content_type.major_type &&
2282 							UDA_content_type.minor_type == content_type.minor_type
2283 						) ||
2284 						(
2285 							UDA_content_type.major_type == content_type.major_type &&
2286 							content_type.minor_type == "*"
2287 						) ||
2288 						(
2289 							content_type.major_type == "*" && content_type.minor_type == "*"
2290 						)
2291 					) &&
2292 					(
2293 						content_type.star_num < min_star_num
2294 					)
2295 				)
2296 				{
2297 					res = cast(int) UDA_ind;
2298 					min_star_num = content_type.star_num;
2299 				}
2300 	return res;
2301 }
2302 
2303 version(unittest)
2304 {
2305 	import std.range.interfaces : OutputRange;
2306 	import vibe.internal.interfaceproxy : InterfaceProxy;
2307 
2308 	void s (OutputRange!char, int){};
2309 	int d (InterfaceProxy!(InputStream)){return 1;}
2310 
2311 	int test1();
2312 
2313 	@resultSerializer!(s,d,"text/plain")
2314 	@resultSerializer!(s,d," aPPliCatIon  /  jsOn  ")
2315 	int test2();
2316 }
2317 
2318 unittest
2319 {
2320 	alias res = ResultSerializersT!(test1);
2321 	assert(res.length == 1);
2322 	assert(res[0].contentType == "application/json; charset=UTF-8");
2323 
2324 	assert(get_matching_content_type!(res)("application/json") == 0);
2325 	assert(get_matching_content_type!(res)("application/*") == 0);
2326 	assert(get_matching_content_type!(res)("  appliCAtIon /  *") == 0);
2327 	assert(get_matching_content_type!(res)("*/*") == 0);
2328 	assert(get_matching_content_type!(res)("") == 0);
2329 	assert(get_matching_content_type!(res)("application/blabla") == -1);
2330 
2331 	alias res2 = ResultSerializersT!(test2);
2332 	assert(res2.length == 2);
2333 	assert(res2[0].contentType == "text/plain");
2334 	assert(res2[1].contentType == " aPPliCatIon  /  jsOn  ");
2335 
2336 	assert(get_matching_content_type!(res2)("text/plain, application/json") == 0);
2337 	assert(get_matching_content_type!(res2)("text/*, application/json") == 1);
2338 	assert(get_matching_content_type!(res2)("*/*, application/json") == 1);
2339 	assert(get_matching_content_type!(res2)("*/*") == 0);
2340 	assert(get_matching_content_type!(res2)("") == 0);
2341 	assert(get_matching_content_type!(res2)("blabla/blabla, blublu/blublu") == -1);
2342 }
2343 
2344 version(unittest)
2345 {
2346 	private struct Aggregate { }
2347 	private interface Interface
2348 	{
2349 		Aggregate[] foo();
2350 	}
2351 }
2352 
2353 unittest
2354 {
2355 	enum imports = generateModuleImports!Interface;
2356 	static assert (imports == "static import vibe.web.rest;");
2357 }
2358 
2359 // Check that the interface is valid. Every checks on the correctness of the
2360 // interface should be put in checkRestInterface, which allows to have consistent
2361 // errors in the server and client.
2362 package string getInterfaceValidationError(I)()
2363 out (result) { assert((result is null) == !result.length); }
2364 do {
2365 	import vibe.web.internal.rest.common : ParameterKind, WebParamUDATuple;
2366 	import std.algorithm : strip;
2367 
2368 	// The hack parameter is to kill "Statement is not reachable" warnings.
2369 	string validateMethod(alias Func)(bool hack = true) {
2370 		import vibe.internal.meta.uda;
2371 		import std.string : format;
2372 
2373 		static assert(is(FunctionTypeOf!Func), "Internal error");
2374 
2375 		if (!__ctfe)
2376 			assert(false, "Internal error");
2377 
2378 		enum FuncId = (fullyQualifiedName!I~ "." ~ __traits(identifier, Func));
2379 		alias PT = ParameterTypeTuple!Func;
2380 		static if (!__traits(compiles, ParameterIdentifierTuple!Func)) {
2381 			if (hack) return "%s: A parameter has no name.".format(FuncId);
2382 			alias PN = AliasSeq!("-DummyInvalid-");
2383 		} else
2384 			alias PN = ParameterIdentifierTuple!Func;
2385 		alias WPAT = UDATuple!(WebParamAttribute, Func);
2386 
2387 		// Check if there is no orphan UDATuple (e.g. typo while writing the name of the parameter).
2388 		foreach (i, uda; WPAT) {
2389 			// Note: static foreach gets unrolled, generating multiple nested sub-scope.
2390 			// The spec / DMD doesn't like when you have the same symbol in those,
2391 			// leading to wrong codegen / wrong template being reused.
2392 			// That's why those templates need different names.
2393 			// See DMD bug #9748.
2394 			mixin(GenOrphan!(i).Decl);
2395 			// template CmpOrphan(string name) { enum CmpOrphan = (uda.identifier == name); }
2396 			static if (!anySatisfy!(mixin(GenOrphan!(i).Name), PN)) {
2397 				if (hack) return "%s: No parameter '%s' (referenced by attribute @%sParam)"
2398 					.format(FuncId, uda.identifier, uda.origin);
2399 			}
2400 		}
2401 
2402 		foreach (i, P; PT) {
2403 			static if (!PN[i].length)
2404 				if (hack) return "%s: Parameter %d has no name."
2405 					.format(FuncId, i);
2406 			// Check for multiple origins
2407 			static if (WPAT.length) {
2408 				// It's okay to reuse GenCmp, as the order of params won't change.
2409 				// It should/might not be reinstantiated by the compiler.
2410 				mixin(GenCmp!("Loop", i, PN[i]).Decl);
2411 				alias WPA = Filter!(mixin(GenCmp!("Loop", i, PN[i]).Name), WPAT);
2412 				static if (WPA.length > 1)
2413 					if (hack) return "%s: Parameter '%s' has multiple @*Param attributes on it."
2414 						.format(FuncId, PN[i]);
2415 			}
2416 		}
2417 
2418 		// Check for misplaced out and non-const ref
2419 		alias PSC = ParameterStorageClass;
2420 		foreach (i, SC; ParameterStorageClassTuple!Func) {
2421 			static if (SC & PSC.out_ || (SC & PSC.ref_ && !is(ConstOf!(PT[i]) == PT[i])) ) {
2422 				mixin(GenCmp!("Loop", i, PN[i]).Decl);
2423 				alias Attr = AliasSeq!(
2424 					WebParamUDATuple!(Func, i),
2425 					Filter!(mixin(GenCmp!("Loop", i, PN[i]).Name), WPAT),
2426 				);
2427 				static if (Attr.length != 1) {
2428 					if (hack) return "%s: Parameter '%s' cannot be %s"
2429 						.format(FuncId, PN[i], SC & PSC.out_ ? "out" : "ref");
2430 				} else static if (Attr[0].origin != ParameterKind.header) {
2431 					if (hack) return "%s: %s parameter '%s' cannot be %s"
2432 						.format(FuncId, Attr[0].origin, PN[i],
2433 							SC & PSC.out_ ? "out" : "ref");
2434 				}
2435 			}
2436 		}
2437 
2438 		// Check for @path(":name")
2439 		enum pathAttr = findFirstUDA!(PathAttribute, Func);
2440 		static if (pathAttr.found) {
2441 			static if (!pathAttr.value.length) {
2442 				if (hack)
2443 					return "%s: Path is null or empty".format(FuncId);
2444 			} else {
2445 				import std.algorithm : canFind, splitter;
2446 				// splitter doesn't work with alias this ?
2447 				auto str = pathAttr.value.data;
2448 				if (str.canFind("//")) return "%s: Path '%s' contains empty entries.".format(FuncId, pathAttr.value);
2449 				str = str.strip('/');
2450 				if (!str.length) return null;
2451 				foreach (elem; str.splitter('/')) {
2452 					assert(elem.length, "Empty path entry not caught yet!?");
2453 
2454 					if (elem[0] == ':') {
2455 						// typeof(PN) is void when length is 0.
2456 						static if (!PN.length) {
2457 							if (hack)
2458 								return "%s: Path contains '%s', but no parameter '_%s' defined."
2459 									.format(FuncId, elem, elem[1..$]);
2460 						} else {
2461 							if (![PN].canFind("_"~elem[1..$]))
2462 								if (hack) return "%s: Path contains '%s', but no parameter '_%s' defined."
2463 									.format(FuncId, elem, elem[1..$]);
2464 							elem = elem[1..$];
2465 						}
2466 					}
2467 				}
2468 				// TODO: Check for validity of the subpath.
2469 			}
2470 		}
2471 		return null;
2472 	}
2473 
2474 	if (!__ctfe)
2475 		assert(false, "Internal error");
2476 	bool hack = true;
2477 	foreach (method; __traits(allMembers, I)) {
2478 		// WORKAROUND #1045 / @@BUG14375@@
2479 		static if (method.length != 0)
2480 			foreach (overload; MemberFunctionsTuple!(I, method)) {
2481 				static if (validateMethod!(overload)())
2482 					if (hack) return validateMethod!(overload)();
2483 			}
2484 	}
2485 	return null;
2486 }
2487 
2488 // Test detection of user typos (e.g., if the attribute is on a parameter that doesn't exist).
2489 unittest {
2490 	enum msg = "No parameter 'ath' (referenced by attribute @headerParam)";
2491 
2492 	interface ITypo {
2493 		@headerParam("ath", "Authorization") // mistyped parameter name
2494 		string getResponse(string auth);
2495 	}
2496 	enum err = getInterfaceValidationError!ITypo;
2497 	static assert(err !is null && stripTestIdent(err) == msg,
2498 		"Expected validation error for getResponse, got: "~stripTestIdent(err));
2499 }
2500 
2501 // Multiple origin for a parameter
2502 unittest {
2503 	enum msg = "Parameter 'arg1' has multiple @*Param attributes on it.";
2504 
2505 	interface IMultipleOrigin {
2506 		@headerParam("arg1", "Authorization") @bodyParam("arg1", "Authorization")
2507 		string getResponse(string arg1, int arg2);
2508 	}
2509 	enum err = getInterfaceValidationError!IMultipleOrigin;
2510 	static assert(err !is null && stripTestIdent(err) == msg, err);
2511 }
2512 
2513 // Missing parameter name
2514 unittest {
2515 	enum msg = "Parameter 0 has no name.";
2516 
2517 	interface IMissingName1 {
2518 		string getResponse(string = "troublemaker");
2519 	}
2520 	interface IMissingName2 {
2521 		string getResponse(string);
2522 	}
2523 	enum err1 = getInterfaceValidationError!IMissingName1;
2524 	static assert(err1 !is null && stripTestIdent(err1) == msg, err1);
2525 	enum err2 = getInterfaceValidationError!IMissingName2;
2526 	static assert(err2 !is null && stripTestIdent(err2) == msg, err2);
2527 }
2528 
2529 // Issue 949
2530 unittest {
2531 	enum msg = "Path contains ':owner', but no parameter '_owner' defined.";
2532 
2533 	@path("/repos/")
2534 	interface IGithubPR {
2535 		@path(":owner/:repo/pulls")
2536 		string getPullRequests(string owner, string repo);
2537 	}
2538 	enum err = getInterfaceValidationError!IGithubPR;
2539 	static assert(err !is null && stripTestIdent(err) == msg, err);
2540 }
2541 
2542 // Issue 1017
2543 unittest {
2544 	interface TestSuccess { @path("/") void test(); }
2545 	interface TestSuccess2 { @path("/test/") void test(); }
2546 	interface TestFail { @path("//") void test(); }
2547 	interface TestFail2 { @path("/test//it/") void test(); }
2548 	static assert(getInterfaceValidationError!TestSuccess is null);
2549 	static assert(getInterfaceValidationError!TestSuccess2 is null);
2550 	static assert(stripTestIdent(getInterfaceValidationError!TestFail)
2551 		== "Path '//' contains empty entries.");
2552 	static assert(stripTestIdent(getInterfaceValidationError!TestFail2)
2553 		== "Path '/test//it/' contains empty entries.");
2554 }
2555 
2556 unittest {
2557 	interface NullPath  { @path(null) void test(); }
2558 	interface ExplicitlyEmptyPath { @path("") void test(); }
2559 	static assert(stripTestIdent(getInterfaceValidationError!NullPath)
2560 				  == "Path is null or empty");
2561 	static assert(stripTestIdent(getInterfaceValidationError!ExplicitlyEmptyPath)
2562 				  == "Path is null or empty");
2563 
2564 	// Note: Implicitly empty path are valid:
2565 	// interface ImplicitlyEmptyPath { void get(); }
2566 }
2567 
2568 // Accept @headerParam ref / out parameters
2569 unittest {
2570 	interface HeaderRef {
2571 		@headerParam("auth", "auth")
2572 		string getData(ref string auth);
2573 		string getData2(@viaHeader("auth") ref string auth);
2574 	}
2575 	static assert(getInterfaceValidationError!HeaderRef is null,
2576 		      stripTestIdent(getInterfaceValidationError!HeaderRef));
2577 
2578 	interface HeaderOut {
2579 		@headerParam("auth", "auth")
2580 		void getData(out string auth);
2581 		void getData(@viaHeader("auth") out string auth);
2582 	}
2583 	static assert(getInterfaceValidationError!HeaderOut is null,
2584 		      stripTestIdent(getInterfaceValidationError!HeaderOut));
2585 }
2586 
2587 // Reject unattributed / @queryParam or @bodyParam ref / out parameters
2588 unittest {
2589 	interface QueryRef {
2590 		@queryParam("auth", "auth")
2591 		string getData(ref string auth);
2592 	}
2593 	static assert(stripTestIdent(getInterfaceValidationError!QueryRef)
2594 		== "query parameter 'auth' cannot be ref");
2595 
2596 	interface QueryRefConst {
2597 		@queryParam("auth", "auth")
2598 		string getData(const ref string auth);
2599 	}
2600 	enum err1 = getInterfaceValidationError!QueryRefConst;
2601 	static assert(err1 is null, err1);
2602 
2603 	interface QueryOut {
2604 		@queryParam("auth", "auth")
2605 		void getData(out string auth);
2606 	}
2607 	static assert(stripTestIdent(getInterfaceValidationError!QueryOut)
2608 		== "query parameter 'auth' cannot be out");
2609 
2610 	interface BodyRef {
2611 		@bodyParam("auth", "auth")
2612 		string getData(ref string auth);
2613 	}
2614 	static assert(stripTestIdent(getInterfaceValidationError!BodyRef)
2615 		== "body_ parameter 'auth' cannot be ref",x);
2616 
2617 	interface BodyRefConst {
2618 		@bodyParam("auth", "auth")
2619 		string getData(const ref string auth);
2620 	}
2621 	enum err2 = getInterfaceValidationError!BodyRefConst;
2622 	static assert(err2 is null, err2);
2623 
2624 	interface BodyOut {
2625 		@bodyParam("auth", "auth")
2626 		void getData(out string auth);
2627 	}
2628 	static assert(stripTestIdent(getInterfaceValidationError!BodyOut)
2629 		== "body_ parameter 'auth' cannot be out");
2630 
2631 	// There's also the possibility of someone using an out unnamed
2632 	// parameter (don't ask me why), but this is catched as unnamed
2633 	// parameter, so we don't need to check it here.
2634 }
2635 
2636 private string stripTestIdent(string msg)
2637 @safe {
2638 	import std.string;
2639 	auto idx = msg.indexOf(": ");
2640 	return idx >= 0 ? msg[idx+2 .. $] : msg;
2641 }
2642 
2643 // Small helper for client code generation
2644 private string paramCTMap(string[string] params)
2645 @safe {
2646 	import std.array : appender, join;
2647 	if (!__ctfe)
2648 		assert (false, "This helper is only supposed to be called for codegen in RestClientInterface.");
2649 	auto app = appender!(string[]);
2650 	foreach (key, val; params) {
2651 		app ~= "\""~key~"\"";
2652 		app ~= val;
2653 	}
2654 	return app.data.join(", ");
2655 }
2656 
2657 package string stripTUnderscore(string name, RestInterfaceSettings settings)
2658 @safe {
2659 	if ((settings is null || settings.stripTrailingUnderscore)
2660 	    && name.endsWith("_"))
2661 		return name[0 .. $-1];
2662 	else return name;
2663 }
2664 
2665 // Workarounds @@DMD:9748@@, and maybe more
2666 package template GenCmp(string name, int id, string cmpTo) {
2667 	import std.string : format;
2668 	import std.conv : to;
2669 	enum Decl = q{
2670 		template %1$s(alias uda) {
2671 			enum %1$s = (uda.identifier == "%2$s");
2672 		}
2673 	}.format(Name, cmpTo);
2674 	enum Name = name~to!string(id);
2675 }
2676 
2677 // Ditto
2678 private template GenOrphan(int id) {
2679 	import std.string : format;
2680 	import std.conv : to;
2681 	enum Decl = q{
2682 		template %1$s(string name) {
2683 			enum %1$s = (uda.identifier == name);
2684 		}
2685 	}.format(Name);
2686 	enum Name = "OrphanCheck"~to!string(id);
2687 }
2688 
2689 // Workaround for issue #1045 / DMD bug 14375
2690 // Also, an example of policy-based design using this module.
2691 @safe unittest {
2692 	import std.traits, std.typetuple;
2693 	import vibe.internal.meta.codegen;
2694 	import vibe.internal.meta.typetuple;
2695 	import vibe.web.internal.rest.common : ParameterKind;
2696 
2697 	interface Policies {
2698 		@headerParam("auth", "Authorization")
2699 		string BasicAuth(string auth, ulong expiry) @safe;
2700 	}
2701 
2702 	@path("/keys/")
2703 	interface IKeys(alias AuthenticationPolicy = Policies.BasicAuth) {
2704 		static assert(is(FunctionTypeOf!AuthenticationPolicy == function),
2705 			      "Policies needs to be functions");
2706 		@path("/") @method(HTTPMethod.POST) @safe
2707 		mixin CloneFunctionDecl!(AuthenticationPolicy, true, "create");
2708 	}
2709 
2710 	class KeysImpl : IKeys!() {
2711 	override:
2712 		string create(string auth, ulong expiry) @safe {
2713 			return "4242-4242";
2714 		}
2715 	}
2716 
2717 	// Some sanity checks
2718         // Note: order is most likely implementation dependent.
2719 	// Good thing we only have one frontend...
2720 	alias WPA = WebParamAttribute;
2721 	static assert(Compare!(
2722 			      Group!(__traits(getAttributes, IKeys!().create)),
2723 			      Group!(PathAttribute("/"),
2724 				     MethodAttribute(HTTPMethod.POST),
2725 				     WPA(ParameterKind.header, "auth", "Authorization"))));
2726 
2727 	void register() @safe {
2728 		auto router = new URLRouter();
2729 		router.registerRestInterface(new KeysImpl());
2730 	}
2731 
2732 	void query() @safe {
2733 		auto client = new RestInterfaceClient!(IKeys!())("http://127.0.0.1:8080");
2734 		assert(client.create("Hello", 0) == "4242-4242");
2735 	}
2736 }
2737 
2738 @safe unittest { // @noRoute support in RestInterfaceClient
2739 	interface I {
2740 		void foo();
2741 		@noRoute int bar(void* someparam);
2742 	}
2743 	auto cli = new RestInterfaceClient!I("http://127.0.0.1/");
2744 }