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