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 }