1 /** 2 * Simple XML-RPC serializer. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.xmlrpc; 15 16 import std.string; 17 import std.conv; 18 import std.exception; 19 20 import ae.utils.xml; 21 22 /// Serialize a D value `v` to an XML node according to XML-RPC. 23 XmlNode valueToXml(T)(T v) 24 { 25 static if (is(T : string)) 26 return (new XmlNode(XmlNodeType.Node, "value")) 27 .addChild((new XmlNode(XmlNodeType.Node, "string")) 28 .addChild(new XmlNode(XmlNodeType.Text, v)) 29 ); 30 else 31 static if (is(T : long)) 32 return (new XmlNode(XmlNodeType.Node, "value")) 33 .addChild((new XmlNode(XmlNodeType.Node, "integer")) 34 .addChild(new XmlNode(XmlNodeType.Text, to!string(v))) 35 ); 36 else 37 static if (is(T U : U[])) 38 { 39 XmlNode data = new XmlNode(XmlNodeType.Node, "data"); 40 foreach (item; v) 41 data.addChild(valueToXml(item)); 42 return (new XmlNode(XmlNodeType.Node, "value")) 43 .addChild((new XmlNode(XmlNodeType.Node, "array")) 44 .addChild(data) 45 ); 46 } 47 else 48 static if (is(T==struct)) 49 { 50 XmlNode s = new XmlNode(XmlNodeType.Node, "struct"); 51 foreach (i, field; v.tupleof) 52 s.addChild((new XmlNode(XmlNodeType.Node, "member")) 53 .addChild((new XmlNode(XmlNodeType.Node, "name")) 54 .addChild(new XmlNode(XmlNodeType.Text, v.tupleof[i].stringof[2..$])) 55 ) 56 .addChild(valueToXml(field)) 57 ); 58 return (new XmlNode(XmlNodeType.Node, "value")) 59 .addChild(s); 60 } 61 else 62 static if (is(typeof(T.keys)) && is(typeof(T.values))) 63 { 64 XmlNode s = new XmlNode(XmlNodeType.Node, "struct"); 65 foreach (key, value; v) 66 s.addChild((new XmlNode(XmlNodeType.Node, "member")) 67 .addChild((new XmlNode(XmlNodeType.Node, "name")) 68 .addChild(new XmlNode(XmlNodeType.Text, key)) 69 ) 70 .addChild(valueToXml(value)) 71 ); 72 return (new XmlNode(XmlNodeType.Node, "value")) 73 .addChild(s); 74 } 75 else 76 static if (is(typeof(*v))) 77 return valueToXml(*v); 78 else 79 static assert(0, "Can't encode " ~ T.stringof ~ " to XML-RPC"); 80 } 81 82 /// Format an XML-RPC method call. 83 XmlDocument formatXmlRpcCall(T...)(string methodName, T params) 84 { 85 auto paramsNode = new XmlNode(XmlNodeType.Node, "params"); 86 87 foreach (param; params) 88 paramsNode.addChild((new XmlNode(XmlNodeType.Node, "param")) 89 .addChild(valueToXml(param)) 90 ); 91 92 auto doc = 93 (new XmlDocument()) 94 .addChild((new XmlNode(XmlNodeType.Meta, "xml")) 95 .addAttribute("version", "1.0") 96 ) 97 .addChild((new XmlNode(XmlNodeType.Node, "methodCall")) 98 .addChild((new XmlNode(XmlNodeType.Node, "methodName")) 99 .addChild(new XmlNode(XmlNodeType.Text, methodName)) 100 ) 101 .addChild(paramsNode) 102 ); 103 return cast(XmlDocument)doc; 104 } 105 106 /// Parse a D value from an XML node according to XML-RPC. 107 T parseXmlValue(T)(XmlNode value) 108 { 109 enforce(value.type==XmlNodeType.Node && value.tag == "value", "Expected <value> node"); 110 enforce(value.children.length==1, "Expected one <value> child"); 111 XmlNode typeNode = value[0]; 112 enforce(typeNode.type==XmlNodeType.Node, "Expected <value> child to be XML node"); 113 string valueType = typeNode.tag; 114 115 static if (is(T : string)) 116 { 117 enforce(valueType == "string", "Expected <string>"); 118 enforce(typeNode.children.length==1, "Expected one <string> child"); 119 XmlNode contentNode = typeNode[0]; 120 enforce(contentNode.type==XmlNodeType.Text, "Expected <string> child to be text node"); 121 return contentNode.tag; 122 } 123 else 124 static if (is(T == bool)) 125 { 126 enforce(valueType == "boolean", "Expected <boolean>"); 127 enforce(typeNode.children.length==1, "Expected one <boolean> child"); 128 XmlNode contentNode = typeNode[0]; 129 enforce(contentNode.type==XmlNodeType.Text, "Expected <boolean> child to be text node"); 130 enforce(contentNode.tag == "0" || contentNode.tag == "1", "Expected <boolean> child to be 0 or 1"); 131 return contentNode.tag == "1"; 132 } 133 else 134 static if (is(T : long)) 135 { 136 enforce(valueType == "integer" || valueType == "int" || valueType == "i4", "Expected <integer> or <int> or <i4>"); 137 enforce(typeNode.children.length==1, "Expected one <integer> child"); 138 XmlNode contentNode = typeNode[0]; 139 enforce(contentNode.type==XmlNodeType.Text, "Expected <integer> child to be text node"); 140 string s = contentNode.tag; 141 static if (is(T==byte)) 142 return to!byte(s); 143 else 144 static if (is(T==ubyte)) 145 return to!ubyte(s); 146 else 147 static if (is(T==short)) 148 return to!short(s); 149 else 150 static if (is(T==ushort)) 151 return to!ushort(s); 152 else 153 static if (is(T==int)) 154 return to!int(s); 155 else 156 static if (is(T==uint)) 157 return to!uint(s); 158 else 159 static if (is(T==long)) 160 return to!long(s); 161 else 162 static if (is(T==ulong)) 163 return to!ulong(s); 164 else 165 static assert(0, "Don't know how to parse numerical type " ~ T.stringof); 166 } 167 else 168 static if (is(T == double)) 169 { 170 enforce(valueType == "double", "Expected <double>"); 171 enforce(typeNode.children.length==1, "Expected one <double> child"); 172 XmlNode contentNode = typeNode[0]; 173 enforce(contentNode.type==XmlNodeType.Text, "Expected <double> child to be text node"); 174 string s = contentNode.tag; 175 return to!double(s); 176 } 177 else 178 static if (is(T U : U[])) 179 { 180 enforce(valueType == "array", "Expected <array>"); 181 enforce(typeNode.children.length==1, "Expected one <array> child"); 182 XmlNode dataNode = typeNode[0]; 183 enforce(dataNode.type==XmlNodeType.Node && dataNode.tag == "data", "Expected <data>"); 184 T result = new U[dataNode.children.length]; 185 foreach (i, child; dataNode.children) 186 result[i] = parseXmlValue!(U)(child); 187 return result; 188 } 189 else 190 static if (is(T==struct)) 191 { 192 enforce(valueType == "struct", "Expected <struct>"); 193 T v; 194 foreach (memberNode; typeNode.children) 195 { 196 enforce(memberNode.type==XmlNodeType.Node && memberNode.tag == "member", "Expected <member>"); 197 enforce(memberNode.children.length == 2, "Expected 2 <member> children"); 198 auto nameNode = memberNode[0]; 199 enforce(nameNode.type==XmlNodeType.Node && nameNode.tag == "name", "Expected <name>"); 200 enforce(nameNode.children.length == 1, "Expected one <name> child"); 201 XmlNode contentNode = nameNode[0]; 202 enforce(contentNode.type==XmlNodeType.Text, "Expected <name> child to be text node"); 203 string memberName = contentNode.tag; 204 205 bool found; 206 foreach (i, field; v.tupleof) 207 if (v.tupleof[i].stringof[2..$] == memberName) 208 { 209 v.tupleof[i] = parseXmlValue!(typeof(v.tupleof[i]))(memberNode[1]); 210 found = true; 211 break; 212 } 213 enforce(found, "Unknown field " ~ memberName); 214 } 215 return v; 216 } 217 else 218 static assert(0, "Can't decode " ~ T.stringof ~ " from XML-RPC"); 219 } 220 221 /// Exception class used for XML-RPC response errors. 222 class XmlRpcException : Exception 223 { 224 int faultCode; /// 225 string faultString; /// 226 227 this(int faultCode, string faultString) 228 { 229 this.faultCode = faultCode; 230 this.faultString = faultString; 231 super(format("XML-RPC error %d (%s)", faultCode, faultString)); 232 } /// 233 } 234 235 /// Parse an XML-RPC response. 236 /// On success, return the response return value, deserialized as `T`. 237 /// On failure, throw `XmlRpcException`. 238 T parseXmlRpcResponse(T)(XmlDocument doc) 239 { 240 auto methodResponse = doc["methodResponse"]; 241 auto fault = methodResponse.findChild("fault"); 242 if (fault) 243 { 244 struct Fault 245 { 246 int faultCode; 247 string faultString; 248 } 249 auto details = parseXmlValue!(Fault)(fault["value"]); 250 throw new XmlRpcException(details.faultCode, details.faultString); 251 } 252 253 auto params = methodResponse.findChild("params"); 254 enforce(params.children.length==1, "Only one response parameter supported"); 255 return parseXmlValue!(T)(params["param"][0]); 256 }