1 module djrpc.v2; 2 3 import std.exception; 4 import std.json; 5 import std.typecons : Nullable; 6 import djrpc.base; 7 import djrpc.exception; 8 9 class JsonRpc2Request : JsonRpcRequest { 10 private JSONValue id; 11 private string method; 12 private JSONValue params; 13 14 this(JSONValue id, string method, JSONValue params) { 15 this.id = id; 16 this.method = method; 17 this.params = params; 18 } 19 20 JsonRpcVersion getVersion() { 21 return JsonRpcVersion.V2_0; 22 } 23 24 static JsonRpcMessage parse(string msg) { 25 JSONValue data = parseJSON(msg); 26 27 string rpc_version = data["jsonrpc"].str; 28 29 if (rpc_version != "2.0") { 30 throw new MalformedRpcMessageException("not a jsonrpc 2.0 request"); 31 } 32 33 JSONValue id = data["id"]; 34 string method = data["method"].str; 35 JSONValue params; 36 37 if (("params" in data) != null) { 38 params = data["params"]; 39 } else { 40 params = JSONValue(null); 41 } 42 43 return new JsonRpc2Request(id, method, params); 44 } 45 46 string encode() { 47 JSONValue request = [ "id": this.id ]; 48 request["jsonrpc"] = JsonRpcVersion.V2_0; 49 request["method"] = this.method; 50 request["params"] = this.params; 51 return toJSON(request); 52 } 53 54 JSONValue getID() { 55 return this.id; 56 } 57 58 string getMethod() { 59 return this.method; 60 } 61 62 JSONValue getParams() { 63 return this.params; 64 } 65 } 66 67 @("request valid jsonrpc") 68 unittest { 69 string json = "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 1}"; 70 JsonRpc2Request req = cast(JsonRpc2Request) JsonRpc2Request.parse(json); 71 72 assert(req.getVersion() == JsonRpcVersion.V2_0); 73 assert(req.getID().integer == 1); 74 assert(req.getMethod() == "subtract"); 75 } 76 77 @("request omit params") 78 @("valid jsonrpc 2.0 request") 79 unittest { 80 string json = "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"id\": 1}"; 81 assertNotThrown!MalformedRpcMessageException(JsonRpc2Request.parse(json)); 82 } 83 84 @("request wrong version number") 85 unittest { 86 string json = "{\"jsonrpc\": \"1.0\", \"method\": \"subtract\", \"params\": [], \"id\": 1}"; 87 assertThrown!MalformedRpcMessageException(JsonRpc2Request.parse(json)); 88 } 89 90 class JsonRpc2Response : JsonRpcResponse { 91 private JSONValue id; 92 private Nullable!JsonRpc2Error error; 93 private Nullable!JSONValue result; 94 95 this(JSONValue id, Nullable!JsonRpc2Error err, Nullable!JSONValue result) { 96 this.id = id; 97 this.error = err; 98 this.result = result; 99 } 100 101 JsonRpcVersion getVersion() { 102 return JsonRpcVersion.V2_0; 103 } 104 105 static JsonRpcMessage parse(string msg) { 106 JSONValue data = parseJSON(msg); 107 108 string rpc_version = data["jsonrpc"].str; 109 110 if (rpc_version != "2.0") { 111 throw new MalformedRpcMessageException("not a jsonrpc 2.0 response"); 112 } 113 114 JSONValue id = data["id"]; 115 Nullable!JsonRpc2Error error = Nullable!JsonRpc2Error.init; 116 Nullable!JSONValue result = Nullable!JSONValue.init; 117 118 if ("error" in data) { 119 error = Nullable!JsonRpc2Error(JsonRpc2Error.fromJSON(data["error"])); 120 } 121 122 if ("result" in data) { 123 result = Nullable!JSONValue(data["result"]); 124 } 125 126 return new JsonRpc2Response(id, error, result); 127 } 128 129 string encode() { 130 JSONValue request = [ "id": this.id ]; 131 request["jsonrpc"] = JsonRpcVersion.V2_0; 132 133 if(this.error.isNull) { 134 request["result"] = this.result.get; 135 } else { 136 request["error"] = this.error.get.toJSON; 137 } 138 139 return toJSON(request); 140 } 141 142 143 JSONValue getID() { 144 return this.id; 145 } 146 147 bool success() { 148 return error.isNull; 149 } 150 151 Nullable!JSONValue getResult() { 152 return this.result; 153 } 154 155 Nullable!JSONValue getError() { 156 if (!this.error.isNull) { 157 JsonRpc2Error err = this.error.get; 158 return Nullable!JSONValue(err.toJSON()); 159 } 160 161 return Nullable!JSONValue.init; 162 } 163 164 Nullable!JsonRpc2Error getErrorObject() { 165 return this.error; 166 } 167 } 168 169 @("response valid jsonrpc 2.0") 170 unittest { 171 string json = "{\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": 1}"; 172 JsonRpc2Response res = cast(JsonRpc2Response) JsonRpc2Response.parse(json); 173 174 assert(res.getVersion() == JsonRpcVersion.V2_0); 175 assert(res.getID().integer == 1); 176 assert(!res.getResult.isNull()); 177 assert(res.getError.isNull()); 178 assert(res.getResult.get().integer() == 19); 179 } 180 181 @("response valid with error") 182 unittest { 183 string json = "{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32601, \"message\": \"Method not found\"}, \"id\": \"1\"}"; 184 JsonRpc2Response res = cast(JsonRpc2Response) JsonRpc2Response.parse(json); 185 186 assert(res.getVersion() == JsonRpcVersion.V2_0); 187 assert(res.getID().str() == "1"); 188 assert(res.getResult.isNull()); 189 assert(!res.getError().isNull()); 190 assert(res.getErrorObject().getCode() == -32601); 191 } 192 193 class JsonRpc2Error { 194 private long code; 195 private string message; 196 private Nullable!JSONValue data; 197 198 this(long code, string msg, Nullable!JSONValue data) { 199 this.code = code; 200 this.message = msg; 201 this.data = data; 202 } 203 204 static JsonRpc2Error fromJSON(JSONValue json) { 205 long code = json["code"].integer; 206 string msg = json["message"].str; 207 Nullable!JSONValue data = Nullable!JSONValue.init; 208 209 if ("data" in json) { 210 data = Nullable!JSONValue(json["data"]); 211 } 212 213 return new JsonRpc2Error(code, msg, data); 214 } 215 216 JSONValue toJSON() { 217 JSONValue json = JSONValue(); 218 json["code"] = this.code; 219 json["message"] = this.message; 220 221 if (!this.data.isNull()) { 222 json["data"] = this.data.get(); 223 } 224 225 return json; 226 } 227 228 long getCode() { 229 return this.code; 230 } 231 232 string getMessage() { 233 return this.message; 234 } 235 236 Nullable!JSONValue getData() { 237 return this.data; 238 } 239 }