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 vibrant.vibe; 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 }