1 module xmlrpcc.encoder; 2 3 import std.base64 : Base64; 4 import std.conv : to; 5 import std.datetime : DateTime; 6 import std.exception : enforce; 7 import std.format : format; 8 import std.stdio : writeln; 9 import std.string : join; 10 import std.variant : Variant; 11 import std.xml : Element, Document; 12 13 import xmlrpcc.error; 14 import xmlrpcc.data; 15 16 @trusted: 17 18 class EncoderException : XmlRpcException { 19 private this(string msg, string file = __FILE__, size_t line = __LINE__) { 20 super(msg, file, line, next); 21 } 22 } 23 24 package: 25 26 string encodeCall(MethodCallData call) { 27 auto root = new Element("methodCall"); 28 root ~= new Element("methodName", call.name); 29 root ~= encodeParams(call.params); 30 return toString(root); 31 } 32 33 string encodeResponse(MethodResponseData resp) { 34 auto root = new Element("methodResponse"); 35 if (resp.fault) { 36 enforce(resp.params.length == 1, new EncoderException("Fault response must contain exactly one parameter")); 37 auto fault = new Element("fault"); 38 fault ~= encodeValue(resp.params[0]); 39 root ~= fault; 40 } else 41 root ~= encodeParams(resp.params); 42 return toString(root); 43 } 44 45 private: 46 47 Element encodeParams(Variant[] params) { 48 auto node = new Element("params"); 49 foreach (ref par; params) 50 node ~= encodeParam(par); 51 return node; 52 } 53 54 Element encodeParam(Variant param) { 55 auto node = new Element("param"); 56 node ~= encodeValue(param); 57 return node; 58 } 59 60 Element encodeValue(Variant param) { 61 auto node = new Element("value"); 62 // TODO: allow associative arrays with keys of arbitrary type? 63 if (param.convertsTo!XmlRpcStruct) 64 node ~= encodeStructValue(param); 65 else if (param.convertsTo!XmlRpcArray) 66 node ~= encodeArrayValue(param); 67 else 68 node ~= encodePrimitiveValue(param); 69 return node; 70 } 71 72 Element encodeStructValue(Variant param) { 73 auto structNode = new Element("struct"); 74 foreach (key, ref value; param.get!XmlRpcStruct()) { 75 auto member = new Element("member"); 76 member ~= new Element("name", key); 77 member ~= encodeValue(value); 78 structNode ~= member; 79 } 80 return structNode; 81 } 82 83 Element encodeArrayValue(Variant param) { 84 auto array = new Element("array"); 85 auto data = new Element("data"); 86 foreach (ref value; param.get!XmlRpcArray) 87 data ~= encodeValue(value); 88 array ~= data; 89 return array; 90 } 91 92 Element encodePrimitiveValue(Variant param) { 93 // Almost everything converts to boolean, thus we need to check the exact type match here: 94 if (param.type() == typeid(bool)) 95 return new Element("boolean", param.get!bool() ? "1" : "0"); 96 97 if (param.convertsTo!int() || param.convertsTo!uint()) 98 return new Element("int", param.toString()); 99 100 // Extension (64-bit signed integer) 101 if (param.convertsTo!long() || param.convertsTo!ulong()) 102 return new Element("i8", param.toString()); 103 104 if (param.convertsTo!real()) 105 return new Element("double", param.toString()); 106 107 if (param.convertsTo!(const(string))() || param.convertsTo!(const(dstring))() || param.convertsTo!(const(wstring))()) { 108 return new Element("string", param.toString()); 109 } 110 111 if (param.convertsTo!DateTime()) { 112 const dt = param.get!DateTime(); 113 return new Element("dateTime.iso8601", dt.toISOString()); 114 } 115 116 if (param.convertsTo!(const(ubyte[]))()) { 117 const source = param.get!(const(ubyte[]))(); 118 char[] encoded = Base64.encode(source); 119 return new Element("base64", to!string(encoded)); 120 } 121 122 // Extension (nil) 123 if (param.convertsTo!(typeof(null)) && param.get!(typeof(null))() == null) 124 return new Element("nil"); 125 126 throw new EncoderException(format("Unable to encode the value of type %s", param.type())); 127 } 128 129 string toString(in Element e) { 130 return join(e.pretty(0), "\n"); 131 } 132 133 version (xmlrpc_unittest) unittest { 134 import xmlrpcc.decoder; 135 136 static import xmlrpcc.paramconv; 137 138 void assertResultsEqual(T)(T a, T b) { 139 writeln(a.toString()); 140 //writeln(b.toString()); 141 // Variant type does not compare arrays properly 142 assert(a.toString() == b.toString()); 143 } 144 145 // Hardcore parameter set 146 Variant[] params = [ 147 Variant(123U), Variant(cast(const) "Our sun is dying."), Variant(0xc0a1_e5ce_d64b_17UL), Variant([Variant(123), 148 Variant(12.3), Variant("abc"d), Variant(0), Variant(true), Variant(null) 149 ]), Variant(DateTime(2013, 8, 25, 13, 38, 42)), Variant(cast(const(ubyte[])) x"de ad be ef"), 150 Variant(["null" : Variant(null)]), Variant(["true" : Variant(true)]), Variant(["false" : Variant(false)]), 151 Variant(["we" : Variant(["need" : Variant("to go deeper")])])]; 152 153 /* 154 * Call 155 */ 156 auto methodCallData = MethodCallData("theMethod", params); 157 auto encoded = encodeCall(methodCallData); 158 MethodCallData decodedCall = decodeCall(encoded); 159 assertResultsEqual(decodedCall, methodCallData); 160 161 /* 162 * Response 163 */ 164 auto methodResponseData = MethodResponseData(false, params); 165 encoded = encodeResponse(methodResponseData); 166 MethodResponseData decodedResponse = decodeResponse(encoded); 167 assertResultsEqual(decodedResponse, methodResponseData); 168 169 /* 170 * Fault response 171 */ 172 auto faultParams = xmlrpcc.paramconv.paramsToVariantArray(["faultCode"w : Variant(42), 173 "faultString" : Variant("Fire in oxygen garden."d)]); 174 methodResponseData = MethodResponseData(true, faultParams); 175 encoded = encodeResponse(methodResponseData); 176 decodedResponse = decodeResponse(encoded); 177 assert(decodedResponse.fault); 178 assert(decodedResponse.params.length == 1); 179 assert(decodedResponse.params[0]["faultCode"] == 42); 180 assert(decodedResponse.params[0]["faultString"] == "Fire in oxygen garden."); 181 }