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