1 module xmlrpcc.paramconv; 2 3 import std.string : format; 4 import std.exception : enforce; 5 import std.variant : Variant, VariantException; 6 import std.range : isForwardRange; 7 import std.conv : to, ConvException; 8 import std.typecons : Tuple; 9 import std.stdio : writeln; 10 11 12 import xmlrpcc.data; 13 import xmlrpcc.error; 14 15 16 import std.traits : isAssociativeArray, isArray, isImplicitlyConvertible, KeyType, ValueType, isSomeString, isScalarType, Unqual; 17 18 @trusted: 19 20 class ParameterConversionException : XmlRpcException { 21 private this(string msg, string file = __FILE__, size_t line = __LINE__) { 22 super(msg, file, line, next); 23 } 24 } 25 26 package: 27 28 Variant[] paramsToVariantArray(Args...)(Args args) { 29 Variant[] result; 30 foreach (a; args) 31 result ~= paramToVariant(a); 32 return result; 33 } 34 35 auto variantArrayToParams(Args...)(Variant[] variants) { 36 enforce(Args.length == variants.length, 37 new ParameterConversionException(format("Wrong number of arguments: expected %s, got %s)", Args.length, 38 variants.length))); 39 40 static if (Args.length == 0) 41 return; 42 static if (Args.length == 1) // Special case 43 return variantToParam!(Args[0])(variants[0]); 44 else { 45 Tuple!(Args) returnValue; 46 foreach (i, ref item; returnValue) 47 item = variantToParam!(typeof(returnValue[i]))(variants[i]); 48 return returnValue; 49 } 50 } 51 52 private: 53 54 Arg variantToParam(Arg)(Variant var) { 55 static if (is(Arg : Variant)) { 56 return var; 57 } else static if (isSomeString!Arg) { 58 return to!Arg(var); 59 } else static if (isArray!Arg) { 60 Arg array; 61 foreach (ref Variant item; var) 62 array ~= variantToParam!(typeof(array[0]))(item); 63 return array; 64 } else static if (isAssociativeArray!Arg) { 65 static assert(isImplicitlyConvertible!(KeyType!Arg, const(string)) 66 || "Associative array key type must be implicitly convertible to string"); 67 Arg assocArray; 68 // Intermediate array is required because iterating over the Variant(Value[Key]) is not possible 69 auto intermediate = var.get!(Variant[string])(); 70 foreach (key, ref value; intermediate) 71 assocArray[key] = variantToParam!(ValueType!Arg)(value); 72 return assocArray; 73 } else static if (is(typeof(var.coerce!Arg()))) // Try to coerce only if the type is coercible 74 { 75 return var.coerce!Arg(); 76 } else { 77 return var.get!Arg(); // Exact match is the last line of defence 78 } 79 } 80 81 Variant paramToVariant(Arg)(Arg arg) { 82 static if (isImplicitlyConvertible!(Arg, Variant)) { 83 return arg; 84 } else static if (isSomeString!Arg || isImplicitlyConvertible!(Arg, const(string))) { 85 return Variant(to!string(arg)); // NOTE: 'wstring', 'dstring' are converted to 'string' 86 } else static if (isForwardRange!Arg) // Order matters because strings are forward ranges too. 87 { 88 Variant[] array; 89 foreach (a; arg) 90 array ~= paramToVariant(a); 91 return Variant(array); 92 } else static if (isAssociativeArray!Arg) { 93 static assert(isSomeString!(KeyType!Arg) || isImplicitlyConvertible!(KeyType!Arg, const(string)) 94 || is(KeyType!Arg == Variant), 95 "Associative array key type must be string, implicitly convertible to string, or Variant"); 96 Variant[string] hash; 97 foreach (key, rawValue; arg) { 98 Variant value = paramToVariant(rawValue); 99 hash[to!string(key)] = value; // Any Variant turns into string 100 } 101 return Variant(hash); 102 } else static if (isScalarType!Arg) // Get rid of type qualifiers 103 { 104 return Variant(to!(Unqual!Arg)(arg)); 105 } else { 106 return Variant(arg); 107 } 108 } 109 110 // From Variant[] 111 version (xmlrpc_unittest) unittest { 112 import std.exception; 113 import std.datetime : DateTime; 114 115 Variant[] vars = [Variant([Variant(123), Variant(456)])]; 116 auto p1 = variantArrayToParams!(int[])(vars); 117 assert(p1 == [123, 456]); 118 119 Variant[string] aa = ["abc" : Variant(456)]; 120 vars = [Variant(aa)]; 121 auto p2 = variantArrayToParams!(int[string])(vars); 122 assert(p2["abc"] == 456); 123 124 // Multiple arguments, implicit string to float conversion 125 aa = ["abc" : Variant(456), "qwerty" : Variant("123.456")]; 126 vars = [Variant(123), Variant("def"), Variant("789"), Variant(aa), Variant(DateTime(2020, 1, 17, 12, 34, 56))]; 127 auto p3 = variantArrayToParams!(int, string, float, real[string], DateTime)(vars); 128 assert(p3[0] == 123); 129 assert(p3[1] == "def"); 130 assert(p3[2] == 789); 131 assert(p3[3]["abc"] == 456); 132 assert(p3[3]["qwerty"] == 123.456); 133 assert(p3[4] == DateTime(2020, 1, 17, 12, 34, 56)); 134 135 // Non-parseable strings 136 vars = [Variant("nonparseable"), Variant("*+j")]; 137 assertThrown!ConvException(variantArrayToParams!(int, float)(vars)); 138 139 // Type mismatch 140 assertThrown!ParameterConversionException(variantArrayToParams!(int)(vars)); 141 assertThrown!VariantException(variantArrayToParams!(int[string], float[])(vars)); 142 143 // Compilation failure 144 static assert(!is(typeof(variantArrayToParams!(string[int])(vars)))); // AA key type 145 } 146 147 // To Variant[] 148 version (xmlrpc_unittest) unittest { 149 import std.exception; 150 151 assert(paramToVariant(123) == 123); 152 153 Variant converted = paramToVariant(["test" : [cast(immutable) 456, cast(const) 789]]); 154 assert(converted["test"][0].type() == typeid(int)); 155 assert(converted["test"][0] == 456); 156 assert(converted["test"][1] == 789); 157 158 converted = paramToVariant(["test" : ["nested"w : "string value"d]]); 159 assert(converted["test"]["nested"] == "string value"); 160 161 /* 162 * Make sure that Variant types passed with no conversion, otherwise the 163 * assoc. array with integer key would be illegal 164 */ 165 converted = paramToVariant(["test" : [Variant(456), Variant([123 : 456])]]); 166 assert(converted["test"][0] == 456); 167 assert(converted["test"][1][123] == 456); 168 169 /* 170 * Associative array key type checking 171 */ 172 assert(is(typeof(paramToVariant(["test" : ["hello" : 456]])))); // OK: String key 173 assertNotThrown(paramToVariant([Variant("hello") : 456])); // OK: Variant key is convertible to string 174 assertNotThrown(paramToVariant([Variant(123456) : 456])); // OK: Variant is not convertible to string 175 assert(!is(typeof(paramToVariant([123 : 456])))); // Fail: Integer key 176 177 /* 178 * Group conversions 179 */ 180 Variant[] va = paramsToVariantArray("string", 123, [465, 789], Variant("variant"), ["key" : "value"]); 181 assert(to!string(va) == `[string, 123, [465, 789], variant, ["key":value]]`); 182 }