schemas.jsonrpc #
JSON-RPC Module
This module provides a robust implementation of the JSON-RPC 2.0 protocol in VLang. It includes utilities for creating, sending, and handling JSON-RPC requests and responses, with support for custom transports, strong typing, and error management.
Features
Request and Response Handling:
Create and encode JSON-RPC requests (generic or non-generic).
Decode and validate JSON-RPC responses.
Manage custom parameters and IDs for requests.
Error Management:
Predefined JSON-RPC errors based on the official specification.
Support for custom error creation and validation.
Generic Support:
Strongly typed request and response handling using generics.
Customizable Transport:
Pluggable transport client interface for flexibility (e.g., WebSocket, HTTP).
Usage
1. Client Setup and Custom Transports
Create a new JSON-RPC client using a custom transport layer. The transport must implement the IRPCTransportClient
interface, which requires a send
method:
pub interface IRPCTransportClient {
mut:
send(request string, params SendParams) !string
}
Example: WebSocket Transport
import freeflowuniverse.herolib.schemas.jsonrpc
import net.websocket
// Implement the IRPCTransportClient interface for WebSocket
struct WebSocketTransport {
mut:
ws &websocket.Client
connected bool
}
// Create a new WebSocket transport
fn new_websocket_transport(url string) !&WebSocketTransport {
mut ws := websocket.new_client(url)!
ws.connect()!
return &WebSocketTransport{
ws: ws
connected: true
}
}
// Implement the send method required by IRPCTransportClient
fn (mut t WebSocketTransport) send(request string, params jsonrpc.SendParams) !string {
if !t.connected {
return error('WebSocket not connected')
}
// Send the request
t.ws.write_string(request)!
// Wait for and return the response
response := t.ws.read_string()!
return response
}
// Create a new JSON-RPC client with WebSocket transport
mut transport := new_websocket_transport('ws://localhost:8080')!
mut client := jsonrpc.new_client(jsonrpc.Client{
transport: transport
})
Example: Unix Domain Socket Transport
import freeflowuniverse.herolib.schemas.jsonrpc
import net.unix
import time
// Implement the IRPCTransportClient interface for Unix domain sockets
struct UnixSocketTransport {
mut:
socket_path string
}
// Create a new Unix socket transport
fn new_unix_socket_transport(socket_path string) &UnixSocketTransport {
return &UnixSocketTransport{
socket_path: socket_path
}
}
// Implement the send method required by IRPCTransportClient
fn (mut t UnixSocketTransport) send(request string, params jsonrpc.SendParams) !string {
// Create a Unix domain socket client
mut socket := unix.connect_stream(t.socket_path)!
defer { socket.close() }
// Set timeout if specified
if params.timeout > 0 {
socket.set_read_timeout(params.timeout * time.second)
socket.set_write_timeout(params.timeout * time.second)
}
// Send the request
socket.write_string(request)!
// Read the response
mut response := ''
mut buf := []u8{len: 4096}
for {
bytes_read := socket.read(mut buf)!
if bytes_read <= 0 {
break
}
response += buf[..bytes_read].bytestr()
// Check if we've received a complete JSON response
// This is a simple approach; a more robust implementation would parse the JSON
if response.ends_with('}') {
break
}
}
return response
}
// Create a new JSON-RPC client with Unix socket transport
mut transport := new_unix_socket_transport('/tmp/jsonrpc.sock')
mut client := jsonrpc.new_client(jsonrpc.Client{
transport: transport
})
2. Sending a Request
Send a strongly-typed JSON-RPC request and handle the response.
import freeflowuniverse.herolib.schemas.jsonrpc
// Define your parameter and result types
struct UserParams {
id int
include_details bool
}
struct UserResult {
name string
email string
role string
}
// Create a strongly-typed request with generic parameters
params := UserParams{
id: 123
include_details: true
}
request := jsonrpc.new_request_generic('getUser', params)
// Configure send parameters
send_params := jsonrpc.SendParams{
timeout: 30
retry: 3
}
// Send the request and receive a strongly-typed response
// The generic types [UserParams, UserResult] ensure type safety for both request and response
user := client.send[UserParams, UserResult](request, send_params) or {
eprintln('Error sending request: $err')
return
}
// Access the strongly-typed result fields directly
println('User name: ${user.name}, email: ${user.email}, role: ${user.role}')
3. Handling Errors
Use the predefined JSON-RPC errors or create custom ones.
import freeflowuniverse.herolib.schemas.jsonrpc
// Predefined error
err := jsonrpc.method_not_found
// Custom error
custom_err := jsonrpc.RPCError{
code: 12345
message: 'Custom error message'
data: 'Additional details'
}
// Attach the error to a response
response := jsonrpc.new_error('request_id', custom_err)
println(response)
4. Working with Generic Responses
The JSON-RPC module provides strong typing for responses using generics, allowing you to define the exact structure of your expected results.
import freeflowuniverse.herolib.schemas.jsonrpc
// Define your result type
struct ServerStats {
cpu_usage f64
memory_usage f64
uptime int
active_connections int
}
// Create a request (with or without parameters)
request := jsonrpc.new_request('getServerStats', '{}')
// Decode a response directly to your type
response_json := '{"jsonrpc":"2.0","result":{"cpu_usage":45.2,"memory_usage":62.7,"uptime":86400,"active_connections":128},"id":1}'
response := jsonrpc.decode_response_generic[ServerStats](response_json) or {
eprintln('Failed to decode response: $err')
return
}
// Access the strongly-typed result
if !response.is_error() {
stats := response.result() or {
eprintln('Error getting result: $err')
return
}
println('Server stats:')
println('- CPU: ${stats.cpu_usage}%')
println('- Memory: ${stats.memory_usage}%')
println('- Uptime: ${stats.uptime} seconds')
println('- Connections: ${stats.active_connections}')
}
5. Creating Generic Responses
When implementing a JSON-RPC server, you can create strongly-typed responses:
import freeflowuniverse.herolib.schemas.jsonrpc
// Define a result type
struct SearchResult {
total_count int
items []string
page int
page_size int
}
// Create a response with a strongly-typed result
result := SearchResult{
total_count: 157
items: ['item1', 'item2', 'item3']
page: 1
page_size: 3
}
// Create a generic response with the strongly-typed result
response := jsonrpc.new_response_generic(1, result)
// Encode the response to send it
json := response.encode()
println(json)
// Output: {"jsonrpc":"2.0","id":1,"result":{"total_count":157,"items":["item1","item2","item3"],"page":1,"page_size":3}}
Modules and Key Components
1. model_request.v
Handles JSON-RPC requests:- Structs: Request
, RequestGeneric[T]
- Methods:
new_request
,new_request_generic[T]
,decode_request
,decode_request_generic[T]
, etc.
2. model_response.v
Handles JSON-RPC responses:- Structs: Response
, ResponseGeneric[D]
- Methods:
new_response
,new_response_generic[D]
,decode_response
,decode_response_generic[D]
,validate
, etc.
3. model_error.v
Manages JSON-RPC errors:- Struct: RPCError
- Predefined errors:
parse_error
,invalid_request
, etc. - Methods:
msg
,is_empty
, etc.
4. client.v
Implements the JSON-RPC client:- Structs: Client
, SendParams
, ClientConfig
- Interface:
IRPCTransportClient
- Method:
send[T, D]
- Generic method for sending requests with parameters of type T and receiving responses with results of type D
JSON-RPC Specification Reference
This module adheres to the JSON-RPC 2.0 specification.
Constants #
const parse_error = RPCError{
code: -32700
message: 'Parse error'
data: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.'
}
parse_error indicates that the server received invalid JSON. This error is returned when the server is unable to parse the request. Error code: -32700
const invalid_request = RPCError{
code: -32600
message: 'Invalid Request'
data: 'The JSON sent is not a valid Request object.'
}
invalid_request indicates that the sent JSON is not a valid Request object. This error is returned when the request object doesn't conform to the JSON-RPC 2.0 specification. Error code: -32600
const method_not_found = RPCError{
code: -32601
message: 'Method not found'
data: 'The method does not exist / is not available.'
}
method_not_found indicates that the requested method doesn't exist or is not available. This error is returned when the method specified in the request is not supported. Error code: -32601
const invalid_params = RPCError{
code: -32602
message: 'Invalid params'
data: 'Invalid method parameter(s).'
}
invalid_params indicates that the method parameters are invalid. This error is returned when the parameters provided to the method are incorrect or incompatible. Error code: -32602
const internal_error = RPCError{
code: -32603
message: 'Internal Error'
data: 'Internal JSON-RPC error.'
}
internal_error indicates an internal JSON-RPC error. This is a generic server-side error when no more specific error is applicable. Error code: -32603
fn decode_request #
fn decode_request(data string) !Request
decode_request parses a JSON string into a Request object.
Parameters:- data: A JSON string representing a JSON-RPC request
Returns:- A Request object or an error if parsing fails
fn decode_request_generic #
fn decode_request_generic[T](data string) !RequestGeneric[T]
decode_request_generic parses a JSON string into a RequestGeneric object with parameters of type T.
Parameters:- data: A JSON string representing a JSON-RPC request
Returns:- A RequestGeneric object with parameters of type T, or an error if parsing fails
fn decode_request_id #
fn decode_request_id(data string) !int
decode_request_id extracts just the ID field from a JSON-RPC request string. This is useful when you only need the ID without parsing the entire request.
Parameters:- data: A JSON string representing a JSON-RPC request
Returns:- The ID as a string, or an error if the ID field is missing
fn decode_request_method #
fn decode_request_method(data string) !string
decode_request_method extracts just the method field from a JSON-RPC request string. This is useful when you need to determine the method without parsing the entire request.
Parameters:- data: A JSON string representing a JSON-RPC request
Returns:- The method name as a string, or an error if the method field is missing
fn decode_response #
fn decode_response(data string) !Response
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
fn decode_response_generic #
fn decode_response_generic[D](data string) !ResponseGeneric[D]
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
fn new_blank_notification #
fn new_blank_notification(method string) Notification
fn new_client #
fn new_client(transport IRPCTransportClient) &Client
new_client creates a new JSON-RPC client with the specified transport.
Parameters:- client: A Client struct with the transport field initialized
Returns:- A pointer to a new Client instance
fn new_error #
fn new_error(id int, error RPCError) Response
new_error creates a new error response for a given request ID. This is a convenience function to create a Response object with an error.
Parameters:- id: The request ID that this error is responding to
- error: The RPCError object to include in the response
Returns:- A Response object containing the error
fn new_error_response #
fn new_error_response(id int, error RPCError) Response
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
fn new_handler #
fn new_handler(handler Handler) !&Handler
new_handler creates a new JSON-RPC handler with the specified procedure handlers.
Parameters:- handler: A Handler struct with the procedures field initialized
Returns:- A pointer to a new Handler instance or an error if creation fails
fn new_notification #
fn new_notification[T](method string, params T) NotificationGeneric[T]
new_notification creates a new JSON-RPC notification with the specified method and parameters. It automatically sets the JSON-RPC version to the current version.
Parameters:- method: The name of the method to invoke on the server
- params: The parameters to the method, encoded as a JSON string
Returns:- A fully initialized Notification object
fn new_request #
fn new_request(method string, params string) Request
new_request creates a new JSON-RPC request with the specified method and parameters. It automatically sets the JSON-RPC version to the current version and generates a unique ID.
Parameters:- method: The name of the method to invoke on the server
- params: The parameters to the method, encoded as a JSON string
Returns:- A fully initialized Request object
fn new_request_generic #
fn new_request_generic[T](method string, params T) RequestGeneric[T]
new_request_generic creates a new generic JSON-RPC request with strongly-typed parameters. It automatically sets the JSON-RPC version and generates a unique ID.
Parameters:- method: The name of the method to invoke on the server
- params: The parameters to the method, of type T
Returns:- A fully initialized RequestGeneric object with parameters of type T
fn new_response #
fn new_response(id int, result string) Response
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
fn new_response_generic #
fn new_response_generic[D](id int, result D) ResponseGeneric[D]
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
fn new_unix_socket_client #
fn new_unix_socket_client(socket_path string) &Client
new_client creates a new zinit client instance socket_path: path to the Unix socket (default: /tmp/zinit.sock)
fn new_unix_socket_transport #
fn new_unix_socket_transport(socket_path string) &UnixSocketTransport
new_unix_socket_transport creates a new Unix socket transport
interface IRPCTransportClient #
interface IRPCTransportClient {
mut:
// send transmits a JSON-RPC request string and returns the response as a string.
// Parameters:
// - request: The JSON-RPC request string to send
// - params: Configuration parameters for the send operation
// Returns:
// - The response string or an error if the send operation fails
send(request string, params SendParams) !string
}
IRPCTransportClient defines the interface for transport mechanisms used by the JSON-RPC client. This allows for different transport implementations (HTTP, WebSocket, etc.) to be used with the same client code.
type ProcedureHandler #
type ProcedureHandler = fn (payload string) !string
ProcedureHandler is a function type that processes a JSON-RPC request payload and returns a response. The function should:1. Decode the payload to extract parameters2. Execute the procedure with the extracted parameters3. Return the result as a JSON-encoded stringIf an error occurs during any of these steps, it should be returned.
fn (RequestGeneric[T]) encode #
fn (req RequestGeneric[T]) encode[T]() string
encode serializes the RequestGeneric object into a JSON string.
Returns:- A JSON string representation of the RequestGeneric object
fn (ResponseGeneric[D]) encode #
fn (resp ResponseGeneric[D]) encode() string
encode serializes the ResponseGeneric object into a JSON string.
Returns:- A JSON string representation of the ResponseGeneric object
fn (ResponseGeneric[D]) validate #
fn (resp ResponseGeneric[D]) validate() !
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
fn (ResponseGeneric[D]) is_error #
fn (resp ResponseGeneric[D]) is_error() bool
is_error checks if the response contains an error.
Returns:- true if the response contains an error, false otherwise
fn (ResponseGeneric[D]) is_result #
fn (resp ResponseGeneric[D]) is_result() bool
is_result checks if the response contains a result.
Returns:- true if the response contains a result, false otherwise
fn (ResponseGeneric[D]) error #
fn (resp ResponseGeneric[D]) error() ?RPCError
error returns the error object if present in the generic response.
Returns:- The error object if present, or none if no error is present
fn (ResponseGeneric[D]) result #
fn (resp ResponseGeneric[D]) result() !D
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
fn (UnixSocketTransport) send #
fn (mut t UnixSocketTransport) send(request string, params SendParams) !string
send implements the IRPCTransportClient interface
struct Client #
struct Client {
pub mut:
// The transport implementation used to send requests and receive responses
transport IRPCTransportClient
}
Client implements a JSON-RPC 2.0 client that can send requests and process responses. It uses a pluggable transport layer that implements the IRPCTransportClient interface.
fn (Client) send #
fn (mut c Client) send[T, D](request RequestGeneric[T], params SendParams) !D
send sends a JSON-RPC request with parameters of type T and expects a response with result of type D. This method handles the full request-response cycle including validation and error handling.
Type Parameters:- T: The type of the request parameters
- D: The expected type of the response result
Parameters:- request: The JSON-RPC request object with parameters of type T
- params: Configuration parameters for the send operation
Returns:- The response result of type D or an error if any step in the process fails
struct Handler #
struct Handler {
pub mut:
// A map where keys are method names and values are the corresponding procedure handler functions
procedures map[string]ProcedureHandler
}
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.
fn (Handler) register_procedure #
fn (mut handler Handler) register_procedure(method string, procedure ProcedureHandler)
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
fn (Handler) handler #
fn (handler Handler) handler(client &websocket.Client, message string) string
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.
Parameters:- client: The WebSocket client that sent the message
- message: The JSON-RPC request message as a string
Returns:- The JSON-RPC response as a string
Note: This method panics if an error occurs during handling
fn (Handler) handle #
fn (handler Handler) handle(message string) !string
handle processes a JSON-RPC request message and invokes the appropriate procedure handler. If the requested method is not found, it returns a method_not_found error response.
Parameters:- message: The JSON-RPC request message as a string
Returns:- The JSON-RPC response as a string, or an error if processing fails
struct Notification #
struct Notification {
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string = '2.0' @[required]
// The name of the method to be invoked on the server
method string @[required]
}
Notification represents a JSON-RPC 2.0 notification object. It contains all the required fields according to the JSON-RPC 2.0 specification. See: https://www.jsonrpc.org/specification#notification
struct NotificationGeneric #
struct NotificationGeneric[T] {
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string = '2.0' @[required]
// The name of the method to be invoked on the server
method string @[required]
params ?T
}
Notification represents a JSON-RPC 2.0 notification object. It contains all the required fields according to the JSON-RPC 2.0 specification. See: https://www.jsonrpc.org/specification#notification
struct RPCError #
struct RPCError {
pub mut:
// Numeric error code. Predefined codes are in the range -32768 to -32000.
// Custom error codes should be outside this range.
code int
// Short description of the error
message string
// Additional information about the error (optional)
data ?string
}
RPCError represents a JSON-RPC 2.0 error object as defined in the specification. Error objects contain a code, message, and optional data field to provide more information about the error that occurred.
fn (RPCError) msg #
fn (err RPCError) msg() string
msg returns the error message. This is a convenience method to access the message field.
Returns:- The error message string
fn (RPCError) code #
fn (err RPCError) code() int
code returns the error code. This is a convenience method to access the code field.
Returns:- The numeric error code
fn (RPCError) is_empty #
fn (err RPCError) is_empty() bool
is_empty checks if the error object is empty (uninitialized). An error is considered empty if its code is 0, which is not a valid JSON-RPC error code.
Returns:- true if the error is empty, false otherwise
struct Request #
struct Request {
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string @[required]
// The name of the method to be invoked on the server
method string @[required]
// The parameters to the method, encoded as a JSON string
// This can be omitted if the method doesn't require parameters
params string
// An identifier established by the client that must be included in the response
// This is used to correlate requests with their corresponding responses
id int @[required]
}
Request represents a JSON-RPC 2.0 request object. It contains all the required fields according to the JSON-RPC 2.0 specification. See: https://www.jsonrpc.org/specification#request_object
fn (Request) encode #
fn (req Request) encode() string
encode serializes the Request object into a JSON string.
Returns:- A JSON string representation of the Request
fn (Request) validate #
fn (req Request) validate() !
validate checks if the Request object contains all required fields according to the JSON-RPC 2.0 specification.
Returns:- An error if validation fails, otherwise nothing
struct RequestGeneric #
struct RequestGeneric[T] {
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string @[required]
// The name of the method to be invoked on the server
method string @[required]
// The parameters to the method, with a specific type T
params T
// An identifier established by the client
id int @[required]
}
RequestGeneric is a type-safe version of the Request struct that allows for strongly-typed parameters using generics. This provides compile-time type safety for request parameters.
struct Response #
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]
}
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
fn (Response) encode #
fn (resp Response) encode() string
encode serializes the Response object into a JSON string.
Returns:- A JSON string representation of the Response
fn (Response) validate #
fn (resp Response) validate() !
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
fn (Response) is_error #
fn (resp Response) is_error() bool
is_error checks if the response contains an error.
Returns:- true if the response contains an error, false otherwise
fn (Response) is_result #
fn (resp Response) is_result() bool
is_result checks if the response contains a result.
Returns:- true if the response contains a result, false otherwise
fn (Response) error #
fn (resp Response) error() ?RPCError
error returns the error object if present in the response.
Returns:- The error object if present, or none if no error is present
fn (Response) result #
fn (resp Response) result() !string
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
struct ResponseGeneric #
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]
}
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.
struct SendParams #
struct SendParams {
pub:
// Maximum time in seconds to wait for a response (default: 2)
timeout int = 2
// Number of times to retry the request if it fails
retry int
}
SendParams defines configuration options for sending JSON-RPC requests. These parameters control timeout and retry behavior.
- README
- Constants
- fn decode_request
- fn decode_request_generic
- fn decode_request_id
- fn decode_request_method
- fn decode_response
- fn decode_response_generic
- fn new_blank_notification
- fn new_client
- fn new_error
- fn new_error_response
- fn new_handler
- fn new_notification
- fn new_request
- fn new_request_generic
- fn new_response
- fn new_response_generic
- fn new_unix_socket_client
- fn new_unix_socket_transport
- interface IRPCTransportClient
- type ProcedureHandler
- type RequestGeneric[T]
- type ResponseGeneric[D]
- type UnixSocketTransport
- struct Client
- struct Handler
- struct Notification
- struct NotificationGeneric
- struct RPCError
- struct Request
- struct RequestGeneric
- struct Response
- struct ResponseGeneric
- struct SendParams