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 : Tuple, tuple; 26 import dls.util.nullable: Nullable; 27 private enum jsonrpcVersion = "2.0"; 28 private enum jsonrpcSeparator = "\r\n\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 dls.util.nullable: 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 dls.util.nullable: nullable; 98 if (request !is null) 99 { 100 send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable); 101 } 102 } 103 104 /// Sends a request or a notification message. 105 string send(string method, Nullable!JSONValue params = Nullable!JSONValue()) 106 { 107 import dls.protocol.handlers : hasResponseHandler, pushHandler; 108 import dls.protocol.logger : logger; 109 import std.uuid : randomUUID; 110 111 if (hasResponseHandler(method)) 112 { 113 immutable id = randomUUID().toString(); 114 pushHandler(id, method); 115 logger.log(`Sending request "%s": %s`, id, method); 116 send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError()); 117 return id; 118 } 119 120 logger.log("Sending notification: %s", method); 121 send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError()); 122 return null; 123 } 124 125 /// Sends a request or a notification message. 126 string send(T)(string method, T params) if (!is(T : Nullable!JSONValue)) 127 { 128 import dls.util.json : convertToJSON; 129 import dls.util.nullable: nullable; 130 131 return send(method, convertToJSON(params)); 132 } 133 134 /// Sends a response message. 135 void send(JSONValue id, Nullable!JSONValue result, 136 Nullable!ResponseError error = Nullable!ResponseError()) 137 { 138 import dls.protocol.logger : logger; 139 140 logger.log("Sending response with %s for request %s", error.isNull ? "result" : "error", id); 141 send!ResponseMessage(id, null, result, error); 142 } 143 144 /// Sends a response message. 145 private void send(T : Message)(JSONValue id, string method, 146 Nullable!JSONValue payload, Nullable!ResponseError error) 147 { 148 import std.meta : AliasSeq; 149 import std.traits : select; 150 151 auto message = new T(); 152 153 __traits(getMember, message, select!(__traits(hasMember, T, "params"))("params", "result")) = payload; 154 155 foreach (member; AliasSeq!("id", "method", "error")) 156 { 157 static if (__traits(hasMember, T, member)) 158 { 159 mixin("message." ~ member ~ " = " ~ member ~ ";"); 160 } 161 } 162 163 send(message); 164 } 165 166 private void send(T : Message)(T m) 167 { 168 import dls.util.communicator : communicator; 169 import dls.util.json : convertToJSON; 170 import std.conv : text; 171 172 auto message = convertToJSON(m); 173 auto messageString = message.get().toString(); 174 175 synchronized 176 { 177 communicator.write( 178 "Content-Length: " 179 ~ text(messageString.length) 180 ~ jsonrpcSeparator 181 ~ messageString 182 ); 183 184 communicator.flush(); 185 } 186 }