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
53 changes: 53 additions & 0 deletions net/jsonrpc/codec.d
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module ae.net.jsonrpc.codec;
import std.exception : assumeUnique;

import ae.net.asockets : IConnection, DisconnectType;
version(unittest) import ae.net.asockets : ConnectionState;
import ae.sys.data : Data;
import ae.utils.array : asBytes;
import ae.utils.serialization.json;
Expand Down Expand Up @@ -224,6 +225,58 @@ private:
}
}

debug(ae_unittest) unittest
{
import ae.utils.promise : resolve;

// A peer may add members to the request object as its protocol
// evolves. JSON-RPC 2.0 specifies the member set but does not oblige
// implementations to reject extra members, and a codec that fails on
// them cannot interoperate across peer versions.
static class MockConnection : IConnection
{
ReadDataHandler readHandler;
string disconnectReason;
bool disconnected;

@property ConnectionState state() { return ConnectionState.connected; }

void send(scope Data[] data, int priority = DEFAULT_PRIORITY) {}
alias send = IConnection.send;

void disconnect(string reason = defaultDisconnectReason, DisconnectType type = DisconnectType.requested)
{
disconnected = true;
disconnectReason = reason;
}
@property void handleConnect(ConnectHandler value) {}
@property void handleReadData(ReadDataHandler value) { readHandler = value; }
@property void handleDisconnect(DisconnectHandler value) {}
@property void handleBufferFlushed(BufferFlushedHandler value) {}
}

string[] dispatched;
auto conn = new MockConnection;
auto codec = new JsonRpcCodec(conn);
codec.handleRequest = (JsonRpcRequest request) {
dispatched ~= request.method;
return resolve(JsonRpcResponse.success(request.id, 0));
};

// Unknown top-level member, and no "jsonrpc" member at all.
conn.readHandler(Data(
`{"method":"peer/notify","params":{},"emittedAtMs":1}`.asBytes));
assert(dispatched == ["peer/notify"], dispatched.length ? dispatched[0] : "(none)");
assert(!conn.disconnected, conn.disconnectReason);

// Same, in a batch.
dispatched = null;
conn.readHandler(Data(
`[{"method":"a","extra":1},{"method":"b","extra":2}]`.asBytes));
assert(dispatched == ["a", "b"]);
assert(!conn.disconnected, conn.disconnectReason);
}

/// Convenience alias for server-only usage.
alias JsonRpcServerCodec = JsonRpcCodec;

Expand Down
3 changes: 3 additions & 0 deletions utils/jsonrpc.d
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ string getDefaultErrorMessage(int code)
// ************************************************************************

/// JSON-RPC 2.0 Error object
@JSONPartial
struct JsonRpcError
{
/// Error code
Expand Down Expand Up @@ -127,6 +128,7 @@ class JsonRpcException : Exception
// ************************************************************************

/// JSON-RPC 2.0 Request object
@JSONPartial
struct JsonRpcRequest
{
/// Protocol version (always "2.0")
Expand Down Expand Up @@ -172,6 +174,7 @@ struct JsonRpcRequest
}

/// JSON-RPC 2.0 Response object
@JSONPartial
struct JsonRpcResponse
{
/// Protocol version (always "2.0")
Expand Down
Loading