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 }