Files
herolib/lib/schemas/jsonrpc
..
...
2025-03-24 06:44:39 +01:00
...
2025-03-24 06:44:39 +01:00
...
2025-09-19 11:52:08 +02:00
...
2025-03-24 06:44:39 +01:00
...
2025-03-24 06:44:39 +01:00
...
2025-03-24 06:44:39 +01:00
...
2025-05-31 12:45:05 +03:00

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 incubaid.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 incubaid.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 incubaid.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 incubaid.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 incubaid.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 incubaid.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.