1 /* 2 *Copyright (C) 2018 Laurent Tréguier 3 * 4 *This file is part of DLS. 5 * 6 *DLS is free software: you can redistribute it and/or modify 7 *it under the terms of the GNU General Public License as published by 8 *the Free Software Foundation, either version 3 of the License, or 9 *(at your option) any later version. 10 * 11 *DLS is distributed in the hope that it will be useful, 12 *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 *GNU General Public License for more details. 15 * 16 *You should have received a copy of the GNU General Public License 17 *along with DLS. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 module dls.protocol.jsonrpc; 22 23 import dls.util.i18n : Tr; 24 import std.json : JSONValue; 25 import std.typecons : Nullable, Tuple, tuple; 26 27 private enum jsonrpcVersion = "2.0"; 28 private enum eol = "\r\n"; 29 30 abstract class Message 31 { 32 string jsonrpc = jsonrpcVersion; 33 } 34 35 class RequestMessage : Message 36 { 37 JSONValue id; 38 string method; 39 Nullable!JSONValue params; 40 } 41 42 class ResponseMessage : Message 43 { 44 JSONValue id; 45 Nullable!JSONValue result; 46 Nullable!ResponseError error; 47 } 48 49 class ResponseError 50 { 51 long code; 52 string message; 53 Nullable!JSONValue data; 54 55 static ResponseError fromErrorCode(ErrorCodes errorCode, JSONValue data) 56 { 57 import dls.util.i18n : tr; 58 import std.typecons : nullable; 59 60 auto response = new ResponseError(); 61 response.code = errorCode[0]; 62 response.message = tr(errorCode[1]); 63 response.data = data; 64 return response.nullable; 65 } 66 } 67 68 class NotificationMessage : Message 69 { 70 string method; 71 Nullable!JSONValue params; 72 } 73 74 enum ErrorCodes : Tuple!(long, Tr) 75 { 76 //dfmt off 77 parseError = tuple(-32_700L, Tr.app_rpc_errorCodes_parseError), 78 invalidRequest = tuple(-32_600L, Tr.app_rpc_errorCodes_invalidRequest), 79 methodNotFound = tuple(-32_601L, Tr.app_rpc_errorCodes_methodNotFound), 80 invalidParams = tuple(-32_602L, Tr.app_rpc_errorCodes_invalidParams), 81 internalError = tuple(-32_603L, Tr.app_rpc_errorCodes_internalError), 82 serverErrorStart = tuple(-32_099L, Tr._), 83 serverErrorEnd = tuple(-32_000L, Tr._), 84 serverNotInitialized = tuple(-32_002L, Tr.app_rpc_errorCodes_serverNotInitialized), 85 unknownErrorCode = tuple(-32_001L, Tr.app_rpc_errorCodes_unknownErrorCode), 86 requestCancelled = tuple(-32_800L, Tr.app_rpc_errorCodes_requestCancelled), 87 //dfmt on 88 } 89 90 class CancelParams 91 { 92 JSONValue id; 93 } 94 95 void sendError(ErrorCodes error, RequestMessage request, JSONValue data) 96 { 97 import std.typecons : nullable; 98 99 if (request !is null) 100 { 101 send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable); 102 } 103 } 104 105 /// Sends a request or a notification message. 106 string send(string method, Nullable!JSONValue params = Nullable!JSONValue()) 107 { 108 import dls.protocol.handlers : hasResponseHandler, pushHandler; 109 import dls.protocol.logger : logger; 110 import std.uuid : randomUUID; 111 112 if (hasResponseHandler(method)) 113 { 114 immutable id = randomUUID().toString(); 115 pushHandler(id, method); 116 logger.log(`Sending request "%s": %s`, id, method); 117 send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError()); 118 return id; 119 } 120 121 logger.log("Sending notification: %s", method); 122 send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError()); 123 return null; 124 } 125 126 /// Sends a request or a notification message. 127 string send(T)(string method, T params) if (!is(T : Nullable!JSONValue)) 128 { 129 import dls.util.json : convertToJSON; 130 import std.typecons : nullable; 131 132 return send(method, convertToJSON(params).nullable); 133 } 134 135 /// Sends a response message. 136 void send(JSONValue id, Nullable!JSONValue result, 137 Nullable!ResponseError error = Nullable!ResponseError()) 138 { 139 import dls.protocol.logger : logger; 140 141 logger.log("Sending response with %s for request %s", error.isNull ? "result" : "error", id); 142 send!ResponseMessage(id, null, result, error); 143 } 144 145 /// Sends a response message. 146 private void send(T : Message)(JSONValue id, string method, 147 Nullable!JSONValue payload, Nullable!ResponseError error) 148 { 149 import std.meta : AliasSeq; 150 import std.traits : select; 151 152 auto message = new T(); 153 154 __traits(getMember, message, select!(__traits(hasMember, T, "params"))("params", "result")) = payload; 155 156 foreach (member; AliasSeq!("id", "method", "error")) 157 { 158 static if (__traits(hasMember, T, member)) 159 { 160 mixin("message." ~ member ~ " = " ~ member ~ ";"); 161 } 162 } 163 164 send(message); 165 } 166 167 private void send(T : Message)(T m) 168 { 169 import dls.util.communicator : communicator; 170 import dls.util.json : convertToJSON; 171 import std.conv : text; 172 import std.utf : toUTF8; 173 174 auto message = convertToJSON(m); 175 auto messageString = message.get().toString().toUTF8(); 176 177 synchronized 178 { 179 foreach (chunk; ["Content-Length: ", text(messageString.length), eol, eol, messageString]) 180 { 181 communicator.write(chunk); 182 } 183 184 communicator.flush(); 185 } 186 }