286 lines
8.6 KiB
V
286 lines
8.6 KiB
V
module jsonrpc
|
|
|
|
import x.json2
|
|
import json
|
|
|
|
// The JSON-RPC version used for all requests and responses according to the specification.
|
|
const jsonrpc_version = '2.0'
|
|
|
|
// Response represents a JSON-RPC 2.0 response object.
|
|
// According to the specification, a response must contain either a result or an error, but not both.
|
|
// See: https://www.jsonrpc.org/specification#response_object
|
|
pub struct Response {
|
|
pub:
|
|
// The JSON-RPC protocol version, must be exactly "2.0"
|
|
jsonrpc string @[required]
|
|
|
|
// The result of the method invocation (only present if the call was successful)
|
|
result ?string
|
|
|
|
// Error object if the request failed (only present if the call failed)
|
|
error_ ?RPCError @[json: 'error']
|
|
|
|
// Must match the id of the request that generated this response
|
|
id int @[required]
|
|
}
|
|
|
|
// new_response creates a successful JSON-RPC response with the given result.
|
|
//
|
|
// Parameters:
|
|
// - id: The ID from the request that this response is answering
|
|
// - result: The result of the method call, encoded as a JSON string
|
|
//
|
|
// Returns:
|
|
// - A Response object containing the result
|
|
pub fn new_response(id int, result string) Response {
|
|
return Response{
|
|
jsonrpc: jsonrpc_version
|
|
result: result
|
|
id: id
|
|
}
|
|
}
|
|
|
|
// new_error_response creates an error JSON-RPC response with the given error object.
|
|
//
|
|
// Parameters:
|
|
// - id: The ID from the request that this response is answering
|
|
// - error: The error that occurred during the method call
|
|
//
|
|
// Returns:
|
|
// - A Response object containing the error
|
|
pub fn new_error_response(id int, error RPCError) Response {
|
|
return Response{
|
|
jsonrpc: jsonrpc_version
|
|
error_: error
|
|
id: id
|
|
}
|
|
}
|
|
|
|
// decode_response parses a JSON string into a Response object.
|
|
// This function handles the complex validation rules for JSON-RPC responses.
|
|
//
|
|
// Parameters:
|
|
// - data: A JSON string representing a JSON-RPC response
|
|
//
|
|
// Returns:
|
|
// - A Response object or an error if parsing fails or the response is invalid
|
|
pub fn decode_response(data string) !Response {
|
|
raw := json2.raw_decode(data)!
|
|
raw_map := raw.as_map()
|
|
|
|
// Validate that the response contains either result or error, but not both or neither
|
|
if 'error' !in raw_map.keys() && 'result' !in raw_map.keys() {
|
|
return error('Invalid JSONRPC response, no error and result found.')
|
|
} else if 'error' in raw_map.keys() && 'result' in raw_map.keys() {
|
|
return error('Invalid JSONRPC response, both error and result found.')
|
|
}
|
|
|
|
// Handle error responses
|
|
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.int()
|
|
jsonrpc: jsonrpc_version
|
|
error_: json2.decode[RPCError](err.str())!
|
|
}
|
|
}
|
|
|
|
// Handle successful responses
|
|
return Response{
|
|
id: raw_map['id'] or { return error('Invalid JSONRPC response, no ID Field found') }.int()
|
|
jsonrpc: jsonrpc_version
|
|
result: raw_map['result']!.str()
|
|
}
|
|
}
|
|
|
|
// encode serializes the Response object into a JSON string.
|
|
//
|
|
// Returns:
|
|
// - A JSON string representation of the Response
|
|
pub fn (resp Response) encode() string {
|
|
return json2.encode(resp)
|
|
}
|
|
|
|
// validate checks that the Response object follows the JSON-RPC 2.0 specification.
|
|
// A valid response must not contain both result and error.
|
|
//
|
|
// Returns:
|
|
// - An error if validation fails, otherwise nothing
|
|
pub fn (resp Response) validate() ! {
|
|
// Note: This validation is currently commented out but should be implemented
|
|
// if err := resp.error_ && resp.result != '' {
|
|
// return error('Response contains both error and result.\n- Error: ${resp.error_.str()}\n- Result: ${resp.result}')
|
|
// }
|
|
}
|
|
|
|
// is_error checks if the response contains an error.
|
|
//
|
|
// Returns:
|
|
// - true if the response contains an error, false otherwise
|
|
pub fn (resp Response) is_error() bool {
|
|
return resp.error_ != none
|
|
}
|
|
|
|
// is_result checks if the response contains a result.
|
|
//
|
|
// Returns:
|
|
// - true if the response contains a result, false otherwise
|
|
pub fn (resp Response) is_result() bool {
|
|
return resp.result != none
|
|
}
|
|
|
|
// error returns the error object if present in the response.
|
|
//
|
|
// Returns:
|
|
// - The error object if present, or none if no error is present
|
|
pub fn (resp Response) error() ?RPCError {
|
|
if err := resp.error_ {
|
|
return err
|
|
}
|
|
return none
|
|
}
|
|
|
|
// result returns the result string if no error is present.
|
|
// If an error is present, it returns the error instead.
|
|
//
|
|
// Returns:
|
|
// - The result string or an error if the response contains an error
|
|
pub fn (resp Response) result() !string {
|
|
if err := resp.error() {
|
|
return err
|
|
}
|
|
// Ensure no error is present
|
|
return resp.result or { '' }
|
|
}
|
|
|
|
// ResponseGeneric is a type-safe version of the Response struct that allows
|
|
// for strongly-typed results using generics.
|
|
// This provides compile-time type safety for response results.
|
|
pub struct ResponseGeneric[D] {
|
|
pub mut:
|
|
// The JSON-RPC protocol version, must be exactly "2.0"
|
|
jsonrpc string @[required]
|
|
|
|
// The result of the method invocation with a specific type D
|
|
result ?D
|
|
|
|
// Error object if the request failed
|
|
error_ ?RPCError @[json: 'error']
|
|
|
|
// Must match the id of the request that generated this response
|
|
id int @[required]
|
|
}
|
|
|
|
// new_response_generic creates a successful generic JSON-RPC response with a strongly-typed result.
|
|
//
|
|
// Parameters:
|
|
// - id: The ID from the request that this response is answering
|
|
// - result: The result of the method call, of type D
|
|
//
|
|
// Returns:
|
|
// - A ResponseGeneric object with result of type D
|
|
pub fn new_response_generic[D](id int, result D) ResponseGeneric[D] {
|
|
return ResponseGeneric[D]{
|
|
jsonrpc: jsonrpc_version
|
|
result: result
|
|
id: id
|
|
}
|
|
}
|
|
|
|
// decode_response_generic parses a JSON string into a ResponseGeneric object with result of type D.
|
|
// This function handles the complex validation rules for JSON-RPC responses.
|
|
//
|
|
// Parameters:
|
|
// - data: A JSON string representing a JSON-RPC response
|
|
//
|
|
// Returns:
|
|
// - 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
|
|
|
|
raw := json2.raw_decode(data)!
|
|
raw_map := raw.as_map()
|
|
|
|
// Validate that the response contains either result or error, but not both or neither
|
|
if 'error' !in raw_map.keys() && 'result' !in raw_map.keys() {
|
|
return error('Invalid JSONRPC response, no error and result found.')
|
|
} else if 'error' in raw_map.keys() && 'result' in raw_map.keys() {
|
|
return error('Invalid JSONRPC response, both error and result found.')
|
|
}
|
|
|
|
// 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')
|
|
}.int()
|
|
jsonrpc: jsonrpc_version
|
|
error_: json2.decode[RPCError](err.str())!
|
|
}
|
|
}
|
|
|
|
// 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') }.int()
|
|
jsonrpc: jsonrpc_version
|
|
result: resp.result
|
|
}
|
|
}
|
|
|
|
// encode serializes the ResponseGeneric object into a JSON string.
|
|
//
|
|
// Returns:
|
|
// - A JSON string representation of the ResponseGeneric object
|
|
pub fn (resp ResponseGeneric[D]) encode() string {
|
|
return json2.encode(resp)
|
|
}
|
|
|
|
// validate checks that the ResponseGeneric object follows the JSON-RPC 2.0 specification.
|
|
// A valid response must not contain both result and error.
|
|
//
|
|
// Returns:
|
|
// - An error if validation fails, otherwise nothing
|
|
pub fn (resp ResponseGeneric[D]) validate() ! {
|
|
if resp.is_error() && resp.is_result() {
|
|
return error('Response contains both error and result.\n- Error: ${resp.error.str()}\n- Result: ${resp.result}')
|
|
}
|
|
}
|
|
|
|
// is_error checks if the response contains an error.
|
|
//
|
|
// Returns:
|
|
// - true if the response contains an error, false otherwise
|
|
pub fn (resp ResponseGeneric[D]) is_error() bool {
|
|
return resp.error_ != none
|
|
}
|
|
|
|
// is_result checks if the response contains a result.
|
|
//
|
|
// Returns:
|
|
// - true if the response contains a result, false otherwise
|
|
pub fn (resp ResponseGeneric[D]) is_result() bool {
|
|
return resp.result != none
|
|
}
|
|
|
|
// error returns the error object if present in the generic response.
|
|
//
|
|
// Returns:
|
|
// - The error object if present, or none if no error is present
|
|
pub fn (resp ResponseGeneric[D]) error() ?RPCError {
|
|
return resp.error_?
|
|
}
|
|
|
|
// result returns the result of type D if no error is present.
|
|
// If an error is present, it returns the error instead.
|
|
//
|
|
// Returns:
|
|
// - The result of type D or an error if the response contains an error
|
|
pub fn (resp ResponseGeneric[D]) result() !D {
|
|
if err := resp.error() {
|
|
return err
|
|
}
|
|
// Ensure no error is present
|
|
return resp.result or { D{} }
|
|
}
|