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(const(char)[] ch) { underlying.put(cast(const(ubyte)[])ch); }
563 	}
564 	auto dst = R(output_range);
565 	value.serializeWithPolicy!(JsonStringSerializer!R, P) (dst);
566 }
567 
568 package T defaultDeserialize (alias P, T, R) (R input_range)
569 {
570 	return deserializeWithPolicy!(JsonStringSerializer!(typeof(std..string.assumeUTF(input_range))), P, T)
571 		(std..string.assumeUTF(input_range));
572 }
573 
574 package alias DefaultSerializerT = ResultSerializer!(
575 	defaultSerialize, defaultDeserialize, "application/json; charset=UTF-8");
576 
577 
578 /// Convenience template to get all the ResultSerializers for a function
579 package template ResultSerializersT(alias func) {
580 	alias DefinedSerializers = getUDAs!(func, ResultSerializer);
581 	static if (DefinedSerializers.length)
582 		alias ResultSerializersT = DefinedSerializers;
583 	else
584 		alias ResultSerializersT = AliasSeq!(DefaultSerializerT);
585 }
586 
587 ///
588 package template SerPolicyT (Iface)
589 {
590 	static if (getUDAs!(Iface, SerPolicy).length)
591 	{
592 		alias SerPolicyT = getUDAs!(Iface, SerPolicy)[0];
593 	}
594 	else
595 	{
596 		alias SerPolicyT = SerPolicy!DefaultPolicy;
597 	}
598 }
599 
600 ///
601 package struct SerPolicy (alias PolicyTemplatePar)
602 {
603 	alias PolicyTemplate = PolicyTemplatePar;
604 }
605 
606 ///
607 public alias serializationPolicy (Args...) = SerPolicy!(Args);
608 
609 unittest
610 {
611 	import vibe.data.serialization : Base64ArrayPolicy;
612 	import std.array : appender;
613 	import std.conv : to;
614 
615 	struct X
616 	{
617 		string name = "test";
618 		ubyte[] arr = [138, 245, 231, 234, 142, 132, 142];
619 	}
620 	X x;
621 
622 	// Interface using Base64 array serialization
623 	@serializationPolicy!(Base64ArrayPolicy)
624 	interface ITestBase64
625 	{
626 		@safe X getTest();
627 	}
628 
629 	alias serPolicyFound = SerPolicyT!ITestBase64;
630 	alias resultSerializerFound = ResultSerializersT!(ITestBase64.getTest)[0];
631 
632 	// serialization test with base64 encoding
633 	auto output = appender!string();
634 
635 	resultSerializerFound.serialize!(serPolicyFound.PolicyTemplate)(output, x);
636 	auto serialized = output.data;
637 	assert(serialized == `{"name":"test","arr":"ivXn6o6Ejg=="}`,
638 			"serialization is not correct, produced: " ~ serialized);
639 
640 	// deserialization test with base64 encoding
641 	auto deserialized = serialized.deserializeWithPolicy!(JsonStringSerializer!string, serPolicyFound.PolicyTemplate, X)();
642 	assert(deserialized.name == "test", "deserialization of `name` is not correct, produced: " ~ deserialized.name);
643 	assert(deserialized.arr == [138, 245, 231, 234, 142, 132, 142],
644 			"deserialization of `arr` is not correct, produced: " ~ to!string(deserialized.arr));
645 
646 	// Interface NOT using Base64 array serialization
647 	interface ITestPlain
648 	{
649 		@safe X getTest();
650 	}
651 
652 	alias plainSerPolicyFound = SerPolicyT!ITestPlain;
653 	alias plainResultSerializerFound = ResultSerializersT!(ITestPlain.getTest)[0];
654 
655 	// serialization test without base64 encoding
656 	output = appender!string();
657 	plainResultSerializerFound.serialize!(plainSerPolicyFound.PolicyTemplate)(output, x);
658 	serialized = output.data;
659 	assert(serialized == `{"name":"test","arr":[138,245,231,234,142,132,142]}`,
660 			"serialization is not correct, produced: " ~ serialized);
661 
662 	// deserialization test without base64 encoding
663 	deserialized = serialized.deserializeWithPolicy!(JsonStringSerializer!string, plainSerPolicyFound.PolicyTemplate, X)();
664 	assert(deserialized.name == "test", "deserialization of `name` is not correct, produced: " ~ deserialized.name);
665 	assert(deserialized.arr == [138, 245, 231, 234, 142, 132, 142],
666 			"deserialization of `arr` is not correct, produced: " ~ to!string(deserialized.arr));
667 }
668 
669 /**
670  * This struct contains the name of a route specified by the `path` function.
671  */
672 struct PathAttribute
673 {
674 	/// The specified path
675 	string data;
676 	alias data this;
677 }
678 
679 /// private
680 package struct NoRouteAttribute {}
681 
682 /**
683  * This struct contains a mapping between the name used by HTTP (field)
684  * and the parameter (identifier) name of the function.
685  */
686 public struct WebParamAttribute {
687 	import vibe.web.internal.rest.common : ParameterKind;
688 
689 	/// The type of the WebParamAttribute
690 	ParameterKind origin;
691 	/// Parameter name (function parameter name).
692 	string identifier;
693 	/// The meaning of this field depends on the origin. (HTTP request name)
694 	string field;
695 }
696 
697 
698 /**
699  * Declare that a parameter will be transmitted to the API through the body.
700  *
701  * It will be serialized as part of a JSON object.
702  * The serialization format is currently not customizable.
703  * If no fieldname is given, the entire body is serialized into the object.
704  *
705  * There are currently two kinds of symbol to do this: `viaBody` and `bodyParam`.
706  * `viaBody` should be applied to the parameter itself, while `bodyParam`
707  * is applied to the function.
708  * `bodyParam` was introduced long before the D language for UDAs on parameters
709  * (introduced in DMD v2.082.0), and will be deprecated in a future release.
710  *
711  * Params:
712  *   identifier = The name of the parameter to customize. A compiler error will be issued on mismatch.
713  *   field = The name of the field in the JSON object.
714  *
715  * ----
716  * void ship(@viaBody("package") int pack);
717  * // The server will receive the following body for a call to ship(42):
718  * // { "package": 42 }
719  * ----
720  */
721 WebParamAttribute viaBody(string field = null)
722 @safe {
723 	import vibe.web.internal.rest.common : ParameterKind;
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 	import vibe.web.internal.rest.common : ParameterKind;
737 	if (!__ctfe)
738 		assert(false, onlyAsUda!__FUNCTION__);
739 	return WebParamAttribute(ParameterKind.body_, identifier, field);
740 }
741 
742 /// ditto
743 WebParamAttribute bodyParam(string identifier)
744 @safe {
745 	import vibe.web.internal.rest.common : ParameterKind;
746 	if (!__ctfe)
747 		assert(false, onlyAsUda!__FUNCTION__);
748 	return WebParamAttribute(ParameterKind.body_, identifier, "");
749 }
750 
751 /**
752  * Declare that a parameter will be transmitted to the API through the headers.
753  *
754  * If the parameter is a string, or any scalar type (float, int, char[], ...), it will be send as a string.
755  * If it's an aggregate, it will be serialized as JSON.
756  * However, passing aggregate via header isn't a good practice and should be avoided for new production code.
757  *
758  * There are currently two kinds of symbol to do this: `viaHeader` and `headerParam`.
759  * `viaHeader` should be applied to the parameter itself, while `headerParam`
760  * is applied to the function.
761  * `headerParam` was introduced long before the D language for UDAs on parameters
762  * (introduced in DMD v2.082.0), and will be deprecated in a future release.
763  *
764  * Params:
765  *   identifier = The name of the parameter to customize. A compiler error will be issued on mismatch.
766  *   field = The name of the header field to use (e.g: 'Accept', 'Content-Type'...).
767  *
768  * ----
769  * // The server will receive the content of the "Authorization" header.
770  * void login(@viaHeader("Authorization") string auth);
771  * ----
772  */
773 WebParamAttribute viaHeader(string field)
774 @safe {
775 	import vibe.web.internal.rest.common : ParameterKind;
776 	if (!__ctfe)
777 		assert(false, onlyAsUda!__FUNCTION__);
778 	return WebParamAttribute(ParameterKind.header, null, field);
779 }
780 
781 /// Ditto
782 WebParamAttribute headerParam(string identifier, string field)
783 @safe {
784 	import vibe.web.internal.rest.common : ParameterKind;
785 	if (!__ctfe)
786 		assert(false, onlyAsUda!__FUNCTION__);
787 	return WebParamAttribute(ParameterKind.header, identifier, field);
788 }
789 
790 /**
791  * Declare that a parameter will be transmitted to the API through the query string.
792  *
793  * It will be serialized as part of a JSON object, and will go through URL serialization.
794  * The serialization format is not customizable.
795  *
796  * There are currently two kinds of symbol to do this: `viaQuery` and `queryParam`.
797  * `viaQuery` should be applied to the parameter itself, while `queryParam`
798  * is applied to the function.
799  * `queryParam` was introduced long before the D language for UDAs on parameters
800  * (introduced in DMD v2.082.0), and will be deprecated in a future release.
801  *
802  * Params:
803  *   identifier = The name of the parameter to customize. A compiler error will be issued on mismatch.
804  *   field = The field name to use.
805  *
806  * ----
807  * // For a call to postData("D is awesome"), the server will receive the query:
808  * // POST /data?test=%22D is awesome%22
809  * void postData(@viaQuery("test") string data);
810  * ----
811  */
812 WebParamAttribute viaQuery(string field)
813 @safe {
814 	import vibe.web.internal.rest.common : ParameterKind;
815 	if (!__ctfe)
816 		assert(false, onlyAsUda!__FUNCTION__);
817 	return WebParamAttribute(ParameterKind.query, null, field);
818 }
819 
820 /// Ditto
821 WebParamAttribute queryParam(string identifier, string field)
822 @safe {
823 	import vibe.web.internal.rest.common : ParameterKind;
824 	if (!__ctfe)
825 		assert(false, onlyAsUda!__FUNCTION__);
826 	return WebParamAttribute(ParameterKind.query, identifier, field);
827 }
828 
829 /**
830 	Determines the naming convention of an identifier.
831 */
832 enum MethodStyle
833 {
834 	/// Special value for free-style conventions
835 	unaltered,
836 	/// camelCaseNaming
837 	camelCase,
838 	/// PascalCaseNaming
839 	pascalCase,
840 	/// lowercasenaming
841 	lowerCase,
842 	/// UPPERCASENAMING
843 	upperCase,
844 	/// lower_case_naming
845 	lowerUnderscored,
846 	/// UPPER_CASE_NAMING
847 	upperUnderscored,
848 	/// lower-case-naming
849 	lowerDashed,
850 	/// UPPER-CASE-NAMING
851 	upperDashed,
852 }
853 
854 
855 /// Speficies how D fields are mapped to form field names
856 enum NestedNameStyle {
857 	underscore, /// Use underscores to separate fields and array indices
858 	d           /// Use native D style and separate fields by dots and put array indices into brackets
859 }
860 
861 
862 // concatenates two URL parts avoiding any duplicate slashes
863 // in resulting URL. `trailing` defines of result URL must
864 // end with slash
865 package string concatURL(string prefix, string url, bool trailing = false)
866 @safe {
867 	import std.algorithm : startsWith, endsWith;
868 
869 	auto pre = prefix.endsWith("/");
870 	auto post = url.startsWith("/");
871 
872 	if (!url.length) return trailing && !pre ? prefix ~ "/" : prefix;
873 
874 	auto suffix = trailing && !url.endsWith("/") ? "/" : null;
875 
876 	if (pre) {
877 		// "/" is ASCII, so can just slice
878 		if (post) return prefix ~ url[1 .. $] ~ suffix;
879 		else return prefix ~ url ~ suffix;
880 	} else {
881 		if (post) return prefix ~ url ~ suffix;
882 		else return prefix ~ "/" ~ url ~ suffix;
883 	}
884 }
885 
886 @safe unittest {
887 	assert(concatURL("/test/", "/it/", false) == "/test/it/");
888 	assert(concatURL("/test", "it/", false) == "/test/it/");
889 	assert(concatURL("/test", "it", false) == "/test/it");
890 	assert(concatURL("/test", "", false) == "/test");
891 	assert(concatURL("/test/", "", false) == "/test/");
892 	assert(concatURL("/test/", "/it/", true) == "/test/it/");
893 	assert(concatURL("/test", "it/", true) == "/test/it/");
894 	assert(concatURL("/test", "it", true) == "/test/it/");
895 	assert(concatURL("/test", "", true) == "/test/");
896 	assert(concatURL("/test/", "", true) == "/test/");
897 }
898 
899 
900 /// private
901 template isNullable(T) {
902 	import std.traits;
903 	enum isNullable = isInstanceOf!(Nullable, T);
904 }
905 
906 static assert(isNullable!(Nullable!int));
907 
908 package struct ParamError {
909 	string field;
910 	string text;
911 }
912 
913 package enum ParamResult {
914 	ok,
915 	skipped,
916 	error
917 }
918 
919 // maximum array index in the parameter fields.
920 private enum MAX_ARR_INDEX = 0xffff;
921 
922 // handle the actual data inside the parameter
923 private ParamResult processFormParam(T)(scope string data, string fieldname, ref T dst, ref ParamError err)
924 {
925 	static if (is(T == bool))
926 	{
927 		// getting here means the item is present, set to true.
928 		dst = true;
929 		return ParamResult.ok;
930 	}
931 	else
932 	{
933 		if (!data.webConvTo(dst, err)) {
934 			err.field = fieldname;
935 			return ParamResult.error;
936 		}
937 		return ParamResult.ok;
938 	}
939 }
940 
941 // NOTE: dst is assumed to be uninitialized
942 package ParamResult readFormParamRec(T)(scope HTTPServerRequest req, ref T dst, string fieldname, bool required, NestedNameStyle style, ref ParamError err)
943 {
944 	import std.traits;
945 	import std.typecons;
946 	import vibe.data.serialization;
947 	import std.algorithm : startsWith;
948 
949 	static if (isStaticArray!T || (isDynamicArray!T && !isSomeString!(OriginalType!T))) {
950 		alias EL = typeof(T.init[0]);
951 		enum isSimpleElement = !(isDynamicArray!EL && !isSomeString!(OriginalType!EL)) &&
952 			!isStaticArray!EL &&
953 			!(is(EL == struct) &&
954 					!is(typeof(EL.fromString(string.init))) &&
955 					!is(typeof(EL.fromStringValidate(string.init, null))) &&
956 					!is(typeof(EL.fromISOExtString(string.init))));
957 
958 		static if (isStaticArray!T)
959 		{
960 			bool[T.length] seen;
961 		}
962 		else
963 		{
964 			static assert(!is(EL == bool),
965 			  "Boolean arrays are not allowed, because their length cannot " ~
966 			  "be uniquely determined. Use a static array instead.");
967 			// array to check for duplicates
968 			dst = T.init;
969 			bool[] seen;
970 		}
971 		// process the items in the order they appear.
972 		char indexSep = style == NestedNameStyle.d ? '[' : '_';
973 		const minLength = fieldname.length + (style == NestedNameStyle.d ? 2 : 1);
974 		const indexTrailer = style == NestedNameStyle.d ? "]" : "";
975 
976 		ParamResult processItems(DL)(DL dlist)
977 		{
978 			foreach (k, v; dlist.byKeyValue)
979 			{
980 				if (k.length < minLength)
981 					// sanity check to prevent out of bounds
982 					continue;
983 				if (k.startsWith(fieldname) && k[fieldname.length] == indexSep)
984 				{
985 					// found a potential match
986 					string key = k[fieldname.length + 1 .. $];
987 					size_t idx;
988 					if (key == indexTrailer)
989 					{
990 						static if (isSimpleElement)
991 						{
992 							// this is a non-indexed array item. Find an empty slot, or expand the array
993 							import std.algorithm : countUntil;
994 							idx = seen[].countUntil(false);
995 							static if (isStaticArray!T)
996 							{
997 								if (idx == size_t.max)
998 								{
999 									// ignore extras, and we know there are no more matches to come.
1000 									break;
1001 								}
1002 							}
1003 							else if (idx == size_t.max)
1004 							{
1005 								// append to the end.
1006 								idx = dst.length;
1007 							}
1008 						}
1009 						else
1010 						{
1011 							// not valid for non-simple elements.
1012 							continue;
1013 						}
1014 					}
1015 					else
1016 					{
1017 						import std.conv;
1018 						idx = key.parse!size_t;
1019 						static if (isStaticArray!T)
1020 						{
1021 							if (idx >= T.length)
1022 								// keep existing behavior, ignore extras
1023 								continue;
1024 						}
1025 						else if (idx > MAX_ARR_INDEX)
1026 						{
1027 							// Getting a bit large, we don't want to allow DOS attacks.
1028 							err.field = k;
1029 							err.text = "Maximum index exceeded";
1030 							return ParamResult.error;
1031 						}
1032 						static if (isSimpleElement)
1033 						{
1034 							if (key != indexTrailer)
1035 								// this can't be a match, simple elements are parsed from
1036 								// the string, there should be no more to the key.
1037 								continue;
1038 						}
1039 						else
1040 						{
1041 							// ensure there's more than just the index trailer
1042 							if (key.length == indexTrailer.length || !key.startsWith(indexTrailer))
1043 								// not a valid entry. ignore this entry to preserve existing behavior.
1044 								continue;
1045 						}
1046 					}
1047 
1048 					static if (!isStaticArray!T)
1049 					{
1050 						// check to see if we need to expand the array
1051 						if (dst.length <= idx)
1052 						{
1053 							dst.length = idx + 1;
1054 							seen.length = idx + 1;
1055 						}
1056 					}
1057 
1058 					if (seen[idx])
1059 					{
1060 						// don't store it twice
1061 						continue;
1062 					}
1063 					seen[idx] = true;
1064 
1065 					static if (isSimpleElement)
1066 					{
1067 						auto result = processFormParam(v, k, dst[idx], err);
1068 					}
1069 					else
1070 					{
1071 						auto subFieldname = k[0 .. $ - key.length + indexTrailer.length];
1072 						auto result = readFormParamRec(req, dst[idx], subFieldname, true, style, err);
1073 					}
1074 					if (result != ParamResult.ok)
1075 						return result;
1076 				}
1077 			}
1078 
1079 			return ParamResult.ok;
1080 		}
1081 
1082 		if (processItems(req.form) == ParamResult.error)
1083 			return ParamResult.error;
1084 		if (processItems(req.query) == ParamResult.error)
1085 			return ParamResult.error;
1086 
1087 		// make sure all static array items have been seen
1088 		static if (isStaticArray!T)
1089 		{
1090 			import std.algorithm : countUntil;
1091 			auto notSeen = seen[].countUntil(false);
1092 			if (notSeen != -1)
1093 			{
1094 				err.field = style.getArrayFieldName(fieldname, notSeen);
1095 				err.text = "Missing array form field entry.";
1096 				return ParamResult.error;
1097 			}
1098 		}
1099 	} else static if (isNullable!T) {
1100 		typeof(dst.get()) el = void;
1101 		auto r = readFormParamRec(req, el, fieldname, false, style, err);
1102 		final switch (r) {
1103 			case ParamResult.ok: dst.setVoid(el); break;
1104 			case ParamResult.skipped: dst.setVoid(T.init); break;
1105 			case ParamResult.error: return ParamResult.error;
1106 		}
1107 	} else static if (is(T == struct) &&
1108 		!is(typeof(T.fromString(string.init))) &&
1109 		!is(typeof(T.fromStringValidate(string.init, null))) &&
1110 		!is(typeof(T.fromISOExtString(string.init))))
1111 	{
1112 		foreach (m; __traits(allMembers, T)) {
1113 			auto r = readFormParamRec(req, __traits(getMember, dst, m), style.getMemberFieldName(fieldname, m), required, style, err);
1114 			if (r != ParamResult.ok)
1115 				return r; // FIXME: in case of errors the struct will be only partially initialized! All previous fields should be deinitialized first.
1116 		}
1117 	} else static if (is(T == bool)) {
1118 		dst = (fieldname in req.form) !is null || (fieldname in req.query) !is null;
1119 	} else if (auto pv = fieldname in req.form) {
1120 		if (!(*pv).webConvTo(dst, err)) {
1121 			err.field = fieldname;
1122 			return ParamResult.error;
1123 		}
1124 	} else if (auto pv = fieldname in req.query) {
1125 		if (!(*pv).webConvTo(dst, err)) {
1126 			err.field = fieldname;
1127 			return ParamResult.error;
1128 		}
1129 	} else if (required) {
1130 		err.field = fieldname;
1131 		err.text = "Missing form field.";
1132 		return ParamResult.error;
1133 	}
1134 	else return ParamResult.skipped;
1135 
1136 	return ParamResult.ok;
1137 }
1138 
1139 // test new array mechanisms
1140 unittest {
1141 	import vibe.http.server;
1142 	import vibe.inet.url;
1143 
1144 	auto req = createTestHTTPServerRequest(URL("http://localhost/route?arr_0=1&arr_2=2&arr_=3"));
1145 	int[] arr;
1146 	ParamError err;
1147 	auto result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.underscore, err);
1148 	assert(result == ParamResult.ok);
1149 	assert(arr == [1,3,2]);
1150 
1151 	// try with static array
1152 	int[3] staticarr;
1153 	result = req.readFormParamRec(staticarr, "arr", false, NestedNameStyle.underscore, err);
1154 	assert(result == ParamResult.ok);
1155 	assert(staticarr == [1,3,2]);
1156 
1157 	// d style
1158 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr[2]=1&arr[0]=2&arr[]=3"));
1159 	result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.d, err);
1160 	assert(result == ParamResult.ok);
1161 	assert(arr == [2,3,1]);
1162 
1163 	result = req.readFormParamRec(staticarr, "arr", false, NestedNameStyle.d, err);
1164 	assert(result == ParamResult.ok);
1165 	assert(staticarr == [2,3,1]);
1166 
1167 	// try nested arrays
1168 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr[2][]=1&arr[0][]=2&arr[1][]=3&arr[0][]=4"));
1169 	int[][] arr2;
1170 	result = req.readFormParamRec(arr2, "arr", false, NestedNameStyle.d, err);
1171 	assert(result == ParamResult.ok);
1172 	assert(arr2 == [[2,4],[3],[1]]);
1173 
1174 	int[][2] staticarr2;
1175 	result = req.readFormParamRec(staticarr2, "arr", false, NestedNameStyle.d, err);
1176 	assert(result == ParamResult.ok);
1177 	assert(staticarr2 == [[2,4],[3]]);
1178 
1179 	// bug with key length
1180 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr=1"));
1181 	result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.d, err);
1182 	assert(result == ParamResult.ok);
1183 	assert(arr.length == 0);
1184 }
1185 
1186 unittest { // complex array parameters
1187 	import vibe.http.server;
1188 	import vibe.inet.url;
1189 
1190 	static struct S {
1191 		int a, b;
1192 	}
1193 
1194 	S[] arr;
1195 	ParamError err;
1196 
1197 	// d style
1198 	auto req = createTestHTTPServerRequest(URL("http://localhost/route?arr[0].a=1&arr[0].b=2"));
1199 	auto result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.d, err);
1200 	assert(result == ParamResult.ok);
1201 	assert(arr == [S(1, 2)]);
1202 
1203 	// underscore style
1204 	req = createTestHTTPServerRequest(URL("http://localhost/route?arr_0_a=1&arr_0_b=2"));
1205 	result = req.readFormParamRec(arr, "arr", false, NestedNameStyle.underscore, err);
1206 	assert(result == ParamResult.ok);
1207 	assert(arr == [S(1, 2)]);
1208 }
1209 
1210 package bool webConvTo(T)(string str, ref T dst, ref ParamError err)
1211 nothrow {
1212 	import std.conv;
1213 	import std.exception;
1214 	try {
1215 		static if (is(typeof(T.fromStringValidate(str, &err.text)))) {
1216 			static assert(is(typeof(T.fromStringValidate(str, &err.text)) == Nullable!T));
1217 			auto res = T.fromStringValidate(str, &err.text);
1218 			if (res.isNull()) return false;
1219 			dst.setVoid(res.get);
1220 		} else static if (is(typeof(T.fromString(str)))) {
1221 			static assert(is(typeof(T.fromString(str)) == T));
1222 			dst.setVoid(T.fromString(str));
1223 		} else static if (is(typeof(T.fromISOExtString(str)))) {
1224 			static assert(is(typeof(T.fromISOExtString(str)) == T));
1225 			dst.setVoid(T.fromISOExtString(str));
1226 		} else {
1227 			dst.setVoid(str.to!T());
1228 		}
1229 	} catch (Exception e) {
1230 		import vibe.core.log : logDebug;
1231 		import std.encoding : sanitize;
1232 		err.text = e.msg;
1233 		debug try logDebug("Error converting web field: %s", e.toString().sanitize);
1234 		catch (Exception) {}
1235 		return false;
1236 	}
1237 	return true;
1238 }
1239 
1240 // properly sets an uninitialized variable
1241 package void setVoid(T, U)(ref T dst, U value)
1242 {
1243 	import std.traits;
1244 	static if (hasElaborateAssign!T) {
1245 		static if (is(T == U)) {
1246 			(cast(ubyte*)&dst)[0 .. T.sizeof] = (cast(ubyte*)&value)[0 .. T.sizeof];
1247 			typeid(T).postblit(&dst);
1248 		} else {
1249 			static T init = T.init;
1250 			(cast(ubyte*)&dst)[0 .. T.sizeof] = (cast(ubyte*)&init)[0 .. T.sizeof];
1251 			dst = value;
1252 		}
1253 	} else dst = value;
1254 }
1255 
1256 unittest {
1257 	static assert(!__traits(compiles, { bool[] barr; ParamError err;readFormParamRec(null, barr, "f", true, NestedNameStyle.d, err); }));
1258 	static assert(__traits(compiles, { bool[2] barr; ParamError err;readFormParamRec(null, barr, "f", true, NestedNameStyle.d, err); }));
1259 
1260 	enum Test: string {	a = "AAA", b="BBB" }
1261 	static assert(__traits(compiles, { Test barr; ParamError err;readFormParamRec(null, barr, "f", true, NestedNameStyle.d, err); }));
1262 }
1263 
1264 private string getArrayFieldName(T)(NestedNameStyle style, string prefix, T index)
1265 {
1266 	import std.format : format;
1267 	final switch (style) {
1268 		case NestedNameStyle.underscore: return format("%s_%s", prefix, index);
1269 		case NestedNameStyle.d: return format("%s[%s]", prefix, index);
1270 	}
1271 }
1272 
1273 private string getMemberFieldName(NestedNameStyle style, string prefix, string member)
1274 @safe {
1275 	import std.format : format;
1276 	final switch (style) {
1277 		case NestedNameStyle.underscore: return format("%s_%s", prefix, member);
1278 		case NestedNameStyle.d: return format("%s.%s", prefix, member);
1279 	}
1280 }