Skip to content

Commit

Permalink
Merge pull request #79 from julia-vscode/static-dispatch
Browse files Browse the repository at this point in the history
Add static message dispatch option
  • Loading branch information
davidanthoff authored Aug 22, 2024
2 parents ddf995e + 9399853 commit 2899b1f
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 31 deletions.
1 change: 1 addition & 0 deletions src/packagedef.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export JSONRPCEndpoint, send_notification, send_request, send_success_response, send_error_response

include("pipenames.jl")
include("core.jl")
include("typed.jl")
include("interface_def.jl")
7 changes: 7 additions & 0 deletions src/pipenames.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function generate_pipe_name()
if Sys.iswindows()
return "\\\\.\\pipe\\jl-$(UUIDs.uuid4())"
else
return tempname()
end
end
37 changes: 37 additions & 0 deletions src/typed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,40 @@ function dispatch_msg(x::JSONRPCEndpoint, dispatcher::MsgDispatcher, msg)
end

is_currently_handling_msg(d::MsgDispatcher) = d._currentlyHandlingMsg

macro message_dispatcher(name, body)
quote
function $(esc(name))(x, msg::Dict{String,Any}, context=nothing)
method_name = msg["method"]::String

$(
(
:(
if method_name == $(esc(i.args[2])).method
param_type = get_param_type($(esc(i.args[2])))
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"])

res = $(esc(i.args[3]))(x, params)

if $(esc(i.args[2])) isa RequestType
if res isa JSONRPCError
send_error_response(x, msg, res.code, res.msg, res.data)
elseif res isa get_return_type($(esc(i.args[2])))
send_success_response(x, msg, res)
else
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."
send_error_response(x, msg, -32603, error_msg, nothing)
error(error_msg)
end
end

return
end
) for i in filter(i->i isa Expr, body.args)
)...
)

error("Unknown method $method_name.")
end
end
end
29 changes: 17 additions & 12 deletions test/shared_test_code.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
using JSONRPC: @dict_readable, Outbound
@testmodule TestStructs begin
using JSONRPC: @dict_readable, Outbound

@dict_readable struct Foo <: Outbound
fieldA::Int
fieldB::String
fieldC::Union{Missing,String}
fieldD::Union{String,Missing}
end
export Foo, Foo2

@dict_readable struct Foo2 <: Outbound
fieldA::Union{Nothing,Int}
fieldB::Vector{Int}
end
@dict_readable struct Foo <: Outbound
fieldA::Int
fieldB::String
fieldC::Union{Missing,String}
fieldD::Union{String,Missing}
end

@dict_readable struct Foo2 <: Outbound
fieldA::Union{Nothing,Int}
fieldB::Vector{Int}
end

Base.:(==)(a::Foo2,b::Foo2) = a.fieldA == b.fieldA && a.fieldB == b.fieldB
Base.:(==)(a::Foo2,b::Foo2) = a.fieldA == b.fieldA && a.fieldB == b.fieldB

end
4 changes: 2 additions & 2 deletions test/test_interface_def.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@testitem "Interface Definition" begin
@testitem "Interface Definition" setup=[TestStructs] begin
using JSON
include("shared_test_code.jl")
using .TestStructs: Foo, Foo2

@test_throws ErrorException Foo()

Expand Down
124 changes: 107 additions & 17 deletions test/test_typed.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
@testitem "Message dispatcher" begin
@testitem "Dynamic message dispatcher" setup=[TestStructs] begin
using Sockets
using .TestStructs: Foo, Foo2

include("shared_test_code.jl")

if Sys.iswindows()
global_socket_name1 = "\\\\.\\pipe\\jsonrpc-testrun1"
elseif Sys.isunix()
global_socket_name1 = joinpath(tempdir(), "jsonrpc-testrun1")
else
error("Unknown operating system.")
end
global_socket_name1 = JSONRPC.generate_pipe_name()

request1_type = JSONRPC.RequestType("request1", Foo, String)
request2_type = JSONRPC.RequestType("request2", Nothing, String)
Expand Down Expand Up @@ -66,13 +59,7 @@

# Now we test a faulty server

if Sys.iswindows()
global_socket_name2 = "\\\\.\\pipe\\jsonrpc-testrun2"
elseif Sys.isunix()
global_socket_name2 = joinpath(tempdir(), "jsonrpc-testrun2")
else
error("Unknown operating system.")
end
global_socket_name2 = JSONRPC.generate_pipe_name()

server_is_up = Base.Condition()

Expand Down Expand Up @@ -121,3 +108,106 @@ end
@test typed_res(['f','o','o'], String) isa String
@test typed_res("foo", String) isa String
end

@testitem "Static message dispatcher" setup=[TestStructs] begin
using Sockets
using .TestStructs: Foo, Foo2

global_socket_name1 = JSONRPC.generate_pipe_name()

request1_type = JSONRPC.RequestType("request1", Foo, String)
request2_type = JSONRPC.RequestType("request2", Nothing, String)
notify1_type = JSONRPC.NotificationType("notify1", String)

global g_var = ""

server_is_up = Base.Condition()

JSONRPC.@message_dispatcher my_dispatcher begin
request1_type => (conn, params) -> begin
params.fieldA == 1 ? "YES" : "NO"
end
request2_type => (conn, params) -> JSONRPC.JSONRPCError(-32600, "Our message", nothing)
notify1_type => (conn, params) -> global g_var = params
end

server_task = @async try
server = listen(global_socket_name1)
notify(server_is_up)
sock = accept(server)
global conn = JSONRPC.JSONRPCEndpoint(sock, sock)
global msg_dispatcher = JSONRPC.MsgDispatcher()

run(conn)

for msg in conn
my_dispatcher(conn, msg)
end
catch err
Base.display_error(stderr, err, catch_backtrace())
end

wait(server_is_up)

sock2 = connect(global_socket_name1)
conn2 = JSONRPCEndpoint(sock2, sock2)

run(conn2)

JSONRPC.send(conn2, notify1_type, "TEST")

res = JSONRPC.send(conn2, request1_type, Foo(fieldA=1, fieldB="FOO"))

@test res == "YES"
@test g_var == "TEST"

@test_throws JSONRPC.JSONRPCError(-32600, "Our message", nothing) JSONRPC.send(conn2, request2_type, nothing)

close(conn2)
close(sock2)
close(conn)

fetch(server_task)

# Now we test a faulty server

global_socket_name2 = JSONRPC.generate_pipe_name()

server_is_up = Base.Condition()

JSONRPC.@message_dispatcher my_dispatcher2 begin
request2_type => (conn, params) -> 34 # The request type requires a `String` return, so this tests whether we get an error.
end

server_task2 = @async try
server = listen(global_socket_name2)
notify(server_is_up)
sock = accept(server)
global conn = JSONRPC.JSONRPCEndpoint(sock, sock)
global msg_dispatcher = JSONRPC.MsgDispatcher()

run(conn)

for msg in conn
@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)
end
catch err
Base.display_error(stderr, err, catch_backtrace())
end

wait(server_is_up)

sock2 = connect(global_socket_name2)
conn2 = JSONRPCEndpoint(sock2, sock2)

run(conn2)

@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)

close(conn2)
close(sock2)
close(conn)

fetch(server_task)

end

0 comments on commit 2899b1f

Please sign in to comment.