Skip to content

Commit 2899b1f

Browse files
authored
Merge pull request #79 from julia-vscode/static-dispatch
Add static message dispatch option
2 parents ddf995e + 9399853 commit 2899b1f

File tree

6 files changed

+171
-31
lines changed

6 files changed

+171
-31
lines changed

src/packagedef.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export JSONRPCEndpoint, send_notification, send_request, send_success_response, send_error_response
22

3+
include("pipenames.jl")
34
include("core.jl")
45
include("typed.jl")
56
include("interface_def.jl")

src/pipenames.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function generate_pipe_name()
2+
if Sys.iswindows()
3+
return "\\\\.\\pipe\\jl-$(UUIDs.uuid4())"
4+
else
5+
return tempname()
6+
end
7+
end

src/typed.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,40 @@ function dispatch_msg(x::JSONRPCEndpoint, dispatcher::MsgDispatcher, msg)
8686
end
8787

8888
is_currently_handling_msg(d::MsgDispatcher) = d._currentlyHandlingMsg
89+
90+
macro message_dispatcher(name, body)
91+
quote
92+
function $(esc(name))(x, msg::Dict{String,Any}, context=nothing)
93+
method_name = msg["method"]::String
94+
95+
$(
96+
(
97+
:(
98+
if method_name == $(esc(i.args[2])).method
99+
param_type = get_param_type($(esc(i.args[2])))
100+
params = param_type === Nothing ? nothing : param_type <: NamedTuple ? convert(param_type,(;(Symbol(i[1])=>i[2] for i in msg["params"])...)) : param_type(msg["params"])
101+
102+
res = $(esc(i.args[3]))(x, params)
103+
104+
if $(esc(i.args[2])) isa RequestType
105+
if res isa JSONRPCError
106+
send_error_response(x, msg, res.code, res.msg, res.data)
107+
elseif res isa get_return_type($(esc(i.args[2])))
108+
send_success_response(x, msg, res)
109+
else
110+
error_msg = "The handler for the '$method_name' request returned a value of type $(typeof(res)), which is not a valid return type according to the request definition."
111+
send_error_response(x, msg, -32603, error_msg, nothing)
112+
error(error_msg)
113+
end
114+
end
115+
116+
return
117+
end
118+
) for i in filter(i->i isa Expr, body.args)
119+
)...
120+
)
121+
122+
error("Unknown method $method_name.")
123+
end
124+
end
125+
end

test/shared_test_code.jl

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
using JSONRPC: @dict_readable, Outbound
1+
@testmodule TestStructs begin
2+
using JSONRPC: @dict_readable, Outbound
23

3-
@dict_readable struct Foo <: Outbound
4-
fieldA::Int
5-
fieldB::String
6-
fieldC::Union{Missing,String}
7-
fieldD::Union{String,Missing}
8-
end
4+
export Foo, Foo2
95

10-
@dict_readable struct Foo2 <: Outbound
11-
fieldA::Union{Nothing,Int}
12-
fieldB::Vector{Int}
13-
end
6+
@dict_readable struct Foo <: Outbound
7+
fieldA::Int
8+
fieldB::String
9+
fieldC::Union{Missing,String}
10+
fieldD::Union{String,Missing}
11+
end
12+
13+
@dict_readable struct Foo2 <: Outbound
14+
fieldA::Union{Nothing,Int}
15+
fieldB::Vector{Int}
16+
end
1417

15-
Base.:(==)(a::Foo2,b::Foo2) = a.fieldA == b.fieldA && a.fieldB == b.fieldB
18+
Base.:(==)(a::Foo2,b::Foo2) = a.fieldA == b.fieldA && a.fieldB == b.fieldB
19+
20+
end

test/test_interface_def.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
@testitem "Interface Definition" begin
1+
@testitem "Interface Definition" setup=[TestStructs] begin
22
using JSON
3-
include("shared_test_code.jl")
3+
using .TestStructs: Foo, Foo2
44

55
@test_throws ErrorException Foo()
66

test/test_typed.jl

Lines changed: 107 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
1-
@testitem "Message dispatcher" begin
1+
@testitem "Dynamic message dispatcher" setup=[TestStructs] begin
22
using Sockets
3+
using .TestStructs: Foo, Foo2
34

4-
include("shared_test_code.jl")
5-
6-
if Sys.iswindows()
7-
global_socket_name1 = "\\\\.\\pipe\\jsonrpc-testrun1"
8-
elseif Sys.isunix()
9-
global_socket_name1 = joinpath(tempdir(), "jsonrpc-testrun1")
10-
else
11-
error("Unknown operating system.")
12-
end
5+
global_socket_name1 = JSONRPC.generate_pipe_name()
136

147
request1_type = JSONRPC.RequestType("request1", Foo, String)
158
request2_type = JSONRPC.RequestType("request2", Nothing, String)
@@ -66,13 +59,7 @@
6659

6760
# Now we test a faulty server
6861

69-
if Sys.iswindows()
70-
global_socket_name2 = "\\\\.\\pipe\\jsonrpc-testrun2"
71-
elseif Sys.isunix()
72-
global_socket_name2 = joinpath(tempdir(), "jsonrpc-testrun2")
73-
else
74-
error("Unknown operating system.")
75-
end
62+
global_socket_name2 = JSONRPC.generate_pipe_name()
7663

7764
server_is_up = Base.Condition()
7865

@@ -121,3 +108,106 @@ end
121108
@test typed_res(['f','o','o'], String) isa String
122109
@test typed_res("foo", String) isa String
123110
end
111+
112+
@testitem "Static message dispatcher" setup=[TestStructs] begin
113+
using Sockets
114+
using .TestStructs: Foo, Foo2
115+
116+
global_socket_name1 = JSONRPC.generate_pipe_name()
117+
118+
request1_type = JSONRPC.RequestType("request1", Foo, String)
119+
request2_type = JSONRPC.RequestType("request2", Nothing, String)
120+
notify1_type = JSONRPC.NotificationType("notify1", String)
121+
122+
global g_var = ""
123+
124+
server_is_up = Base.Condition()
125+
126+
JSONRPC.@message_dispatcher my_dispatcher begin
127+
request1_type => (conn, params) -> begin
128+
params.fieldA == 1 ? "YES" : "NO"
129+
end
130+
request2_type => (conn, params) -> JSONRPC.JSONRPCError(-32600, "Our message", nothing)
131+
notify1_type => (conn, params) -> global g_var = params
132+
end
133+
134+
server_task = @async try
135+
server = listen(global_socket_name1)
136+
notify(server_is_up)
137+
sock = accept(server)
138+
global conn = JSONRPC.JSONRPCEndpoint(sock, sock)
139+
global msg_dispatcher = JSONRPC.MsgDispatcher()
140+
141+
run(conn)
142+
143+
for msg in conn
144+
my_dispatcher(conn, msg)
145+
end
146+
catch err
147+
Base.display_error(stderr, err, catch_backtrace())
148+
end
149+
150+
wait(server_is_up)
151+
152+
sock2 = connect(global_socket_name1)
153+
conn2 = JSONRPCEndpoint(sock2, sock2)
154+
155+
run(conn2)
156+
157+
JSONRPC.send(conn2, notify1_type, "TEST")
158+
159+
res = JSONRPC.send(conn2, request1_type, Foo(fieldA=1, fieldB="FOO"))
160+
161+
@test res == "YES"
162+
@test g_var == "TEST"
163+
164+
@test_throws JSONRPC.JSONRPCError(-32600, "Our message", nothing) JSONRPC.send(conn2, request2_type, nothing)
165+
166+
close(conn2)
167+
close(sock2)
168+
close(conn)
169+
170+
fetch(server_task)
171+
172+
# Now we test a faulty server
173+
174+
global_socket_name2 = JSONRPC.generate_pipe_name()
175+
176+
server_is_up = Base.Condition()
177+
178+
JSONRPC.@message_dispatcher my_dispatcher2 begin
179+
request2_type => (conn, params) -> 34 # The request type requires a `String` return, so this tests whether we get an error.
180+
end
181+
182+
server_task2 = @async try
183+
server = listen(global_socket_name2)
184+
notify(server_is_up)
185+
sock = accept(server)
186+
global conn = JSONRPC.JSONRPCEndpoint(sock, sock)
187+
global msg_dispatcher = JSONRPC.MsgDispatcher()
188+
189+
run(conn)
190+
191+
for msg in conn
192+
@test_throws ErrorException("The handler for the 'request2' request returned a value of type $Int, which is not a valid return type according to the request definition.") my_dispatcher2(conn, msg)
193+
end
194+
catch err
195+
Base.display_error(stderr, err, catch_backtrace())
196+
end
197+
198+
wait(server_is_up)
199+
200+
sock2 = connect(global_socket_name2)
201+
conn2 = JSONRPCEndpoint(sock2, sock2)
202+
203+
run(conn2)
204+
205+
@test_throws JSONRPC.JSONRPCError(-32603, "The handler for the 'request2' request returned a value of type $Int, which is not a valid return type according to the request definition.", nothing) JSONRPC.send(conn2, request2_type, nothing)
206+
207+
close(conn2)
208+
close(sock2)
209+
close(conn)
210+
211+
fetch(server_task)
212+
213+
end

0 commit comments

Comments
 (0)