1 module dls.protocol.jsonrpc;
2 
3 import std.json : JSONValue;
4 import std.typecons : Nullable, Tuple, nullable, tuple;
5 
6 private enum jsonrpcVersion = "2.0";
7 private enum eol = "\r\n";
8 
9 abstract class Message
10 {
11     string jsonrpc = jsonrpcVersion;
12 }
13 
14 class RequestMessage : Message
15 {
16     JSONValue id;
17     string method;
18     Nullable!JSONValue params;
19 }
20 
21 class ResponseMessage : Message
22 {
23     JSONValue id;
24     Nullable!JSONValue result;
25     Nullable!ResponseError error;
26 }
27 
28 class ResponseError
29 {
30     int code;
31     string message;
32     Nullable!JSONValue data;
33 
34     static ResponseError fromErrorCode(ErrorCodes errorCode, JSONValue data)
35     {
36         auto response = new ResponseError();
37         response.code = errorCode[0];
38         response.message = errorCode[1];
39         response.data = data;
40         return response.nullable;
41     }
42 }
43 
44 class NotificationMessage : Message
45 {
46     string method;
47     Nullable!JSONValue params;
48 }
49 
50 enum ErrorCodes : Tuple!(int, string)
51 {
52     parseError = tuple(-32_700, "Parse error"),
53     invalidRequest = tuple(-32_600,
54             "Invalid Request"),
55     methodNotFound = tuple(
56             -32_601, "Method not found"),
57     invalidParams = tuple(-32_602,
58             "Invalid params"),
59     internalError = tuple(-32_603,
60             "Internal error"),
61     serverNotInitialized = tuple(-32_202,
62             "Server not initialized"),
63     unknownErrorCode = tuple(-32_201,
64             "Unknown error"),
65     requestCancelled = tuple(-32_800, "Request cancelled")
66 }
67 
68 class CancelParams
69 {
70     JSONValue id;
71 }
72 
73 void sendError(ErrorCodes error, RequestMessage request, JSONValue data)
74 {
75     if (request !is null)
76     {
77         send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable);
78     }
79 }
80 
81 /++ Sends a request or a notification message. +/
82 string send(string method, Nullable!JSONValue params = Nullable!JSONValue())
83 {
84     import dls.protocol.handlers : hasRegisteredHandler, pushHandler;
85     import std.uuid : randomUUID;
86 
87     if (hasRegisteredHandler(method))
88     {
89         auto id = "dls-" ~ randomUUID().toString();
90         pushHandler(id, method);
91         send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError());
92         return id;
93     }
94 
95     send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError());
96     return null;
97 }
98 
99 string send(T)(string method, T params) if (!is(T : Nullable!JSONValue))
100 {
101     import dls.util.json : convertToJSON;
102 
103     return send(method, convertToJSON(params).nullable);
104 }
105 
106 /++ Sends a response message. +/
107 void send(JSONValue id, Nullable!JSONValue result,
108         Nullable!ResponseError error = Nullable!ResponseError())
109 {
110     send!ResponseMessage(id, null, result, error);
111 }
112 
113 private void send(T : Message)(JSONValue id, string method,
114         Nullable!JSONValue payload, Nullable!ResponseError error)
115 {
116     import std.meta : AliasSeq;
117     import std.traits : select;
118 
119     auto message = new T();
120 
121     __traits(getMember, message, select!(__traits(hasMember, T, "params"))("params", "result")) = payload;
122 
123     foreach (member; AliasSeq!("id", "method", "error"))
124     {
125         static if (__traits(hasMember, T, member))
126         {
127             mixin("message." ~ member ~ " = " ~ member ~ ";");
128         }
129     }
130 
131     send(message);
132 }
133 
134 private void send(T : Message)(T m)
135 {
136     import dls.util.json : convertToJSON;
137     import std.conv : to;
138     import std.stdio : stdout;
139     import std.utf : toUTF8;
140 
141     auto message = convertToJSON(m);
142     auto messageString = message.get().toString();
143 
144     synchronized
145     {
146         foreach (chunk; ["Content-Length: ", messageString.length.to!string,
147                 eol, eol, messageString])
148         {
149             stdout.rawWrite(chunk.toUTF8());
150         }
151 
152         stdout.flush();
153     }
154 }