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 std.json : JSONValue; 24 import std.typecons : Nullable, Tuple, tuple; 25 26 private enum jsonrpcVersion = "2.0"; 27 private enum eol = "\r\n"; 28 29 abstract class Message 30 { 31 string jsonrpc = jsonrpcVersion; 32 } 33 34 class RequestMessage : Message 35 { 36 JSONValue id; 37 string method; 38 Nullable!JSONValue params; 39 } 40 41 class ResponseMessage : Message 42 { 43 JSONValue id; 44 Nullable!JSONValue result; 45 Nullable!ResponseError error; 46 } 47 48 class ResponseError 49 { 50 long code; 51 string message; 52 Nullable!JSONValue data; 53 54 static ResponseError fromErrorCode(ErrorCodes errorCode, JSONValue data) 55 { 56 import std.typecons : nullable; 57 58 auto response = new ResponseError(); 59 response.code = errorCode[0]; 60 response.message = errorCode[1]; 61 response.data = data; 62 return response.nullable; 63 } 64 } 65 66 class NotificationMessage : Message 67 { 68 string method; 69 Nullable!JSONValue params; 70 } 71 72 enum ErrorCodes : Tuple!(long, string) 73 { 74 parseError = tuple(-32_700L, "Parse error"), 75 invalidRequest = tuple(-32_600L, 76 "Invalid Request"), 77 methodNotFound = tuple( 78 -32_601L, "Method not found"), 79 invalidParams = tuple(-32_602L, 80 "Invalid params"), 81 internalError = tuple(-32_603L, 82 "Internal error"), 83 serverNotInitialized = tuple(-32_202L, 84 "Server not initialized"), 85 unknownErrorCode = tuple( 86 -32_201L, "Unknown error"), 87 requestCancelled = tuple(-32_800L, "Request cancelled") 88 } 89 90 class CancelParams 91 { 92 JSONValue id; 93 } 94 95 class InvalidParamsException : Exception 96 { 97 this(string msg) 98 { 99 super("Invalid parameters: " ~ msg); 100 } 101 } 102 103 void sendError(ErrorCodes error, RequestMessage request, JSONValue data) 104 { 105 import std.typecons : nullable; 106 107 if (request !is null) 108 { 109 send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable); 110 } 111 } 112 113 /++ Sends a request or a notification message. +/ 114 string send(string method, Nullable!JSONValue params = Nullable!JSONValue()) 115 { 116 import dls.protocol.handlers : hasResponseHandler, pushHandler; 117 import std.uuid : randomUUID; 118 119 if (hasResponseHandler(method)) 120 { 121 auto id = randomUUID().toString(); 122 pushHandler(id, method); 123 send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError()); 124 return id; 125 } 126 127 send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError()); 128 return null; 129 } 130 131 string send(T)(string method, T params) if (!is(T : Nullable!JSONValue)) 132 { 133 import dls.util.json : convertToJSON; 134 import std.typecons : nullable; 135 136 return send(method, convertToJSON(params).nullable); 137 } 138 139 /++ Sends a response message. +/ 140 void send(JSONValue id, Nullable!JSONValue result, 141 Nullable!ResponseError error = Nullable!ResponseError()) 142 { 143 send!ResponseMessage(id, null, result, error); 144 } 145 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.json : convertToJSON; 170 import std.conv : to; 171 import std.stdio : stdout; 172 import std.utf : toUTF8; 173 174 auto message = convertToJSON(m); 175 auto messageString = message.get().toString(); 176 177 synchronized 178 { 179 foreach (chunk; ["Content-Length: ", messageString.length.to!string, 180 eol, eol, messageString]) 181 { 182 stdout.rawWrite(chunk.toUTF8()); 183 } 184 185 stdout.flush(); 186 } 187 }