1 /**
2 	Contains common functionality for the REST and WEB interface generators.
3 
4 	Copyright: © 2012-2017 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Михаил Страшун
7 */
8 module vibe.web.common;
9 
10 import vibe.http.common;
11 import vibe.http.server : HTTPServerRequest;
12 import vibe.data.json;
13 import vibe.internal.meta.uda : onlyAsUda, UDATuple;
14 
15 import std.meta : AliasSeq;
16 static import std.utf;
17 static import std.string;
18 import std.traits : getUDAs, ReturnType;
19 import std.typecons : Nullable;
20 
21 
22 /**
23 	Adjusts the naming convention for a given function name to the specified style.
24 
25 	The input name is assumed to be in lowerCamelCase (D-style) or PascalCase. Acronyms
26 	(e.g. "HTML") should be written all caps
27 */
28 string adjustMethodStyle(string name, MethodStyle style)
29 @safe {
30 	if (!name.length) {
31 		return "";
32 	}
33 
34 	import std.uni;
35 
36 	string separate(char separator, bool upper_case)
37 	{
38 		string ret;
39 		size_t start = 0, i = 0;
40 		while (i < name.length) {
41 			// skip acronyms
42 			while (i < name.length && (i+1 >= name.length || (name[i+1] >= 'A' && name[i+1] <= 'Z'))) {
43 				std.utf.decode(name, i);
44 			}
45 
46 			// skip the main (lowercase) part of a word
47 			while (i < name.length && !(name[i] >= 'A' && name[i] <= 'Z')) {
48 				std.utf.decode(name, i);
49 			}
50 
51 			// add a single word
52 			if( ret.length > 0 ) {
53 				ret ~= separator;
54 			}
55 			ret ~= name[start .. i];
56 
57 			// quick skip the capital and remember the start of the next word
58 			start = i;
59 			if (i < name.length) {
60 				std.utf.decode(name, i);
61 			}
62 		}
63 		if (start < name.length) {
64 			ret ~= separator ~ name[start .. $];
65 		}
66 		return upper_case ? std..string.toUpper(ret) : std..string.toLower(ret);
67 	}
68 
69 	final switch(style) {
70 		case MethodStyle.unaltered:
71 			return name;
72 		case MethodStyle.camelCase:
73 			size_t i = 0;
74 			foreach (idx, dchar ch; name) {
75 				if (isUpper(ch)) {
76 					i = idx;
77 				}
78 				else break;
79 			}
80 			if (i == 0) {
81 				std.utf.decode(name, i);
82 				return std..string.toLower(name[0 .. i]) ~ name[i .. $];
83 			} else {
84 				std.utf.decode(name, i);
85 				if (i < name.length) {
86 					return std..string.toLower(name[0 .. i-1]) ~ name[i-1 .. $];
87 				}
88 				else {
89 					return std..string.toLower(name);
90 				}
91 			}
92 		case MethodStyle.pascalCase:
93 			size_t idx = 0;
94 			std.utf.decode(name, idx);
95 			return std..string.toUpper(name[0 .. idx]) ~ name[idx .. $];
96 		case MethodStyle.lowerCase:
97 			return std..string.toLower(name);
98 		case MethodStyle.upperCase:
99 			return std..string.toUpper(name);
100 		case MethodStyle.lowerUnderscored: return separate('_', false);
101 		case MethodStyle.upperUnderscored: return separate('_', true);
102 		case MethodStyle.lowerDashed: return separate('-', false);
103 		case MethodStyle.upperDashed: return separate('-', true);
104 	}
105 }
106 
107 unittest
108 {
109 	assert(adjustMethodStyle("methodNameTest", MethodStyle.unaltered) == "methodNameTest");
110 	assert(adjustMethodStyle("methodNameTest", MethodStyle.camelCase) == "methodNameTest");
111 	assert(adjustMethodStyle("methodNameTest", MethodStyle.pascalCase) == "MethodNameTest");
112 	assert(adjustMethodStyle("methodNameTest", MethodStyle.lowerCase) == "methodnametest");
113 	assert(adjustMethodStyle("methodNameTest", MethodStyle.upperCase) == "METHODNAMETEST");
114 	assert(adjustMethodStyle("methodNameTest", MethodStyle.lowerUnderscored) == "method_name_test");
115 	assert(adjustMethodStyle("methodNameTest", MethodStyle.upperUnderscored) == "METHOD_NAME_TEST");
116 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.unaltered) == "MethodNameTest");
117 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.camelCase) == "methodNameTest");
118 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.pascalCase) == "MethodNameTest");
119 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.lowerCase) == "methodnametest");
120 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.upperCase) == "METHODNAMETEST");
121 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.lowerUnderscored) == "method_name_test");
122 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.upperUnderscored) == "METHOD_NAME_TEST");
123 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.lowerDashed) == "method-name-test");
124 	assert(adjustMethodStyle("MethodNameTest", MethodStyle.upperDashed) == "METHOD-NAME-TEST");
125 	assert(adjustMethodStyle("Q", MethodStyle.lowerUnderscored) == "q");
126 	assert(adjustMethodStyle("getHTML", MethodStyle.lowerUnderscored) == "get_html");
127 	assert(adjustMethodStyle("getHTMLEntity", MethodStyle.lowerUnderscored) == "get_html_entity");
128 	assert(adjustMethodStyle("ID", MethodStyle.lowerUnderscored) == "id");
129 	assert(adjustMethodStyle("ID", MethodStyle.pascalCase) == "ID");
130 	assert(adjustMethodStyle("ID", MethodStyle.camelCase) == "id");
131 	assert(adjustMethodStyle("IDTest", MethodStyle.lowerUnderscored) == "id_test");
132 	assert(adjustMethodStyle("IDTest", MethodStyle.pascalCase) == "IDTest");
133 	assert(adjustMethodStyle("IDTest", MethodStyle.camelCase) == "idTest");
134 	assert(adjustMethodStyle("anyA", MethodStyle.lowerUnderscored) == "any_a", adjustMethodStyle("anyA", MethodStyle.lowerUnderscored));
135 }
136 
137 
138 /**
139 	Determines the HTTP method and path for a given function symbol.
140 
141 	The final method and path are determined from the function name, as well as
142 	any $(D @method) and $(D @path) attributes that may be applied to it.
143 
144 	This function is designed for CTFE usage and will assert at run time.
145 
146 	Returns:
147 		A tuple of three elements is returned:
148 		$(UL
149 			$(LI flag "was UDA used to override path")
150 			$(LI $(D HTTPMethod) extracted)
151 			$(LI URL path extracted)
152 		)
153  */
154 auto extractHTTPMethodAndName(alias Func, bool indexSpecialCase)()
155 {
156 	if (!__ctfe)
157 		assert(false);
158 
159 	struct HandlerMeta
160 	{
161 		bool hadPathUDA;
162 		HTTPMethod method;
163 		string url;
164 	}
165 
166 	import vibe.internal.meta.uda : findFirstUDA;
167 	import vibe.internal.meta.traits : isPropertySetter,
168 		isPropertyGetter;
169 	import std.algorithm : startsWith;
170 	import std.typecons : Nullable;
171 
172 	immutable httpMethodPrefixes = [
173 		HTTPMethod.GET    : [ "get", "query" ],
174 		HTTPMethod.PUT    : [ "put", "set" ],
175 		HTTPMethod.PATCH  : [ "update", "patch" ],
176 		HTTPMethod.POST   : [ "add", "create", "post" ],
177 		HTTPMethod.DELETE : [ "remove", "erase", "delete" ],
178 	];
179 
180 	enum name = __traits(identifier, Func);
181 	alias T = typeof(&Func);
182 
183 	Nullable!HTTPMethod udmethod;
184 	Nullable!string udurl;
185 
186 	// Cases may conflict and are listed in order of priority
187 
188 	// Workaround for Nullable incompetence
189 	enum uda1 = findFirstUDA!(MethodAttribute, Func);
190 	enum uda2 = findFirstUDA!(PathAttribute, Func);
191 
192 	static if (uda1.found) {
193 		udmethod = uda1.value;
194 	}
195 	static if (uda2.found) {
196 		udurl = uda2.value;
197 	}
198 
199 	// Everything is overriden, no further analysis needed
200 	if (!udmethod.isNull() && !udurl.isNull()) {
201 		return HandlerMeta(true, udmethod.get(), udurl.get());
202 	}
203 
204 	// Anti-copy-paste delegate
205 	typeof(return) udaOverride( HTTPMethod method, string url ){
206 		return HandlerMeta(
207 			!udurl.isNull(),
208 			udmethod.isNull() ? method : udmethod.get(),
209 			udurl.isNull() ? url : udurl.get()
210 		);
211 	}
212 
213 	if (isPropertyGetter!T) {
214 		return udaOverride(HTTPMethod.GET, name);
215 	}
216 	else if(isPropertySetter!T) {
217 		return udaOverride(HTTPMethod.PUT, name);
218 	}
219 	else {
220 		foreach (method, prefixes; httpMethodPrefixes) {
221 			foreach (prefix; prefixes) {
222 				import std.uni : isLower;
223 				if (name.startsWith(prefix) && (name.length == prefix.length || !name[prefix.length].isLower)) {
224 					string tmp = name[prefix.length..$];
225 					return udaOverride(method, tmp.length ? tmp : "/");
226 				}
227 			}
228 		}
229 
230 		static if (indexSpecialCase && name == "index") {
231 			return udaOverride(HTTPMethod.GET, "/");
232 		} else
233 			return udaOverride(HTTPMethod.POST, name);
234 	}
235 }
236 
237 unittest
238 {
239 	interface Sample
240 	{
241 		string getInfo();
242 		string updateDescription();
243 
244 		@method(HTTPMethod.DELETE)
245 		string putInfo();
246 
247 		@path("matters")
248 		string getMattersnot();
249 
250 		@path("compound/path") @method(HTTPMethod.POST)
251 		string mattersnot();
252 
253 		string get();
254 
255 		string posts();
256 
257 		string patches();
258 	}
259 
260 	enum ret1 = extractHTTPMethodAndName!(Sample.getInfo, false,);
261 	static assert (ret1.hadPathUDA == false);
262 	static assert (ret1.method == HTTPMethod.GET);
263 	static assert (ret1.url == "Info");
264 	enum ret2 = extractHTTPMethodAndName!(Sample.updateDescription, false);
265 	static assert (ret2.hadPathUDA == false);
266 	static assert (ret2.method == HTTPMethod.PATCH);
267 	static assert (ret2.url == "Description");
268 	enum ret3 = extractHTTPMethodAndName!(Sample.putInfo, false);
269 	static assert (ret3.hadPathUDA == false);
270 	static assert (ret3.method == HTTPMethod.DELETE);
271 	static assert (ret3.url == "Info");
272 	enum ret4 = extractHTTPMethodAndName!(Sample.getMattersnot, false);
273 	static assert (ret4.hadPathUDA == true);
274 	static assert (ret4.method == HTTPMethod.GET);
275 	static assert (ret4.url == "matters");
276 	enum ret5 = extractHTTPMethodAndName!(Sample.mattersnot, false);
277 	static assert (ret5.hadPathUDA == true);
278 	static assert (ret5.method == HTTPMethod.POST);
279 	static assert (ret5.url == "compound/path");
280 	enum ret6 = extractHTTPMethodAndName!(Sample.get, false);
281 	static assert (ret6.hadPathUDA == false);
282 	static assert (ret6.method == HTTPMethod.GET);
283 	static assert (ret6.url == "/");
284 	enum ret7 = extractHTTPMethodAndName!(Sample.posts, false);
285 	static assert(ret7.hadPathUDA == false);
286 	static assert(ret7.method == HTTPMethod.POST);
287 	static assert(ret7.url == "posts");
288 	enum ret8 = extractHTTPMethodAndName!(Sample.patches, false);
289 	static assert(ret8.hadPathUDA == false);
290 	static assert(ret8.method == HTTPMethod.POST);
291 	static assert(ret8.url == "patches");
292 }
293 
294 
295 /**
296     Attribute to define the content type for methods.
297 
298     This currently applies only to methods returning an $(D InputStream) or
299     $(D ubyte[]).
300 */
301 ContentTypeAttribute contentType(string data)
302 @safe {
303 	if (!__ctfe)
304 		assert(false, onlyAsUda!__FUNCTION__);
305 	return ContentTypeAttribute(data);
306 }
307 
308 
309 /**
310 	Attribute to force a specific HTTP method for an interface method.
311 
312 	The usual URL generation rules are still applied, so if there
313 	are any "get", "query" or similar prefixes, they are filtered out.
314  */
315 MethodAttribute method(HTTPMethod data)
316 @safe {
317 	if (!__ctfe)
318 		assert(false, onlyAsUda!__FUNCTION__);
319 	return MethodAttribute(data);
320 }
321 
322 ///
323 unittest {
324 	interface IAPI
325 	{
326 		// Will be "POST /info" instead of default "GET /info"
327 		@method(HTTPMethod.POST) string getInfo();
328 	}
329 }
330 
331 
332 /**
333 	Attibute to force a specific URL path.
334 
335 	This attribute can be applied either to an interface itself, in which
336 	case it defines the root path for all methods within it,
337 	or on any function, in which case it defines the relative path
338 	of this method.
339 	Path are always relative, even path on interfaces, as you can
340 	see in the example below.
341 
342 	See_Also: $(D rootPathFromName) for automatic name generation.
343 */
344 PathAttribute path(string data)
345 @safe {
346 	if (!__ctfe)
347 		assert(false, onlyAsUda!__FUNCTION__);
348 	return PathAttribute(data);
349 }
350 
351 ///
352 @safe unittest {
353 	@path("/foo")
354 	interface IAPI
355 	{
356 		@path("info2") string getInfo() @safe;
357 	}
358 
359 	class API : IAPI {
360 		string getInfo() @safe { return "Hello, World!"; }
361 	}
362 
363 	void test()
364 	@safe {
365 		import vibe.http.router;
366 		import vibe.web.rest;
367 
368 		auto router = new URLRouter;
369 
370 		// Tie IAPI.getInfo to "GET /root/foo/info2"
371 		router.registerRestInterface!IAPI(new API(), "/root/");
372 
373 		// Or just to "GET /foo/info2"
374 		router.registerRestInterface!IAPI(new API());
375 
376 		// ...
377 	}
378 }
379 
380 
381 /// Convenience alias to generate a name from the interface's name.
382 @property PathAttribute rootPathFromName()
383 @safe {
384 	if (!__ctfe)
385 		assert(false, onlyAsUda!__FUNCTION__);
386 	return PathAttribute("");
387 }
388 ///
389 @safe unittest
390 {
391 	import vibe.http.router;
392 	import vibe.web.rest;
393 
394 	@rootPathFromName
395 	interface IAPI
396 	{
397 		int getFoo() @safe;
398 	}
399 
400 	class API : IAPI
401 	{
402 		int getFoo()
403 		{
404 			return 42;
405 		}
406 	}
407 
408 	auto router = new URLRouter();
409 	registerRestInterface(router, new API());
410 	auto routes= router.getAllRoutes();
411 
412 	assert(routes[0].pattern == "/iapi/foo" && routes[0].method == HTTPMethod.GET);
413 }
414 
415 
416 /**
417 	Methods marked with this attribute will not be treated as web endpoints.
418 
419 	This attribute enables the definition of public methods that do not take
420 	part in the interface genration process.
421 */
422 @property NoRouteAttribute noRoute()
423 {
424 	import vibe.web.common : onlyAsUda;
425 	if (!__ctfe)
426 		assert(false, onlyAsUda!__FUNCTION__);
427 	return NoRouteAttribute.init;
428 }
429 
430 ///
431 unittest {
432 	interface IAPI {
433 		// Accessible as "GET /info"
434 		string getInfo();
435 
436 		// Not accessible over HTTP
437 		@noRoute
438 		int getFoo();
439 	}
440 }
441 
442 
443 /**
444  	Respresents a Rest error response
445 */
446 class RestException : HTTPStatusException {
447 	private {
448 		Json m_jsonResult;
449 	}
450 
451     ///
452 	this (int status, string result, string file = __FILE__, int line = __LINE__,
453 		Throwable next = null) @safe
454 	{
455 		Json jsonResult = Json.emptyObject;
456 		jsonResult["statusMessage"] = result;
457 		this(status, jsonResult, file, line);
458 	}
459 
460 	///
461 	this (int status, Json jsonResult, string file = __FILE__, int line = __LINE__,
462 		Throwable next = null) @safe
463 	{
464 		if (jsonResult.type == Json.Type.object && jsonResult["statusMessage"].type == Json.Type..string) {
465 			super(status, jsonResult["statusMessage"].get!string, file, line, next);
466 		}
467 		else {
468 			super(status, httpStatusText(status) ~ " (" ~ jsonResult.toString() ~ ")", file, line, next);
469 		}
470 
471 		m_jsonResult = jsonResult;
472 	}
473 
474 	/// The result text reported to the client
475 	@property inout(Json) jsonResult () inout nothrow pure @safe @nogc
476 	{
477 		return m_jsonResult;
478 	}
479 }
480 
481 /// private
482 package struct ContentTypeAttribute
483 {
484 	string data;
485 	alias data this;
486 }
487 
488 /// private
489 package struct MethodAttribute
490 {
491 	HTTPMethod data;
492 	alias data this;
493 }
494 
495 
496 /** UDA for using a custom serializer for the method return value.
497 
498 	Instead of using the default serializer (JSON), this allows to define
499 	custom serializers. Multiple serializers can be specified and will be
500 	matched against the `Accept` header of the HTTP request.
501 
502 	Params:
503 		serialize = An alias to a generic function taking an output range as
504 			its first argument and the value to be serialized as its second
505 			argument. The result of the serialization is written byte-wise into
506 			the output range.
507 		deserialize = An alias to a generic function taking a forward range
508 			as its first argument and a reference to the value that is to be
509 			deserialized.
510 		content_type = The MIME type of the serialized representation.
511 */
512 alias resultSerializer(alias serialize, alias deserialize, string content_type)
513 	= ResultSerializer!(serialize, deserialize, content_type);
514 
515 ///
516 unittest {
517 	import std.bitmanip : bigEndianToNative, nativeToBigEndian;
518 
519 	interface MyRestInterface {
520 		static struct Point {
521 			int x, y;
522 		}
523 
524 		static void serialize(R, T)(ref R output_range, const ref T value)
525 		{
526 			static assert(is(T == Point)); // only Point supported in this example
527 			output_range.put(nativeToBigEndian(value.x));
528 			output_range.put(nativeToBigEndian(value.y));
529 		}
530 
531 		static T deserialize(T, R)(R input_range)
532 		{
533 			static assert(is(T == Point)); // only Point supported in this example
534 			T ret;
535 			ubyte[4] xbuf, ybuf;
536 			input_range.takeExactly(4).copy(xbuf[]);
537 			input_range.takeExactly(4).copy(ybuf[]);
538 			ret.x = bigEndianToNative!int(xbuf);
539 			ret.y = bigEndianToNative!int(ybuf);
540 			return ret;
541 		}
542 
543 		// serialize as binary data in network byte order
544 		@resultSerializer!(serialize, deserialize, "application/binary")
545 		Point getPoint();
546 	}
547 }
548 
549 /// private
550 struct ResultSerializer(alias ST, alias DT, string ContentType) {
551 	enum contentType = ContentType;
552 	alias serialize = ST;
553 	alias deserialize = DT;
554 }
555 
556 
557 package void defaultSerialize (alias P, T, RT) (ref RT output_range, const scope ref T value)
558 {
559 	static struct R {
560 		typeof(output_range) underlying;
561 		void put(char ch) { underlying.put(ch); }
562 		void put(scope const(char)[] ch) { underlying.put(cast(const(ubyte)[])ch); }
563 	}
564 	auto dst = R(output_range);
565 	// NOTE: serializeWithPolicy does not take value as scope due to issues
566 	//       deeply buried in the standard library
567 	() @trusted { return value; } ().serializeWithPolicy!(JsonStringSerializer!R, P) (dst);
568 }
569 
570 package T defaultDeserialize (alias P, T, R) (R input_range)
571 {
572 	return deserializeWithPolicy!(JsonStringSerializer!(typeof(std..string.assumeUTF(input_range))), P, T)
573 		(std..string.assumeUTF(input_range));
574 }
575 
576 package alias DefaultSerializerT = ResultSerializer!(
577 	defaultSerialize, defaultDeserialize, "application/json; charset=UTF-8");
578 
579 
580 /// Convenience template to get all the ResultSerializers for a function
581 package template ResultSerializersT(alias func) {
582 	alias DefinedSerializers = getUDAs!(func, ResultSerializer);
583 	static if (DefinedSerializers.length)
584 		alias ResultSerializersT = DefinedSerializers;
585 	else
586 		alias ResultSerializersT = AliasSeq!(DefaultSerializerT);
587 }
588 
589 ///
590 package template SerPolicyT (Iface)
591 {
592 	static if (getUDAs!(Iface, SerPolicy).length)
593 	{
594 		alias SerPolicyT = getUDAs!(Iface, SerPolicy)[0];
595 	}
596 	else
597 	{
598 		alias SerPolicyT = SerPolicy!DefaultPolicy;
599 	}
600 }
601 
602 ///
603 package struct SerPolicy (alias PolicyTemplatePar)
604 {
605 	alias PolicyTemplate = PolicyTemplatePar;
606 }
607 
608 ///
609 public alias serializationPolicy (Args...) = SerPolicy!(Args);
610 
611 unittest
612 {
613 	import vibe.data.serialization : Base64ArrayPolicy;
614 	import std.array : appender;
615 	import std.conv : to;
616 
617 	struct X
618 	{
619 		string name = "test";
620 		ubyte[] arr = [138, 245, 231, 234, 142, 132, 142];
621 	}
622 	X x;
623 
624 	// Interface using Base64 array serialization
625 	@serializationPolicy!(Base64ArrayPolicy)
626 	interface ITestBase64
627 	{
628 		@safe X getTest();
629 	}
630 
631 	alias serPolicyFound = SerPolicyT!ITestBase64;
632 	alias resultSerializerFound = ResultSerializersT!(ITestBase64.getTest)[0];
633 
634 	// serialization test with base64 encoding
635 	auto output = appender!string();
636 
637 	resultSerializerFound.serialize!(serPolicyFound.PolicyTemplate)(output, x);
638 	auto serialized = output.data;
639 	assert(serialized == `{"name":"test","arr":"ivXn6o6Ejg=="}`,
640 			"serialization is not correct, produced: " ~ serialized);
641 
642 	// deserialization test with base64 encoding
643 	auto deserialized = serialized.deserializeWithPolicy!(JsonStringSerializer!string, serPolicyFound.PolicyTemplate, X)();
644 	assert(deserialized.name == "test", "deserialization of `name` is not correct, produced: " ~ deserialized.name);
645 	assert(deserialized.arr == [138, 245, 231, 234, 142, 132, 142],
646 			"deserialization of `arr` is not correct, produced: " ~ to!string(deserialized.arr));
647 
648 	// Interface NOT using Base64 array serialization
649 	interface ITestPlain
650 	{
651 		@safe X getTest();
652 	}
653 
654 	alias plainSerPolicyFound = SerPolicyT!ITestPlain;
655 	alias plainResultSerializerFound = ResultSerializersT!(ITestPlain.getTest)[0];
656 
657 	// serialization test without base64 encoding
658 	output = appender!string();
659 	plainResultSerializerFound.serialize!(plainSerPolicyFound.PolicyTemplate)(output, x);
660 	serialized = output.data;
661 	assert(serialized == `{"name":"test","arr":[138,245,231,234,142,132,142]}`,
662 			"serialization is not correct, produced: " ~ serialized);
663 
664 	// deserialization test without base64 encoding
665 	deserialized = serialized.deserializeWithPolicy!(JsonStringSerializer!string, plainSerPolicyFound.PolicyTemplate, X)();
666 	assert(deserialized.name == "test", "deserialization of `name` is not correct, produced: " ~ deserialized.name);
667 	assert(deserialized.arr == [138, 245, 231, 234, 142, 132, 142],
668 			"deserialization of `arr` is not correct, produced: " ~ to!string(deserialized.arr));
669 }
670 
671 /**
672  * This struct contains the name of a route specified by the `path` function.
673  */
674 struct PathAttribute
675 {
676 	/// The specified path
677 	string data;
678 	alias data this;
679 }
680 
681 /// private
682 package struct NoRouteAttribute {}
683 
684 /**
685  * This struct contains a mapping between the name used by HTTP (field)
686  * and the parameter (identifier) name of the function.
687  */
688 public struct WebParamAttribute {
689 	import vibe.web.internal.rest.common : ParameterKind;
690 
691 	/// The type of the WebParamAttribute
692 	ParameterKind origin;
693 	/// Parameter name (function parameter name).
694 	string identifier;
695 	/// The meaning of this field depends on the origin. (HTTP request name)
696 	string field;
697 }
698 
699 
700 /**
701  * Declare that a parameter will be transmitted to the API through the body.
702  *
703  * It will be serialized as part of a JSON object.
704  * The serialization format is currently not customizable.
705  * If no fieldname is given, the entire body is serialized into the object.
706  *
707  * There are currently two kinds of symbol to do this: `viaBody` and `bodyParam`.
708  * `viaBody` should be applied to the parameter itself, while `bodyParam`
709  * is applied to the function.
710  * `bodyParam` was introduced long before the D language for UDAs on parameters
711  * (introduced in DMD v2.082.0), and will be deprecated in a future release.
712  *
713  * Params:
714  *   identifier = The name of the parameter to customize. A compiler error will be issued on mismatch.
715  *   field = The name of the field in the JSON object.
716  *
717  * ----
718  * void ship(@viaBody("package") int pack);
719  * // The server will receive the following body for a call to ship(42):
720  * // { "package": 42 }
721  * ----
722  */
723 WebParamAttribute viaBody(string field = null)
724 @safe {
725 	import vibe.web.internal.rest.common : ParameterKind;
726 	if (!__ctfe)
727 		assert(false, onlyAsUda!__FUNCTION__);
728 	return WebParamAttribute(ParameterKind.body_, null, field);
729 }
730 
731 /// Ditto
732 WebParamAttribute bodyParam(string identifier, string field) @safe
733 in {
734 	assert(field.length > 0, "fieldname can't be empty.");
735 }
736 do
737 {
738 	import vibe.web.internal.rest.common : ParameterKind;
739 	if (!__ctfe)
740 		assert(false, onlyAsUda!__FUNCTION__);
741 	return WebParamAttribute(ParameterKind.body_, identifier, field);
742 }
743 
744 /// ditto
745 WebParamAttribute bodyParam(string identifier)
746 @safe {
747 	import vibe.web.internal.rest.common : ParameterKind;
748 	if (!__ctfe)
749 		assert(false, onlyAsUda!__FUNCTION__);
750 	return WebParamAttribute(ParameterKind.body_, identifier, "");
751 }
752 
753 /**
754  * Declare that a parameter will be transmitted to the API through the headers.
755  *
756  * If the parameter is a string, or any scalar type (float, int, char[], ...), it will be send as a string.
757  * If it's an aggregate, it will be serialized as JSON.
758  * However, passing aggregate via header isn't a good practice and should be avoided for new production code.
759  *
760  * There are currently two kinds of symbol to do this: `viaHeader` and `headerParam`.
761  * `viaHeader` should be applied to the parameter itself, while `headerParam`
762  * is applied to the function.
763  * `headerParam` was introduced long before the D language for UDAs on parameters
764  * (introduced in DMD v2.082.0), and will be deprecated in a future release.
765  *
766  * Params:
767  *   identifier = The name of the parameter to customize. A compiler error will be issued on mismatch.
768  *   field = The name of the header field to use (e.g: 'Accept', 'Content-Type'...).
769  *
770  * ----
771  * // The server will receive the content of the "Authorization" header.
772  * void login(@viaHeader("Authorization") string auth);
773  * ----
774  */
775 WebParamAttribute viaHeader(string field)
776 @safe {
777 	import vibe.web.internal.rest.common : ParameterKind;
778 	if (!__ctfe)
779 		assert(false, onlyAsUda!__FUNCTION__);
780 	return WebParamAttribute(ParameterKind.header, null, field);
781 }
782 
783 /// Ditto
784 WebParamAttribute headerParam(string identifier, string field)
785 @safe {
786 	import vibe.web.internal.rest.common : ParameterKind;
787 	if (!__ctfe)
788 		assert(false, onlyAsUda!__FUNCTION__);
789 	return WebParamAttribute(ParameterKind.header, identifier, field);
790 }
791 
792 /**
793  * Declare that a parameter will be transmitted to the API through the query string.
794  *
795  * It will be serialized as part of a JSON object, and will go through URL serialization.
796  * The serialization format is not customizable.
797  *
798  * There are currently two kinds of symbol to do this: `viaQuery` and `queryParam`.
799  * `viaQuery` should be applied to the parameter itself, while `queryParam`
800  * is applied to the function.
801  * `queryParam` was introduced long before the D language for UDAs on parameters
802  * (introduced in DMD v2.082.0), and will be deprecated in a future release.
803  *
804  * Params:
805  *   identifier = The name of the parameter to customize. A compiler error will be issued on mismatch.
806  *   field = The field name to use.
807  *
808  * ----
809  * // For a call to postData("D is awesome"), the server will receive the query:
810  * // POST /data?test=%22D is awesome%22
811  * void postData(@viaQuery("test") string data);
812  * ----
813  */
814 WebParamAttribute viaQuery(string field)
815 @safe {
816 	import vibe.web.internal.rest.common : ParameterKind;
817 	if (!__ctfe)
818 		assert(false, onlyAsUda!__FUNCTION__);
819 	return WebParamAttribute(ParameterKind.query, null, field);
820 }
821 
822 /// Ditto
823 WebParamAttribute queryParam(string identifier, string field)
824 @safe {
825 	import vibe.web.internal.rest.common : ParameterKind;
826 	if (!__ctfe)
827 		assert(false, onlyAsUda!__FUNCTION__);
828 	return WebParamAttribute(ParameterKind.query, identifier, field);
829 }
830 
831 /**
832 	Determines the naming convention of an identifier.
833 */
834 enum MethodStyle
835 {
836 	/// Special value for free-style conventions
837 	unaltered,
838 	/// camelCaseNaming
839 	camelCase,
840 	/// PascalCaseNaming
841 	pascalCase,
842 	/// lowercasenaming
843 	lowerCase,
844 	/// UPPERCASENAMING
845 	upperCase,
846 	/// lower_case_naming
847 	lowerUnderscored,
848 	/// UPPER_CASE_NAMING
849 	upperUnderscored,
850 	/// lower-case-naming
851 	lowerDashed,
852 	/// UPPER-CASE-NAMING
853 	upperDashed,
854 }
855 
856 
857 /// Speficies how D fields are mapped to form field names
858 enum NestedNameStyle {
859 	underscore, /// Use underscores to separate fields and array indices
860 	d           /// Use native D style and separate fields by dots and put array indices into brackets
861 }
862 
863 
864 // concatenates two URL parts avoiding any duplicate slashes
865 // in resulting URL. `trailing` defines of result URL must
866 // end with slash
867 package string concatURL(string prefix, string url, bool trailing = false)
868 @safe {
869 	import std.algorithm : startsWith, endsWith;
870 
871 	auto pre = prefix.endsWith("/");
872 	auto post = url.startsWith("/");
873 
874 	if (!url.length) return trailing && !pre ? prefix ~ "/" : prefix;
875 
876 	auto suffix = trailing && !url.endsWith("/") ? "/" : null;
877 
878 	if (pre) {
879 		// "/" is ASCII, so can just slice
880 		if (post) return prefix ~ url[1 .. $] ~ suffix;
881 		else return prefix ~ url ~ suffix;
882 	} else {
883 		if (post) return prefix ~ url ~ suffix;
884 		else return prefix ~ "/" ~ url ~ suffix;
885 	}
886 }
887 
888 @safe unittest {
889 	assert(concatURL("/test/", "/it/", false) == "/test/it/");
890 	assert(concatURL("/test", "it/", false) == "/test/it/");
891 	assert(concatURL("/test", "it", false) == "/test/it");
892 	assert(concatURL("/test", "", false) == "/test");
893 	assert(concatURL("/test/", "", false) == "/test/");
894 	assert(concatURL("/test/", "/it/", true) == "/test/it/");
895 	assert(concatURL("/test", "it/", true) == "/test/it/");
896 	assert(concatURL("/test", "it", true) == "/test/it/");
897 	assert(concatURL("/test", "", true) == "/test/");
898 	assert(concatURL("/test/", "", true) == "/test/");
899 }
900 
901 
902 /// private
903 template isNullable(T) {
904 	import std.traits;
905 	enum isNullable = isInstanceOf!(Nullable, T);
906 }
907 
908 static assert(isNullable!(Nullable!int));
909 
910 package struct ParamError {
911 	string field;
912 	string text;
913 }
914 
915 package enum ParamResult {
916 	ok,
917 	skipped,
918 	error
919 }
920 
921 // maximum array index in the parameter fields.
922 private enum MAX_ARR_INDEX = 0xffff;
923 
924 // handle the actual data inside the parameter
925 private ParamResult processFormParam(T)(scope string data, string fieldname, ref T dst, ref ParamError err)
926 {
927 	static if (is(T == bool))
928 	{
929 		// getting here means the item is present, set to true.
930 		dst = true;
931 		return ParamResult.ok;
932 	}
933 	else
934 	{
935 		if (!data.webConvTo(dst, err)) {
936 			err.field = fieldname;
937 			return ParamResult.error;
938 		}
939 		return ParamResult.ok;
940 	}
941 }
942 
943 // NOTE: dst is assumed to be uninitialized
944 package ParamResult readFormParamRec(T)(scope HTTPServerRequest req, ref T dst, string fieldname, bool required, NestedNameStyle style, ref ParamError err)
945 {
946 	import std.traits;
947 	import std.typecons;
948 	import vibe.data.serialization;
949 	import std.algorithm : startsWith;
950 
951 	static if (isStaticArray!T || (isDynamicArray!T && !isSomeString!(OriginalType!T))) {
952 		alias EL = typeof(T.init[0]);
953 		enum isSimpleElement = !(isDynamicArray!EL && !isSomeString!(OriginalType!EL)) &&
954 			!isStaticArray!EL &&
955 			!(is(EL == struct) &&
956 					!is(typeof(EL.fromString(string.init))) &&
957 					!is(typeof(EL.fromStringValidate(string.init, null))) &&
958 					!is(typeof(EL.fromISOExtString(string.init))));
959 
960 		static if (isStaticArray!T)
961 		{
962 			bool[T.length] seen;
963 		}
964 		else
965 		{
966 			static assert(!is(EL == bool),
967 			  "Boolean arrays are not allowed, because their length cannot " ~
968 			  "be uniquely determined. Use a static array instead.");
969 			// array to check for duplicates
970 			dst = T.init;
971 			bool[] seen;
972 		}
973 		// process the items in the order they appear.
974 		char indexSep = style == NestedNameStyle.d ? '[' : '_';
975 		const minLength = fieldname.length + (style == NestedNameStyle.d ? 2 : 1);
976 		const indexTrailer = style == NestedNameStyle.d ? "]" : "";
977 
978 		ParamResult processItems(DL)(DL dlist)
979 		{
980 			foreach (k, v; dlist.byKeyValue)
981 			{
982 				if (k.length < minLength)
983 					// sanity check to prevent out of bounds
984 					continue;
985 				if (k.startsWith(fieldname) && k[fieldname.length] == indexSep)
986 				{
987 					// found a potential match
988 					string key = k[fieldname.length + 1 .. $];
989 					size_t idx;
990 					if (key == indexTrailer)
991 					{
992 						static if (isSimpleElement)
993 						{
994 							// this is a non-indexed array item. Find an empty slot, or expand the array
995 							import std.algorithm : countUntil;
996 							idx = seen[].countUntil(false);
997 							static if (isStaticArray!T)
998 							{
999 								if (idx == size_t.max)
1000 								{
1001 									// ignore extras, and we know there are no more matches to come.
1002 									break;
1003 								}
1004 							}
1005 							else if (idx == size_t.max)
1006 							{
1007 								// append to the end.
1008 								idx = dst.length;
1009 							}
1010 						}
1011 						else
1012 						{
1013 							// not valid for non-simple elements.
1014 							continue;
1015 						}
1016 					}
1017 					else
1018 					{
1019 						import std.conv;
1020 						idx = key.parse!size_t;
1021 						static if (isStaticArray!T)
1022 						{
1023 							if (idx >= T.length)
1024 								// keep existing behavior, ignore extras
1025 								continue;
1026 						}
1027 						else if (idx > MAX_ARR_INDEX)
1028 						{
1029 							// Getting a bit large, we don't want to allow DOS attacks.
1030 							err.field = k;
1031 							err.text = "Maximum index exceeded";
1032 							return ParamResult.error;
1033 						}
1034 						static if (isSimpleElement)
1035 						{
1036 							if (key != indexTrailer)
1037 								// this can't be a match, simple elements are parsed from
1038 								// the string, there should be no more to the key.
1039 								continue;
1040 						}
1041 						else
1042 						{
1043 							// ensure there's more than just the index trailer
1044 							if (key.length == indexTrailer.length || !key.startsWith(indexTrailer))
1045 								// not a valid entry. ignore this entry to preserve existing behavior.
1046 								continue;
1047 						}
1048 					}
1049 
1050 					static if (!isStaticArray!T)
1051 					{
1052 						// check to see if we need to expand the array
1053 						if (dst.length <= idx)
1054 						{
1055 							dst.length = idx + 1;
1056 							seen.length = idx + 1;
1057 						}
1058 					}
1059 
1060 					if (seen[idx])
1061 					{
1062 						// don't store it twice
1063 						continue;
1064 					}
1065 					seen[idx] = true;
1066 
1067 					static if (isSimpleElement)
1068 					{
1069 						auto result = processFormParam(v, k, dst[idx], err);
1070 					}
1071 					else
1072 					{
1073 						auto subFieldname = k[0 .. $ - key.length + indexTrailer.length];
1074 						auto result = readFormParamRec(req, dst[idx], subFieldname, true, style, err);
1075 					}
1076 					if (result != ParamResult.ok)
1077 						return result;
1078 				}
1079 			}
1080 
1081 			return ParamResult.ok;
1082 		}
1083 
1084 		if (processItems(req.form) == ParamResult.error)
1085 			return ParamResult.error;
1086 		if (processItems(req.query) == ParamResult.error)
1087 			return ParamResult.error;
1088 
1089 		// make sure all static array items have been seen
1090 		static if (isStaticArray!T)
1091 		{
1092 			import std.algorithm : countUntil;
1093 			auto notSeen = seen[].countUntil(false);
1094 			if (notSeen != -1)
1095 			{
1096 				err.field = style.getArrayFieldName(fieldname, notSeen);
1097 				err.text = "Missing array form field entry.";
1098 				return ParamResult.error;
1099 			}
1100 		}
1101 	} else static if (isNullable!T) {
1102 		typeof(dst.get()) el = void;
1103 		auto r = readFormParamRec(req, el, fieldname, false, style, err);
1104 		final switch (r) {
1105 			case ParamResult.ok: dst.setVoid(el); break;
1106 			case ParamResult.skipped: dst.setVoid(T.init); break;
1107 			case ParamResult.error: return ParamResult.error;
1108 		}
1109 	} else static if (is(T == struct) &&
1110 		!is(typeof(T.fromString(string.init))) &&
1111 		!is(typeof(T.fromStringValidate(string.init, null))) &&
1112 		!is(typeof(T.fromISOExtString(string.init))))
1113 	{
1114 		foreach (m; __traits(allMembers, T)) {
1115 			auto r = readFormParamRec(req, __traits(getMember, dst, m), style.getMemberFieldName(fieldname, m), required, style, err);
1116 			if (r != ParamResult.ok)
1117 				return r; // FIXME: in case of errors the struct will be only partially initialized! All previous fields should be deinitialized first.
1118 		}
1119 	} else static if (is(T == bool)) {
1120 		dst = (fieldname in req.form) !is null || (fieldname in req.query) !is null;
1121 	} else if (auto pv = fieldname in req.form) {
1122 		if (!(*pv).webConvTo(dst, err)) {
1123 			err.field = fieldname;
1124 			return ParamResult.error;
1125 		}
1126 	} else if (auto pv = fieldname in req.query) {
1127 		if (!(*pv).webConvTo(dst, err)) {
1128 			err.field = fieldname;
1129 			return ParamResult.error;
1130 		}
1131 	} else if (required) {
1132 		err.field = fieldname;
1133 		err.text = "Missing form field.";
1134 		return ParamResult.error;
1135 	}
1136 	else return ParamResult.skipped;
1137 
1138 	return ParamResult.ok;
1139 }
1140 
1141 // test new array mechanisms
1142 unittest {
1143 	import vibe.http.server;
1144 	import vibe.inet.url;
1145 
1146 	auto req = createTestHTTPServerRequest(URL("http://localhost/route?arr_0=1&arr_2=2&arr_=3"));
1147 	int[] arr;
1148 	ParamError err;
1149 	auto result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.underscore, err);
1150 	assert(result == ParamResult.ok);
1151 	assert(arr == [1,3,2]);
1152 
1153 	// try with static array
1154 	int[3] staticarr;
1155 	result = req.readFormParamRec(staticarr, "arr", false, NestedNameStyle.underscore, err);
1156 	assert(result == ParamResult.ok);
1157 	assert(staticarr == [1,3,2]);
1158 
1159 	// d style
1160 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr[2]=1&arr[0]=2&arr[]=3"));
1161 	result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.d, err);
1162 	assert(result == ParamResult.ok);
1163 	assert(arr == [2,3,1]);
1164 
1165 	result = req.readFormParamRec(staticarr, "arr", false, NestedNameStyle.d, err);
1166 	assert(result == ParamResult.ok);
1167 	assert(staticarr == [2,3,1]);
1168 
1169 	// try nested arrays
1170 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr[2][]=1&arr[0][]=2&arr[1][]=3&arr[0][]=4"));
1171 	int[][] arr2;
1172 	result = req.readFormParamRec(arr2, "arr", false, NestedNameStyle.d, err);
1173 	assert(result == ParamResult.ok);
1174 	assert(arr2 == [[2,4],[3],[1]]);
1175 
1176 	int[][2] staticarr2;
1177 	result = req.readFormParamRec(staticarr2, "arr", false, NestedNameStyle.d, err);
1178 	assert(result == ParamResult.ok);
1179 	assert(staticarr2 == [[2,4],[3]]);
1180 
1181 	// bug with key length
1182 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr=1"));
1183 	result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.d, err);
1184 	assert(result == ParamResult.ok);
1185 	assert(arr.length == 0);
1186 }
1187 
1188 unittest { // complex array parameters
1189 	import vibe.http.server;
1190 	import vibe.inet.url;
1191 
1192 	static struct S {
1193 		int a, b;
1194 	}
1195 
1196 	S[] arr;
1197 	ParamError err;
1198 
1199 	// d style
1200 	auto req = createTestHTTPServerRequest(URL("http://localhost/route?arr[0].a=1&arr[0].b=2"));
1201 	auto result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.d, err);
1202 	assert(result == ParamResult.ok);
1203 	assert(arr == [S(1, 2)]);
1204 
1205 	// underscore style
1206 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr_0_a=1&arr_0_b=2"));
1207 	result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.underscore, err);
1208 	assert(result == ParamResult.ok);
1209 	assert(arr == [S(1, 2)]);
1210 }
1211 
1212 package bool webConvTo(T)(string str, ref T dst, ref ParamError err)
1213 nothrow {
1214 	import std.conv;
1215 	import std.exception;
1216 	try {
1217 		static if (is(typeof(T.fromStringValidate(str, &err.text)))) {
1218 			static assert(is(typeof(T.fromStringValidate(str, &err.text)) == Nullable!T));
1219 			auto res = T.fromStringValidate(str, &err.text);
1220 			if (res.isNull()) return false;
1221 			dst.setVoid(res.get);
1222 		} else static if (is(typeof(T.fromString(str)))) {
1223 			static assert(is(typeof(T.fromString(str)) == T));
1224 			dst.setVoid(T.fromString(str));
1225 		} else static if (is(typeof(T.fromISOExtString(str)))) {
1226 			static assert(is(typeof(T.fromISOExtString(str)) == T));
1227 			dst.setVoid(T.fromISOExtString(str));
1228 		} else {
1229 			dst.setVoid(str.to!T());
1230 		}
1231 	} catch (Exception e) {
1232 		import vibe.core.log : logDebug;
1233 		import std.encoding : sanitize;
1234 		err.text = e.msg;
1235 		debug try logDebug("Error converting web field: %s", e.toString().sanitize);
1236 		catch (Exception) {}
1237 		return false;
1238 	}
1239 	return true;
1240 }
1241 
1242 // properly sets an uninitialized variable
1243 package void setVoid(T, U)(ref T dst, U value)
1244 {
1245 	import std.traits;
1246 	static if (hasElaborateAssign!T) {
1247 		static if (is(T == U)) {
1248 			(cast(ubyte*)&dst)[0 .. T.sizeof] = (cast(ubyte*)&value)[0 .. T.sizeof];
1249 			typeid(T).postblit(&dst);
1250 		} else {
1251 			static T init = T.init;
1252 			(cast(ubyte*)&dst)[0 .. T.sizeof] = (cast(ubyte*)&init)[0 .. T.sizeof];
1253 			dst = value;
1254 		}
1255 	} else dst = value;
1256 }
1257 
1258 unittest {
1259 	static assert(!__traits(compiles, { bool[] barr; ParamError err;readFormParamRec(HTTPServerRequest.init, barr, "f", true, NestedNameStyle.d, err); }));
1260 	static assert(__traits(compiles, { bool[2] barr; ParamError err;readFormParamRec(HTTPServerRequest.init, barr, "f", true, NestedNameStyle.d, err); }));
1261 
1262 	enum Test: string {	a = "AAA", b="BBB" }
1263 	static assert(__traits(compiles, { Test barr; ParamError err;readFormParamRec(HTTPServerRequest.init, barr, "f", true, NestedNameStyle.d, err); }));
1264 }
1265 
1266 private string getArrayFieldName(T)(NestedNameStyle style, string prefix, T index)
1267 {
1268 	import std.format : format;
1269 	final switch (style) {
1270 		case NestedNameStyle.underscore: return format("%s_%s", prefix, index);
1271 		case NestedNameStyle.d: return format("%s[%s]", prefix, index);
1272 	}
1273 }
1274 
1275 private string getMemberFieldName(NestedNameStyle style, string prefix, string member)
1276 @safe {
1277 	import std.format : format;
1278 	final switch (style) {
1279 		case NestedNameStyle.underscore: return format("%s_%s", prefix, member);
1280 		case NestedNameStyle.d: return format("%s.%s", prefix, member);
1281 	}
1282 }