Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/juliaci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,21 @@ jobs:
permissions: write-all
secrets:
codecov_token: ${{ secrets.CODECOV_TOKEN }}

json-compat:
name: JSON ${{ matrix.json-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
json-version: ["0.21", "1.7"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't strictly need to run on JSON 1.x here since that gets picked up by the normal tests, but can't hurt I guess.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't put extra jobs into that workflow, that is really a template for the underlying shared workflow. We could use a second workflow file for this, or I'm also still thinking whether there is a good way to handle this natively in the testitem-workflow story.

I'll open a new PR doing whatever I come up with :)

steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: "1"
- uses: julia-actions/cache@v2
- name: Install and verify JSON version
run: |
julia --project=. -e 'using Pkg; Pkg.add(PackageSpec(name="JSON", version="${{ matrix.json-version }}")); using JSON; @assert startswith(string(pkgversion(JSON)), "${{ matrix.json-version }}."); Pkg.status()'
- uses: julia-actions/julia-runtest@v1
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
JSON = "0.20, 0.21"
JSON = "0.20, 0.21, 1"
julia = "1"
CancellationTokens = "2.0.2"

Expand Down
14 changes: 7 additions & 7 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
"""
struct NewlineDelimitedFraming <: FramingMode end

mutable struct JSONRPCEndpoint{IOIn<:IO,IOOut<:IO,S<:JSON.Serialization,F<:FramingMode}
mutable struct JSONRPCEndpoint{IOIn<:IO,IOOut<:IO,S<:JSONSerialization,F<:FramingMode}
pipe_in::IOIn
pipe_out::IOOut

Expand Down Expand Up @@ -225,7 +225,7 @@
close_started::Bool
end

JSONRPCEndpoint(pipe_in, pipe_out, serialization::JSON.Serialization=JSON.StandardSerialization(); framing::FramingMode=ContentLengthFraming()) =
JSONRPCEndpoint(pipe_in, pipe_out, serialization::JSONSerialization=DefaultJSONSerialization(); framing::FramingMode=ContentLengthFraming()) =
JSONRPCEndpoint(
pipe_in,
pipe_out,
Expand Down Expand Up @@ -265,7 +265,7 @@

read_transport_layer(stream, token::CancellationTokens.CancellationToken, ::ContentLengthFraming) = read_transport_layer(stream, token)

function read_transport_layer(stream, token::CancellationTokens.CancellationToken)

Check notice on line 268 in src/core.jl

View workflow job for this annotation

GitHub Actions / julia-ci / lint

unused_function_argument

An argument is included in a function signature but not used within its body.
try
header_dict = Dict{String,String}()
line = chomp(readline(stream))
Expand Down Expand Up @@ -335,7 +335,7 @@
end
end

function read_transport_layer(stream, token::CancellationTokens.CancellationToken, ::NewlineDelimitedFraming)

Check notice on line 338 in src/core.jl

View workflow job for this annotation

GitHub Actions / julia-ci / lint

unused_function_argument

An argument is included in a function signature but not used within its body.
try
line = readline(stream)
if isempty(line)
Expand Down Expand Up @@ -403,7 +403,7 @@
# A blocked write task cannot report itself — it is inside the write. Hence a timer, which
# only ever warns once per stall and resets when writes start completing again.
function _start_write_monitor(x::JSONRPCEndpoint)
return Timer(WRITE_STALL_CHECK_SECONDS; interval=WRITE_STALL_CHECK_SECONDS) do t

Check notice on line 406 in src/core.jl

View workflow job for this annotation

GitHub Actions / julia-ci / lint

unused_binding

Variable has been assigned but not used.
try
started = x.write_started_at
if started === nothing
Expand Down Expand Up @@ -496,7 +496,7 @@
end

message_dict = try
JSON.parse(message)
_parse_json(message)
catch parse_err
# Corrupted/truncated message (e.g. remote process crashed mid-write).
# Treat as broken pipe and exit the read loop.
Expand Down Expand Up @@ -616,7 +616,7 @@

message = Dict("jsonrpc" => "2.0", "method" => method, "params" => params)

message_json = sprint(JSON.show_json, x.serialization, message)
message_json = _serialize_json(x.serialization, message)

put!(x.out_msg_queue, message_json)

Expand All @@ -632,7 +632,7 @@
response_channel = Channel{Any}(1)
x.outstanding_requests[id] = response_channel

message_json = sprint(JSON.show_json, x.serialization, message)
message_json = _serialize_json(x.serialization, message)

put!(x.out_msg_queue, message_json)

Expand Down Expand Up @@ -800,7 +800,7 @@
end
end

function Base.iterate(endpoint::JSONRPCEndpoint, state = nothing)

Check notice on line 803 in src/core.jl

View workflow job for this annotation

GitHub Actions / julia-ci / lint

unused_function_argument

An argument is included in a function signature but not used within its body.
if endpoint.status !== status_running
buffered = _take_buffered_message(endpoint)
buffered === nothing || return buffered, nothing
Expand Down Expand Up @@ -834,7 +834,7 @@

response = Dict("jsonrpc" => "2.0", "id" => original_request.id, "result" => result)

response_json = sprint(JSON.show_json, endpoint.serialization, response)
response_json = _serialize_json(endpoint.serialization, response)

put!(endpoint.out_msg_queue, response_json)
end
Expand All @@ -848,7 +848,7 @@

response = Dict("jsonrpc" => "2.0", "id" => original_request.id, "error" => Dict("code" => code, "message" => message, "data" => data))

response_json = sprint(JSON.show_json, endpoint.serialization, response)
response_json = _serialize_json(endpoint.serialization, response)

put!(endpoint.out_msg_queue, response_json)
end
Expand Down
26 changes: 8 additions & 18 deletions src/interface_def.jl
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
abstract type Outbound end

function JSON.Writer.CompositeTypeWrapper(t::Outbound)
fns = collect(fieldnames(typeof(t)))
dels = Int[]
for i = 1:length(fns)
f = fns[i]
if getfield(t, f) isa Missing
push!(dels, i)
end
end
deleteat!(fns, dels)
JSON.Writer.CompositeTypeWrapper(t, Tuple(fns))
end

function JSON.lower(a::Outbound)
if nfields(a) > 0
JSON.Writer.CompositeTypeWrapper(a)
else
nothing
nfields(a) == 0 && return nothing

fields = Dict{String,Any}()
for field in fieldnames(typeof(a))
value = getfield(a, field)
ismissing(value) || (fields[string(field)] = value)
end
return fields
end

function field_allows_missing(field::Expr)
Expand All @@ -27,7 +17,7 @@
any(i -> i == :Missing, field.args[2].args)
end

function field_type(field::Expr, typename::String)

Check notice on line 20 in src/interface_def.jl

View workflow job for this annotation

GitHub Actions / julia-ci / lint

unused_function_argument

An argument is included in a function signature but not used within its body.
if field.args[2] isa Expr && field.args[2].head == :curly && field.args[2].args[1] == :Union
if length(field.args[2].args) == 3 && (field.args[2].args[2] == :Missing || field.args[2].args[3] == :Missing)
return field.args[2].args[2] == :Missing ? field.args[2].args[3] : field.args[2].args[2]
Expand Down Expand Up @@ -60,7 +50,7 @@
end
) : nothing)

function $tname(dict::Dict)
function $tname(dict::AbstractDict)
end
end

Expand Down
17 changes: 17 additions & 0 deletions src/jsoncompat.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@static if isdefined(JSON, :JSONStyle)
const JSONSerialization = JSON.JSONStyle
const DefaultJSONSerialization = JSON.JSONWriteStyle
else
const JSONSerialization = JSON.Serialization
const DefaultJSONSerialization = JSON.StandardSerialization
end

function _serialize_json(serialization::JSONSerialization, value)
@static if isdefined(JSON, :JSONStyle)
return JSON.json(value; style=serialization)
else
return sprint(JSON.show_json, serialization, value)
end
end

_parse_json(value) = JSON.parse(value; dicttype=Dict{String,Any})
3 changes: 2 additions & 1 deletion src/packagedef.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ export JSONRPCEndpoint, TransportError, EndpointStatus, start, send_notification
export FramingMode, ContentLengthFraming, NewlineDelimitedFraming

include("pipenames.jl")
include("jsoncompat.jl")
include("core.jl")
include("typed.jl")
include("interface_def.jl")

function _precompile_()
ccall(:jl_generating_output, Cint, ()) == 1 || return nothing

E = JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint, JSON.Serializations.StandardSerialization, ContentLengthFraming}
E = JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint, DefaultJSONSerialization, ContentLengthFraming}
precompile(start, (E,))
precompile(send_notification, (E, String, Any))
precompile(send_request, (E, String, Any))
Expand Down
13 changes: 8 additions & 5 deletions test/test_json_serialization.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
@testitem "Custom JSON serialization" setup=[NamedPipes] begin
using JSON
using JSON: StructuralContext, begin_object, show_pair, end_object, show_json, Serializations.StandardSerialization

struct OurSerialization <: JSON.Serializations.CommonSerialization end

struct OurStruct
a::String
b::String
end

function JSON.show_json(io::StructuralContext, s::OurSerialization, f::OurStruct)
show_json(io, StandardSerialization(), "$(f.a):$(f.b)")
@static if isdefined(JSON, :JSONStyle)
struct OurSerialization <: JSON.JSONStyle end
JSON.StructUtils.lower(::OurSerialization, f::OurStruct) = "$(f.a):$(f.b)"
else
struct OurSerialization <: JSON.Serializations.CommonSerialization end
function JSON.show_json(io::JSON.StructuralContext, ::OurSerialization, f::OurStruct)
JSON.show_json(io, JSON.StandardSerialization(), "$(f.a):$(f.b)")
end
end

x = OurStruct("Hello", "World")
Expand Down
6 changes: 5 additions & 1 deletion test/test_misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ end
using JSON
buf = IOBuffer()
ep = JSONRPC.JSONRPCEndpoint(buf, buf)
@test ep.serialization isa JSON.Serializations.StandardSerialization
@static if isdefined(JSON, :JSONStyle)
@test ep.serialization isa JSON.JSONWriteStyle
else
@test ep.serialization isa JSON.Serializations.StandardSerialization
end
@test ep.status == JSONRPC.status_idle
@test ep.err === nothing
@test ep.read_task === nothing
Expand Down
Loading