1 // Written in D programming language 2 /** 3 * JSON-RPC 2.0 Protocol<br> 4 * 5 * $(B This module contain JSON-RPC 2.0 request) 6 * 7 * See_Also: 8 * $(LINK http://www.jsonrpc.org/specification) 9 * 10 * Copyright: © 2014 DSoftOut 11 * License: Subject to the terms of the MIT license, as written in the included LICENSE file. 12 * Authors: Zaramzan <shamyan.roman@gmail.com> 13 * 14 */ 15 16 module json_rpc.request; 17 18 import std.conv; 19 import std.exception; 20 21 import vibe.data.json; 22 23 import util; 24 25 import json_rpc.error; 26 27 28 /** 29 * structure describes JSON-RPC 2.0 request 30 * 31 * Example 32 * ------ 33 * auto req = RpcRequest(json); 34 * writefln("id=%s method=%s params:%s", req.id, req.method, req.params); 35 * ------ 36 * 37 * Authors: Zaramzan <shamyan.roman@gmail.com> 38 */ 39 struct RpcRequest 40 { 41 @required 42 string jsonrpc; 43 44 @required 45 string method; 46 47 @possible 48 string[] params; 49 50 @possible 51 Json id = Json(null); 52 53 string[string] auth = null; 54 55 this(Json json) 56 { 57 this = tryEx!(RpcInvalidRequest, deserializeFromJson!RpcRequest)(json); 58 enforceEx!RpcInvalidRequest(isRpc2, "Unsupported rpc version"); 59 } 60 61 this(string jsonStr) 62 { 63 auto json = tryEx!(RpcParseError, parseJsonString)(jsonStr); 64 65 this = this(json); 66 } 67 68 bool isRpc2() 69 { 70 return jsonrpc == "2.0"; 71 } 72 73 void toString(scope void delegate(const(char)[]) sink) const 74 { 75 sink("JSON RPC request {\n"); 76 sink(text("Version: ", jsonrpc, "\n")); 77 sink(text("Method: ", method, "\n")); 78 sink(text("Params: ", params, "\n")); 79 sink(text("ID: ", id, "\n")); 80 sink(text("Auth: ", auth, "\n")); 81 sink("}"); 82 } 83 84 version (unittest) 85 { 86 this(string jsonrpc, string method, string[] params, Json id) 87 { 88 this.jsonrpc = jsonrpc; 89 90 this.method = method; 91 92 this.params = params; 93 94 this.id = id; 95 96 } 97 98 this(string jsonrpc, string method, string[] params) 99 { 100 this.jsonrpc = jsonrpc; 101 102 this.method = method; 103 104 this.params = params; 105 } 106 107 bool eq(RpcRequest s2) 108 { 109 if (this.id == s2.id) 110 { 111 if (this.method == s2.method) 112 { 113 if (this.jsonrpc == s2.jsonrpc) 114 { 115 116 if (this.params.length == s2.params.length) 117 { 118 foreach(int i, string vl; s2.params) 119 { 120 if (this.params[i] != s2.params[i]) 121 return false; 122 } 123 124 return true; 125 } 126 127 } 128 } 129 } 130 return false; 131 } 132 } 133 } 134 135 136 version(unittest) 137 { 138 // For local tests 139 enum example1 = 140 "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 1}"; 141 142 enum example2 = 143 "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": {\"subtrahend\": 23, \"minuend\": 42}, \"id\": 3}"; 144 145 enum example3 = 146 "{\"jsonrpc\": \"2.0\", \"method\": \"update\", \"params\": [1,2,3,4,5]}"; 147 148 enum example4 = 149 "{\"jsonrpc\": \"2.0\", \"method\": \"foobar\"}"; 150 151 enum example5 = 152 "{\"jsonrpc\": \"2.0\", \"method\": \"foobar, \"params\": \"bar\", \"baz]"; 153 154 enum example6 = 155 "[]"; 156 157 enum example7 = 158 "{\"jsonrpc\": \"2.0\", \"method\": \"divide\", \"params\": [42, 23], \"id\": 1}"; 159 160 enum example8 = 161 "{\"jsonrpc\": \"2.0\", \"method\": \"mult\", \"params\": [33,22]}"; 162 163 //For global tests 164 __gshared RpcRequest normalReq = RpcRequest("2.0", "subtract", ["42", "23"]); 165 166 __gshared RpcRequest notificationReq = RpcRequest("2.0", "multiply", ["42", "23"]); 167 168 __gshared RpcRequest methodNotFoundReq = RpcRequest("2.0","foobar", null); 169 170 __gshared RpcRequest invalidParamsReq = RpcRequest("2.0", "subtract", ["sunday"]); 171 } 172 173 unittest 174 { 175 import std.stdio; 176 import std.exception; 177 178 //Testing normal rpc request 179 auto req1 = RpcRequest("2.0", "subtract", ["42", "23"], Json(1)); 180 assert(RpcRequest(example1).eq(req1), "RpcRequest test failed"); 181 182 //Testing RpcInvalidRequest() 183 assertThrown!RpcInvalidRequest(RpcRequest(example2)); 184 185 186 //Testing rpc notification with params 187 auto req3 = RpcRequest("2.0", "update", ["1", "2", "3", "4", "5"], Json(null)); 188 assert(RpcRequest(example3).eq(req3), "RpcRequest test failed"); 189 190 //Testing rpc notification w/o params 191 auto req4 = RpcRequest("2.0", "foobar", null, Json(null)); 192 assert(RpcRequest(example4).eq(req4), "RpcRequest test failed"); 193 194 //Testing invalid json 195 assertThrown!RpcParseError(RpcRequest(example5), "RpcRequest test failed"); 196 197 //Testing empty json array 198 assertThrown!RpcInvalidRequest(RpcRequest(example6), "RpcRequest test failed"); 199 200 201 }