fix jsonrpc serializations

This commit is contained in:
Timur Gordon
2025-03-14 02:36:40 +01:00
parent dd68bf950c
commit 1e26162e00
7 changed files with 58 additions and 48 deletions

View File

@@ -37,6 +37,7 @@ pub fn new_client(client Client) &Client {
// These parameters control timeout and retry behavior.
@[params]
pub struct SendParams {
pub:
// Maximum time in seconds to wait for a response (default: 60)
timeout int = 60

View File

@@ -9,7 +9,7 @@ import net.websocket
// Handler is a JSON-RPC request handler that maps method names to their corresponding procedure handlers.
// It can be used with a WebSocket server to handle incoming JSON-RPC requests.
pub struct Handler {
pub:
pub mut:
// A map where keys are method names and values are the corresponding procedure handler functions
procedures map[string]ProcedureHandler
}
@@ -20,7 +20,7 @@ pub:
// 2. Execute the procedure with the extracted parameters
// 3. Return the result as a JSON-encoded string
// If an error occurs during any of these steps, it should be returned.
type ProcedureHandler = fn (payload string) !string
pub type ProcedureHandler = fn (payload string) !string
// new_handler creates a new JSON-RPC handler with the specified procedure handlers.
//
@@ -33,6 +33,15 @@ pub fn new_handler(handler Handler) !&Handler {
return &Handler{...handler}
}
// register_procedure registers a new procedure handler for the specified method.
//
// Parameters:
// - method: The name of the method to register
// - procedure: The procedure handler function to register
pub fn (mut handler Handler) register_procedure(method string, procedure ProcedureHandler) {
handler.procedures[method] = procedure
}
// handler is a callback function compatible with the WebSocket server's message handler interface.
// It processes an incoming WebSocket message as a JSON-RPC request and returns the response.
//
@@ -58,14 +67,14 @@ pub fn (handler Handler) handler(client &websocket.Client, message string) strin
pub fn (handler Handler) handle(message string) !string {
// Extract the method name from the request
method := decode_request_method(message)!
log.info('Handling remote procedure call to method: ${method}')
// log.info('Handling remote procedure call to method: ${method}')
// Look up the procedure handler for the requested method
procedure_func := handler.procedures[method] or {
log.error('No procedure handler for method ${method} found')
// log.error('No procedure handler for method ${method} found')
return method_not_found
}
// Execute the procedure handler with the request payload
response := procedure_func(message) or { panic(err) }
return response

View File

@@ -73,7 +73,7 @@ pub mut:
//
// Returns:
// - A Response object containing the error
pub fn new_error(id string, error RPCError) Response {
pub fn new_error(id int, error RPCError) Response {
return Response{
jsonrpc: jsonrpc_version
error_: error

View File

@@ -1,5 +1,6 @@
module jsonrpc
import json
import x.json2
import rand
@@ -20,7 +21,7 @@ pub mut:
// An identifier established by the client that must be included in the response
// This is used to correlate requests with their corresponding responses
id string @[required]
id int @[required]
}
// new_request creates a new JSON-RPC request with the specified method and parameters.
@@ -37,7 +38,7 @@ pub fn new_request(method string, params string) Request {
jsonrpc: jsonrpc.jsonrpc_version
method: method
params: params
id: rand.uuid_v4() // Automatically generate a unique ID using UUID v4
id: rand.i32() // Automatically generate a unique ID using UUID v4
}
}
@@ -68,7 +69,7 @@ pub fn (req Request) encode() string {
pub fn (req Request) validate() ! {
if req.jsonrpc == '' {
return error('request jsonrpc version not specified')
} else if req.id == '' {
} else if req.id == -1 {
return error('request id is empty')
} else if req.method == '' {
return error('request method is empty')
@@ -90,7 +91,7 @@ pub mut:
params T
// An identifier established by the client
id string @[required]
id int @[required]
}
// new_request_generic creates a new generic JSON-RPC request with strongly-typed parameters.
@@ -107,7 +108,7 @@ pub fn new_request_generic[T](method string, params T) RequestGeneric[T] {
jsonrpc: jsonrpc.jsonrpc_version
method: method
params: params
id: rand.uuid_v4()
id: rand.i32()
}
}
@@ -119,11 +120,11 @@ pub fn new_request_generic[T](method string, params T) RequestGeneric[T] {
//
// Returns:
// - The ID as a string, or an error if the ID field is missing
pub fn decode_request_id(data string) !string {
pub fn decode_request_id(data string) !int {
data_any := json2.raw_decode(data)!
data_map := data_any.as_map()
id_any := data_map['id'] or { return error('ID field not found') }
return id_any.str()
return id_any.int()
}
// decode_request_method extracts just the method field from a JSON-RPC request string.
@@ -149,7 +150,7 @@ pub fn decode_request_method(data string) !string {
// Returns:
// - A RequestGeneric object with parameters of type T, or an error if parsing fails
pub fn decode_request_generic[T](data string) !RequestGeneric[T] {
return json2.decode[RequestGeneric[T]](data)!
return json.decode(RequestGeneric[T], data)!
}
// encode serializes the RequestGeneric object into a JSON string.

View File

@@ -5,7 +5,7 @@ fn test_new_request() {
assert request.jsonrpc == jsonrpc.jsonrpc_version
assert request.method == 'test_method'
assert request.params == 'test_params'
assert request.id != '' // Ensure the ID is generated
assert request.id != -1 // Ensure the ID is generated
}
fn test_decode_request() {
@@ -17,7 +17,7 @@ fn test_decode_request() {
assert request.jsonrpc == '2.0'
assert request.method == 'test_method'
assert request.params == 'test_params'
assert request.id == '123'
assert request.id == 123
}
fn test_request_encode() {
@@ -32,7 +32,7 @@ fn test_new_request_generic() {
assert request.jsonrpc == jsonrpc.jsonrpc_version
assert request.method == 'test_method'
assert request.params == params
assert request.id != '' // Ensure the ID is generated
assert request.id != -1 // Ensure the ID is generated
}
fn test_decode_request_id() {
@@ -41,7 +41,7 @@ fn test_decode_request_id() {
assert false, 'Failed to decode request ID: $err'
return
}
assert id == '123'
assert id == 123
}
fn test_decode_request_method() {
@@ -54,7 +54,7 @@ fn test_decode_request_method() {
}
fn test_decode_request_generic() {
data := '{"jsonrpc":"2.0","method":"test_method","params":{"key":"value"},"id":"123"}'
data := '{"jsonrpc":"2.0","method":"test_method","params":{"key":"value"},"id":123}'
request := decode_request_generic[map[string]string](data) or {
assert false, 'Failed to decode generic request: $err'
return
@@ -62,7 +62,7 @@ fn test_decode_request_generic() {
assert request.jsonrpc == '2.0'
assert request.method == 'test_method'
assert request.params == {'key': 'value'}
assert request.id == '123'
assert request.id == 123
}
fn test_request_generic_encode() {

View File

@@ -21,7 +21,7 @@ pub:
error_ ?RPCError @[json: 'error']
// Must match the id of the request that generated this response
id string @[required]
id int @[required]
}
// new_response creates a successful JSON-RPC response with the given result.
@@ -32,7 +32,7 @@ pub:
//
// Returns:
// - A Response object containing the result
pub fn new_response(id string, result string) Response {
pub fn new_response(id int, result string) Response {
return Response{
jsonrpc: jsonrpc.jsonrpc_version
result: result
@@ -48,7 +48,7 @@ pub fn new_response(id string, result string) Response {
//
// Returns:
// - A Response object containing the error
pub fn new_error_response(id string, error RPCError) Response {
pub fn new_error_response(id int, error RPCError) Response {
return Response{
jsonrpc: jsonrpc.jsonrpc_version
error_: error
@@ -79,7 +79,7 @@ pub fn decode_response(data string) !Response {
if err := raw_map['error'] {
id_any := raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}
return Response {
id: id_any.str()
id: id_any.int()
jsonrpc: jsonrpc_version
error_: json2.decode[RPCError](err.str())!
}
@@ -87,7 +87,7 @@ pub fn decode_response(data string) !Response {
// Handle successful responses
return Response {
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.str()
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
jsonrpc: jsonrpc_version
result: raw_map['result']!.str()
}
@@ -167,7 +167,7 @@ pub mut:
error_ ?RPCError @[json: 'error']
// Must match the id of the request that generated this response
id string @[required]
id int @[required]
}
// new_response_generic creates a successful generic JSON-RPC response with a strongly-typed result.
@@ -178,7 +178,7 @@ pub mut:
//
// Returns:
// - A ResponseGeneric object with result of type D
pub fn new_response_generic[D](id string, result D) ResponseGeneric[D] {
pub fn new_response_generic[D](id int, result D) ResponseGeneric[D] {
return ResponseGeneric[D]{
jsonrpc: jsonrpc.jsonrpc_version
result: result
@@ -196,7 +196,6 @@ pub fn new_response_generic[D](id string, result D) ResponseGeneric[D] {
// - A ResponseGeneric object with result of type D, or an error if parsing fails
pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
// Debug output - consider removing in production
println('respodata ${data}')
raw := json2.raw_decode(data)!
raw_map := raw.as_map()
@@ -211,7 +210,7 @@ pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
// Handle error responses
if err := raw_map['error'] {
return ResponseGeneric[D] {
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.str()
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
jsonrpc: jsonrpc_version
error_: json2.decode[RPCError](err.str())!
}
@@ -220,7 +219,7 @@ pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
// Handle successful responses
resp := json.decode(ResponseGeneric[D], data)!
return ResponseGeneric[D] {
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.str()
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
jsonrpc: jsonrpc_version
result: resp.result
}

View File

@@ -1,9 +1,9 @@
module jsonrpc
fn test_new_response() {
response := new_response('123', 'test_result')
response := new_response(123, 'test_result')
assert response.jsonrpc == jsonrpc.jsonrpc_version
assert response.id == '123'
assert response.id == 123
assert response.is_result()
assert !response.is_error() // Ensure no error is set
@@ -20,13 +20,13 @@ fn test_new_error_response() {
message: 'Test error'
data: 'Error details'
}
response := new_error_response('123', error)
response := new_error_response(123, error)
assert response.jsonrpc == jsonrpc.jsonrpc_version
response.validate()!
assert response.is_error()
assert !response.is_result() // Ensure no result is set
assert response.id == '123'
assert response.id == 123
response_error := response.error()?
assert response_error == error
@@ -39,7 +39,7 @@ fn test_decode_response() {
return
}
assert response.jsonrpc == '2.0'
assert response.id == '123'
assert response.id == 123
assert response.is_result()
assert !response.is_error() // Ensure no error is set
@@ -51,13 +51,13 @@ fn test_decode_response() {
}
fn test_response_encode() {
response := new_response('123', 'test_result')
response := new_response(123, 'test_result')
json := response.encode()
assert json.contains('"jsonrpc"') && json.contains('"result"') && json.contains('"id"')
}
fn test_response_validate() {
response := new_response('123', 'test_result')
response := new_response(123, 'test_result')
response.validate() or { assert false, 'Validation failed for valid response: $err' }
error := RPCError{
@@ -69,7 +69,7 @@ fn test_response_validate() {
jsonrpc: '2.0'
result: 'test_result'
error_: error
id: '123'
id: 123
}
invalid_response.validate() or {
assert err.msg().contains('Response contains both error and result.')
@@ -82,7 +82,7 @@ fn test_response_error() {
message: 'Test error'
data: 'Error details'
}
response := new_error_response('123', error)
response := new_error_response(123, error)
err := response.error() or {
assert false, 'Failed to get error: $err'
return
@@ -93,7 +93,7 @@ fn test_response_error() {
}
fn test_response_result() {
response := new_response('123', 'test_result')
response := new_response(123, 'test_result')
result := response.result() or {
assert false, 'Failed to get result: $err'
return
@@ -102,9 +102,9 @@ fn test_response_result() {
}
fn test_new_response_generic() {
response := new_response_generic('123', {'key': 'value'})
response := new_response_generic(123, {'key': 'value'})
assert response.jsonrpc == jsonrpc.jsonrpc_version
assert response.id == '123'
assert response.id == 123
assert response.is_result()
assert !response.is_error() // Ensure no error is set
@@ -122,7 +122,7 @@ fn test_decode_response_generic() {
return
}
assert response.jsonrpc == '2.0'
assert response.id == '123'
assert response.id == 123
assert response.is_result()
assert !response.is_error() // Ensure no error is set
@@ -134,13 +134,13 @@ fn test_decode_response_generic() {
}
fn test_response_generic_encode() {
response := new_response_generic('123', {'key': 'value'})
response := new_response_generic(123, {'key': 'value'})
json := response.encode()
assert json.contains('"jsonrpc"') && json.contains('"result"') && json.contains('"id"')
}
fn test_response_generic_validate() {
response := new_response_generic('123', {'key': 'value'})
response := new_response_generic(123, {'key': 'value'})
response.validate() or { assert false, 'Validation failed for valid response: $err' }
error := RPCError{
@@ -152,7 +152,7 @@ fn test_response_generic_validate() {
jsonrpc: '2.0'
result: {'key': 'value'}
error_: error
id: '123'
id: 123
}
invalid_response.validate() or {
assert err.msg().contains('Response contains both error and result.')
@@ -168,7 +168,7 @@ fn test_response_generic_error() {
response := ResponseGeneric[map[string]string]{
jsonrpc: '2.0'
error_: error
id: '123'
id: 123
}
err := response.error() or {
assert false, 'Failed to get error: $err'
@@ -180,7 +180,7 @@ fn test_response_generic_error() {
}
fn test_response_generic_result() {
response := new_response_generic('123', {'key': 'value'})
response := new_response_generic(123, {'key': 'value'})
result := response.result() or {
assert false, 'Failed to get result: $err'
return