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 }