1 module vibrant.parameter;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.regex;
6 import std.string;
7 import std.traits;
8 import std.typetuple;
9 
10 import vibe.d;
11 
12 class ParameterMissing : Exception {
13 
14 	this(string name) {
15 		super("Missing parameter: " ~ name);
16 	}
17 
18 }
19 
20 class Parameter {
21 	private {
22 		string _value;
23 
24 		Parameter[] _array;
25 
26 		Parameter[string] _assoc;
27 	}
28 
29 	this() {
30 	}
31 
32 	/+ Helpers +/
33 
34 	private {
35 		Parameter opAssign(string param) {
36 			_value = param;
37 			return this;
38 		}
39 
40 		Parameter opOpAssign(string op : "~")(string param) {
41 			Parameter element = new Parameter();
42 			this ~= (element = param);
43 			return this;
44 		}
45 
46 		Parameter opOpAssign(string op : "~")(Parameter param) {
47 			_array ~= param;
48 			return this;
49 		}
50 
51 		Parameter opIndexAssign(string param, string index) {
52 			Parameter element = new Parameter();
53 			this[index] = (element = param);
54 			return this;
55 		}
56 
57 		Parameter opIndexAssign(Parameter param, string index) {
58 			_assoc[index] = param;
59 			return this;
60 		}
61 
62 		Parameter opIndexOpAssign(string op : "~")(string param, string index) {
63 			Parameter element = new Parameter();
64 			this[index] ~= (element = param);
65 			return this;
66 		}
67 
68 		Parameter opIndexOpAssign(string op : "~")(Parameter param, string index) {
69 			_assoc[index] ~= param;
70 			return this;
71 		}
72 
73 		Parameter opIndexOpAssign(string op : "|")(lazy Parameter param, string index) {
74 			if (index !in _assoc) {
75 				this[index] = param;
76 			}
77 
78 			return this;
79 		}
80 	}
81 
82 	/+ Properties +/
83 
84 	@property
85 	string value() {
86 		return _value;
87 	}
88 
89 	@property
90 	Parameter[] array() {
91 		return _array;
92 	}
93 
94 	@property
95 	Parameter[string] assoc() {
96 		return _assoc;
97 	}
98 
99 	@property
100 	bool isA(Type)() {
101 		try {
102 			cast(Type) this;
103 			return true;
104 		} catch (ConvException e) {
105 			return false;
106 		}
107 	}
108 
109 	@property
110 	alias isAn(Type) = isA!Type;
111 
112 	@property
113 	Type as(Type)() {
114 		return cast(Type) this;
115 	}
116 
117 	/+ Operators +/
118 
119 	Parameter* opBinaryRight(string op : "in")(string index) {
120 		return index in _assoc;
121 	}
122 
123 	bool opBinaryRight(string op : "!in")(string index) {
124 		return index !in _assoc;
125 	}
126 
127 	/++
128 	 + Unspecialized type cast.
129 	 ++/
130 	Type opCast(Type)() {
131 		return _value.to!Type;
132 	}
133 
134 	string opCast(Type : string)() {
135 		return _value;
136 	}
137 
138 	Parameter opCast(Type : Parameter)() {
139 		return this;
140 	}
141 
142 	string[] opCast(Type : string[])() {
143 		string[] elements;
144 
145 		foreach (element; _array) {
146 			elements ~= element;
147 		}
148 
149 		return elements;
150 	}
151 
152 	Parameter[] opCast(Type : Parameter[])() {
153 		return _array;
154 	}
155 
156 	string[string] opCast(Type : string[string])() {
157 		string[string] elements;
158 
159 		foreach (key, element; _assoc) {
160 			elements[key] = element;
161 		}
162 
163 		return elements;
164 	}
165 
166 	Parameter[string] opCast(Type : Parameter[string])() {
167 		return _assoc;
168 	}
169 
170 	Parameter opIndex(size_t index) {
171 		if (index < _array.length) {
172 			return _array[index];
173 		} else {
174 			return null;
175 		}
176 	}
177 
178 	Parameter opIndex(string index) {
179 		auto ptr = index in this;
180 		return ptr ? *ptr : null;
181 	}
182 
183 	/+ Functions +/
184 
185 	Parameter require(string key) {
186 		auto ptr = key in this;
187 
188 		if (ptr is null) {
189 			throw new ParameterMissing(key);
190 		} else {
191 			return *ptr;
192 		}
193 	}
194 
195 	/++
196 	 + Returns a JSON-like string representation of the parameter.
197 	 ++/
198 	string toPrettyString() {
199 		string[] buffer;
200 
201 		// Include value component.
202 		if (_value !is null) {
203 			buffer ~= format(`"%s"`, _value);
204 		}
205 
206 		// Include array component.
207 		if (_array.length > 0) {
208 			string[] tmp;
209 
210 			foreach (element; _array) {
211 				tmp ~= element.toPrettyString;
212 			}
213 
214 			buffer ~= "[ " ~ tmp.joiner(", ").text ~ " ]";
215 		}
216 
217 		// Include assoc component.
218 		if (_assoc.length > 0) {
219 			string[] tmp;
220 
221 			foreach (key, element; _assoc) {
222 				tmp ~= "\"" ~ key ~ "\" : " ~ element.toPrettyString;
223 			}
224 
225 			buffer ~= "{ " ~ tmp.joiner(", ").text ~ " }";
226 		}
227 
228 		return buffer.joiner(", ").text;
229 	}
230 
231 	override string toString() {
232 		return _value;
233 	}
234 }
235 
236 Parameter normalizeParameters(Parameter param, string name, string value) {
237 	static auto r1 = ctRegex!(r"^([^\[\]]+)");
238 	static auto r2 = ctRegex!(r"^\[([^\[\]]+)\]");
239 	static auto r3 = ctRegex!(r"^(?:\[\])+");
240 
241 	Captures!string captures;
242 
243 	// Check for assoc or named parameters.
244 	if (!(captures = name.matchFirst(r1)).empty ||
245 		!(captures = name.matchFirst(r2)).empty) {
246 		string key = captures[1];
247 		string after = captures.post;
248 
249 		// Create a parameter if it doesn't exist.
250 		param[key] |= new Parameter();
251 
252 		param[key] = normalizeParameters(
253 			param[key], after, value
254 		);
255 	} // Check for array parameters.
256 	else if (!(captures = name.matchFirst(r3)).empty) {
257 		Parameter target = null;
258 		string after = captures.post;
259 
260 		// Check if the array is empty.
261 		if (param._array.length > 0) {
262 			// Check if the next parameter is an assoc.
263 			if (!(captures = after.matchFirst(r2)).empty) {
264 				Parameter last = param._array[$ - 1];
265 
266 				// Check if we should complete the last array.
267 				if (captures[1]!in last) {
268 					target = last;
269 				}
270 			}
271 		}
272 
273 		// Check if we have a target.
274 		if (target is null) {
275 			// Create a new element and append it.
276 			param ~= normalizeParameters(
277 				new Parameter(), after, value
278 			);
279 		} else {
280 			// Add to the existing array.
281 			normalizeParameters(
282 				target, after, value
283 			);
284 		}
285 	} // Plain parameter.
286 	else {
287 		param = value;
288 	}
289 
290 	return param;
291 }
292 
293 @property
294 Parameter createParameters(string[] params, string[] values) {
295 	auto assoc = new Parameter();
296 
297 	foreach (idx, param; params) {
298 		normalizeParameters(assoc, param, values[idx]);
299 	}
300 
301 	return assoc;
302 }
303 
304 @property
305 Parameter createParameters(HTTPServerRequest request) {
306 	auto assoc = new Parameter();
307 
308 	// Include form parameters.
309 	foreach (key, value; request.form.byKeyValue) {
310 		normalizeParameters(assoc, key, value);
311 	}
312 
313 	// Include request parameters.
314 	foreach (key, value; request.query.byKeyValue) {
315 		normalizeParameters(assoc, key, value);
316 	}
317 
318 	// Include URL parameters.
319 	foreach (key, value; request.params.byKeyValue) {
320 		normalizeParameters(assoc, key, value);
321 	}
322 
323 	return assoc;
324 }