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