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.handlers;
22 
23 import std.json : JSONValue;
24 import std.traits : isSomeFunction;
25 import dls.util.nullable : Nullable;
26 
27 alias RequestHandler = Nullable!JSONValue delegate(Nullable!JSONValue);
28 alias NotificationHandler = void delegate(Nullable!JSONValue);
29 alias ResponseHandler = void delegate(string id, Nullable!JSONValue);
30 
31 private shared RequestHandler[string] requestHandlers;
32 private shared NotificationHandler[string] notificationHandlers;
33 private shared ResponseHandler[string] responseHandlers;
34 private shared ResponseHandler[string] runtimeResponseHandlers;
35 
36 class HandlerNotFoundException : Exception
37 {
38     this(const string method)
39     {
40         super("No handler found for method " ~ method);
41     }
42 }
43 
44 /++
45 Checks if a method has a response handler registered for it. Used to determine
46 if the server should send a request or a notification to the client (if the
47 method has a response handler, then the server will expect a response and thus
48 send a request instead of a notification).
49 +/
50 bool hasResponseHandler(const string method)
51 {
52     return (method in responseHandlers) !is null;
53 }
54 
55 /++
56 Registers a new handler of any kind (`RequestHandler`, `NotificationHandler` or
57 `ResponseHandler`).
58 +/
59 void pushHandler(F)(const string method, F func)
60         if (isSomeFunction!F && !is(F == RequestHandler)
61             && !is(F == NotificationHandler) && !is(F == ResponseHandler))
62 {
63     import dls.util.json : convertFromJSON;
64     import std.traits : Parameters, ReturnType;
65 
66     static if ((Parameters!F).length == 1)
67     {
68         pushHandler(method, (Nullable!JSONValue params) {
69             import dls.util.json : convertToJSON;
70 
71             auto arg = convertFromJSON!((Parameters!F)[0])(params.isNull ? JSONValue(null) : params);
72 
73             static if (is(ReturnType!F == void))
74             {
75                 func(arg);
76             }
77             else
78             {
79                 return convertToJSON(func(arg));
80             }
81         });
82     }
83     else static if ((Parameters!F).length == 2)
84     {
85         pushHandler(method, (string id, Nullable!JSONValue params) => func(id,
86                 convertFromJSON!((Parameters!F)[1])(params.isNull ? JSONValue(null) : params)));
87     }
88     else
89     {
90         static assert(false);
91     }
92 }
93 
94 /++ Registers a new static `RequestHandler`. +/
95 private void pushHandler(const string method, RequestHandler h)
96 {
97     requestHandlers[method] = h;
98 }
99 
100 /++ Registers a new static `NotificationHandler`. +/
101 private void pushHandler(const string method, NotificationHandler h)
102 {
103     notificationHandlers[method] = h;
104 }
105 
106 /++ Registers a new static `ResponseHandler`. +/
107 private void pushHandler(const string method, ResponseHandler h)
108 {
109     responseHandlers[method] = h;
110 }
111 
112 /++ Registers a new dynamic `ResponseHandler` (used at runtime) +/
113 void pushHandler(const string id, const string method)
114 {
115     runtimeResponseHandlers[id] = responseHandlers[method];
116 }
117 
118 /++
119 Returns the `RequestHandler`/`NotificationHandler`/`ResponseHandler`
120 corresponding to a specific LSP method.
121 +/
122 T handler(T)(const string methodOrId)
123         if (is(T == RequestHandler) || is(T == NotificationHandler) || is(T == ResponseHandler))
124 {
125     static if (is(T == RequestHandler))
126     {
127         alias handlers = requestHandlers;
128     }
129     else static if (is(T == NotificationHandler))
130     {
131         alias handlers = notificationHandlers;
132     }
133     else
134     {
135         alias handlers = runtimeResponseHandlers;
136     }
137 
138     if (methodOrId in handlers)
139     {
140         auto h = handlers[methodOrId];
141 
142         static if (is(T == ResponseHandler))
143         {
144             runtimeResponseHandlers.remove(methodOrId);
145         }
146 
147         return h;
148     }
149 
150     throw new HandlerNotFoundException(methodOrId);
151 }