Merge branch 'development' into 6-openrpc-code-generator
This commit is contained in:
@@ -1,47 +1,81 @@
|
||||
module jsonrpc
|
||||
|
||||
// Interface for a transport client used by the JSON-RPC client.
|
||||
// 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.
|
||||
pub interface IRPCTransportClient {
|
||||
mut:
|
||||
send(string, SendParams) !string // Sends a request and returns the response as a string
|
||||
// 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
|
||||
}
|
||||
|
||||
// JSON-RPC WebSocket client implementation.
|
||||
// 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.
|
||||
pub struct Client {
|
||||
mut:
|
||||
transport IRPCTransportClient // Transport layer to handle communication
|
||||
// The transport implementation used to send requests and receive responses
|
||||
transport IRPCTransportClient
|
||||
}
|
||||
|
||||
// Creates a new JSON-RPC client instance.
|
||||
// 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
|
||||
pub fn new_client(client Client) &Client {
|
||||
return &Client{...client}
|
||||
}
|
||||
|
||||
// Parameters for configuring the `send` function.
|
||||
// SendParams defines configuration options for sending JSON-RPC requests.
|
||||
// These parameters control timeout and retry behavior.
|
||||
@[params]
|
||||
pub struct SendParams {
|
||||
timeout int = 60 // Timeout in seconds (default: 60)
|
||||
retry int // Number of retry attempts
|
||||
// Maximum time in seconds to wait for a response (default: 60)
|
||||
timeout int = 60
|
||||
|
||||
// Number of times to retry the request if it fails
|
||||
retry int
|
||||
}
|
||||
|
||||
// Sends a JSON-RPC request and returns the response result of type `D`.
|
||||
// Validates the response and ensures the request/response IDs match.
|
||||
// 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
|
||||
pub fn (mut c Client) send[T, D](request RequestGeneric[T], params SendParams) !D {
|
||||
response_json := c.transport.send(request.encode(), params)! // Send the encoded request
|
||||
// Send the encoded request through the transport layer
|
||||
response_json := c.transport.send(request.encode(), params)!
|
||||
|
||||
// Decode the response JSON into a strongly-typed response object
|
||||
response := decode_response_generic[D](response_json) or {
|
||||
return error('Unable to decode response.\n- Response: ${response_json}\n- Error: ${err}')
|
||||
}
|
||||
|
||||
// Validate the response according to the JSON-RPC specification
|
||||
response.validate() or {
|
||||
return error('Received invalid response: ${err}')
|
||||
}
|
||||
|
||||
// Ensure the response ID matches the request ID to prevent response/request mismatch
|
||||
if response.id != request.id {
|
||||
return error('Received response with different id ${response}')
|
||||
}
|
||||
|
||||
println('response ${response}')
|
||||
|
||||
// Return the result or propagate the error.
|
||||
// Return the result or propagate any error from the response
|
||||
return response.result()!
|
||||
}
|
||||
@@ -2,13 +2,31 @@ module jsonrpc
|
||||
|
||||
import time
|
||||
|
||||
// This file contains tests for the JSON-RPC client implementation.
|
||||
// It uses a mock transport client to simulate JSON-RPC server responses without requiring an actual server.
|
||||
|
||||
// TestRPCTransportClient is a mock implementation of the RPCTransport interface.
|
||||
// It simulates a JSON-RPC server by returning predefined responses based on the method name.
|
||||
struct TestRPCTransportClient {}
|
||||
|
||||
// send implements the RPCTransport interface's send method.
|
||||
// Instead of sending the request to a real server, it decodes the request and returns
|
||||
// a predefined response based on the method name.
|
||||
//
|
||||
// Parameters:
|
||||
// - request_json: The JSON-RPC request as a JSON string
|
||||
// - params: Additional parameters for sending the request
|
||||
//
|
||||
// Returns:
|
||||
// - A JSON-encoded response string or an error if decoding fails
|
||||
fn (t TestRPCTransportClient) send(request_json string, params SendParams) !string {
|
||||
// Decode the incoming request to determine which response to return
|
||||
request := decode_request(request_json)!
|
||||
|
||||
// instead of sending request and returning response from rpc server
|
||||
// our test rpc transport client return a response
|
||||
// Return different responses based on the method name:
|
||||
// - 'echo': Returns the params as the result
|
||||
// - 'test_error': Returns an error response
|
||||
// - anything else: Returns a method_not_found error
|
||||
response := if request.method == 'echo' {
|
||||
new_response(request.id, request.params)
|
||||
} else if request.method == 'test_error' {
|
||||
@@ -24,43 +42,58 @@ fn (t TestRPCTransportClient) send(request_json string, params SendParams) !stri
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// TestClient extends the Client struct for testing purposes.
|
||||
struct TestClient {
|
||||
Client
|
||||
}
|
||||
|
||||
// test_new tests the creation of a new JSON-RPC client with a mock transport.
|
||||
fn test_new() {
|
||||
// Create a new client with the mock transport
|
||||
client := new_client(
|
||||
transport: TestRPCTransportClient{}
|
||||
)
|
||||
}
|
||||
|
||||
// test_send_json_rpc tests the client's ability to send requests and handle responses.
|
||||
// It tests three scenarios:
|
||||
// 1. Successful response from an 'echo' method
|
||||
// 2. Error response from a 'test_error' method
|
||||
// 3. Method not found error from a non-existent method
|
||||
fn test_send_json_rpc() {
|
||||
// Create a new client with the mock transport
|
||||
mut client := new_client(
|
||||
transport: TestRPCTransportClient{}
|
||||
)
|
||||
|
||||
// Test case 1: Successful echo response
|
||||
request0 := new_request_generic[string]('echo', 'ECHO!')
|
||||
response0 := client.send[string, string](request0)!
|
||||
assert response0 == 'ECHO!'
|
||||
|
||||
// Test case 2: Error response
|
||||
request1 := new_request_generic[string]('test_error', '')
|
||||
if response1 := client.send[string, string](request1) {
|
||||
assert false, 'Should return internal error'
|
||||
} else {
|
||||
// Verify the error details
|
||||
assert err is RPCError
|
||||
assert err.code() == 1
|
||||
assert err.msg() == 'intentional jsonrpc error response'
|
||||
}
|
||||
|
||||
// Test case 3: Method not found error
|
||||
request2 := new_request_generic[string]('nonexistent_method', '')
|
||||
if response2 := client.send[string, string](request2) {
|
||||
assert false, 'Should return not found error'
|
||||
} else {
|
||||
// Verify the error details
|
||||
assert err is RPCError
|
||||
assert err.code() == 32601
|
||||
assert err.code() == -32601
|
||||
assert err.msg() == 'Method not found'
|
||||
}
|
||||
|
||||
// Duplicate of test case 1 (can be removed or kept for additional verification)
|
||||
request := new_request_generic[string]('echo', 'ECHO!')
|
||||
response := client.send[string, string](request)!
|
||||
assert response == 'ECHO!'
|
||||
|
||||
@@ -3,33 +3,70 @@ module jsonrpc
|
||||
import log
|
||||
import net.websocket
|
||||
|
||||
// JSON-RPC WebSoocket Server
|
||||
// This file implements a JSON-RPC 2.0 handler for WebSocket servers.
|
||||
// It provides functionality to register procedure handlers and process incoming JSON-RPC requests.
|
||||
|
||||
// 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:
|
||||
// map of method names to procedure handlers
|
||||
// A map where keys are method names and values are the corresponding procedure handler functions
|
||||
procedures map[string]ProcedureHandler
|
||||
}
|
||||
|
||||
// ProcedureHandler handles executing procedure calls
|
||||
// decodes payload, execute procedure function, return encoded result
|
||||
// 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 parameters
|
||||
// 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
|
||||
|
||||
// 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
|
||||
pub fn new_handler(handler Handler) !&Handler {
|
||||
return &Handler{...handler}
|
||||
}
|
||||
|
||||
// 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
|
||||
pub fn (handler Handler) handler(client &websocket.Client, message string) string {
|
||||
return handler.handle(message) or { panic(err) }
|
||||
}
|
||||
|
||||
// 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
|
||||
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}')
|
||||
|
||||
// Look up the procedure handler for the requested method
|
||||
procedure_func := handler.procedures[method] or {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,68 +1,144 @@
|
||||
module jsonrpc
|
||||
|
||||
// echo method for testing purposes
|
||||
// This file contains tests for the JSON-RPC handler implementation.
|
||||
// It tests the handler's ability to process requests, invoke the appropriate procedure,
|
||||
// and return properly formatted responses.
|
||||
|
||||
// method_echo is a simple test method that returns the input text.
|
||||
// Used to test successful request handling.
|
||||
//
|
||||
// Parameters:
|
||||
// - text: A string to echo back
|
||||
//
|
||||
// Returns:
|
||||
// - The same string that was passed in
|
||||
fn method_echo(text string) !string {
|
||||
return text
|
||||
}
|
||||
|
||||
// TestStruct is a simple struct used for testing struct parameter handling.
|
||||
pub struct TestStruct {
|
||||
data string
|
||||
}
|
||||
|
||||
// structure echo method for testing purposes
|
||||
// method_echo_struct is a test method that returns the input struct.
|
||||
// Used to test handling of complex types in JSON-RPC.
|
||||
//
|
||||
// Parameters:
|
||||
// - structure: A TestStruct instance to echo back
|
||||
//
|
||||
// Returns:
|
||||
// - The same TestStruct that was passed in
|
||||
fn method_echo_struct(structure TestStruct) !TestStruct {
|
||||
return structure
|
||||
}
|
||||
|
||||
// method that returns error for testing purposes
|
||||
// method_error is a test method that always returns an error.
|
||||
// Used to test error handling in the JSON-RPC flow.
|
||||
//
|
||||
// Parameters:
|
||||
// - text: A string (not used)
|
||||
//
|
||||
// Returns:
|
||||
// - Always returns an error
|
||||
fn method_error(text string) !string {
|
||||
return error('some error')
|
||||
}
|
||||
|
||||
// method_echo_handler is a procedure handler for the method_echo function.
|
||||
// It decodes the request, calls method_echo, and encodes the response.
|
||||
//
|
||||
// Parameters:
|
||||
// - data: The JSON-RPC request as a string
|
||||
//
|
||||
// Returns:
|
||||
// - A JSON-encoded response string
|
||||
fn method_echo_handler(data string) !string {
|
||||
// Decode the request with string parameters
|
||||
request := decode_request_generic[string](data)!
|
||||
|
||||
// Call the echo method and handle any errors
|
||||
result := method_echo(request.params) or {
|
||||
// If an error occurs, create an error response
|
||||
response := new_error_response(request.id,
|
||||
code: err.code()
|
||||
message: err.msg()
|
||||
)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Create a success response with the result
|
||||
response := new_response_generic(request.id, result)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// method_echo_struct_handler is a procedure handler for the method_echo_struct function.
|
||||
// It demonstrates handling of complex struct types in JSON-RPC.
|
||||
//
|
||||
// Parameters:
|
||||
// - data: The JSON-RPC request as a string
|
||||
//
|
||||
// Returns:
|
||||
// - A JSON-encoded response string
|
||||
fn method_echo_struct_handler(data string) !string {
|
||||
// Decode the request with TestStruct parameters
|
||||
request := decode_request_generic[TestStruct](data)!
|
||||
|
||||
// Call the echo struct method and handle any errors
|
||||
result := method_echo_struct(request.params) or {
|
||||
// If an error occurs, create an error response
|
||||
response := new_error_response(request.id,
|
||||
code: err.code()
|
||||
message: err.msg()
|
||||
)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Create a success response with the struct result
|
||||
response := new_response_generic[TestStruct](request.id, result)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// method_error_handler is a procedure handler for the method_error function.
|
||||
// It demonstrates error handling in JSON-RPC procedure handlers.
|
||||
//
|
||||
// Parameters:
|
||||
// - data: The JSON-RPC request as a string
|
||||
//
|
||||
// Returns:
|
||||
// - A JSON-encoded error response string
|
||||
fn method_error_handler(data string) !string {
|
||||
// Decode the request with string parameters
|
||||
request := decode_request_generic[string](data)!
|
||||
|
||||
// Call the error method, which always returns an error
|
||||
result := method_error(request.params) or {
|
||||
// Create an error response with the error details
|
||||
response := new_error_response(request.id,
|
||||
code: err.code()
|
||||
message: err.msg()
|
||||
)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// This code should never be reached since method_error always returns an error
|
||||
response := new_response_generic(request.id, result)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// test_new tests the creation of a new JSON-RPC handler.
|
||||
fn test_new() {
|
||||
// Create a new handler with no procedures
|
||||
handler := new_handler(Handler{})!
|
||||
}
|
||||
|
||||
// test_handle tests the handler's ability to process different types of requests.
|
||||
// It tests three scenarios:
|
||||
// 1. A successful string echo request
|
||||
// 2. A successful struct echo request
|
||||
// 3. A request that results in an error
|
||||
fn test_handle() {
|
||||
// Create a new handler with three test procedures
|
||||
handler := new_handler(Handler{
|
||||
procedures: {
|
||||
'method_echo': method_echo_handler
|
||||
@@ -71,18 +147,21 @@ fn test_handle() {
|
||||
}
|
||||
})!
|
||||
|
||||
// Test case 1: String echo request
|
||||
params0 := 'ECHO!'
|
||||
request0 := new_request_generic[string]('method_echo', params0)
|
||||
decoded0 := handler.handle(request0.encode())!
|
||||
response0 := decode_response_generic[string](decoded0)!
|
||||
assert response0.result()! == params0
|
||||
|
||||
// Test case 2: Struct echo request
|
||||
params1 := TestStruct{'ECHO!'}
|
||||
request1 := new_request_generic[TestStruct]('method_echo_struct', params1)
|
||||
decoded1 := handler.handle(request1.encode())!
|
||||
response1 := decode_response_generic[TestStruct](decoded1)!
|
||||
assert response1.result()! == params1
|
||||
|
||||
// Test case 3: Error request
|
||||
params2 := 'ECHO!'
|
||||
request2 := new_request_generic[string]('method_error', params2)
|
||||
decoded2 := handler.handle(request2.encode())!
|
||||
|
||||
BIN
lib/schemas/jsonrpc/jsonrpc.dylib
Executable file
BIN
lib/schemas/jsonrpc/jsonrpc.dylib
Executable file
Binary file not shown.
@@ -1,41 +1,78 @@
|
||||
module jsonrpc
|
||||
|
||||
// Predefined JSON-RPC errors as per the specification: https://www.jsonrpc.org/specification
|
||||
// Standard JSON-RPC 2.0 error codes and messages as defined in the specification
|
||||
// See: https://www.jsonrpc.org/specification#error_object
|
||||
|
||||
// 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
|
||||
pub const parse_error = RPCError{
|
||||
code: 32700
|
||||
code: -32700
|
||||
message: 'Parse error'
|
||||
data: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.'
|
||||
}
|
||||
|
||||
// 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
|
||||
pub const invalid_request = RPCError{
|
||||
code: 32600
|
||||
code: -32600
|
||||
message: 'Invalid Request'
|
||||
data: 'The JSON sent is not a valid Request object.'
|
||||
}
|
||||
|
||||
// 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
|
||||
pub const method_not_found = RPCError{
|
||||
code: 32601
|
||||
code: -32601
|
||||
message: 'Method not found'
|
||||
data: 'The method does not exist / is not available.'
|
||||
}
|
||||
|
||||
// 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
|
||||
pub const invalid_params = RPCError{
|
||||
code: 32602
|
||||
code: -32602
|
||||
message: 'Invalid params'
|
||||
data: 'Invalid method parameter(s).'
|
||||
}
|
||||
|
||||
// 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
|
||||
pub const internal_error = RPCError{
|
||||
code: 32603
|
||||
message: 'Internal RPCError'
|
||||
code: -32603
|
||||
message: 'Internal Error'
|
||||
data: 'Internal JSON-RPC error.'
|
||||
}
|
||||
|
||||
// Represents a JSON-RPC error object with a code, message, and optional data.
|
||||
// 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.
|
||||
pub struct RPCError {
|
||||
pub mut:
|
||||
code int // Error code indicating the type of error
|
||||
message string // Brief error description
|
||||
data string // Additional details about the error
|
||||
// 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
|
||||
}
|
||||
|
||||
// Creates a new error response for a given request ID.
|
||||
// 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
|
||||
pub fn new_error(id string, error RPCError) Response {
|
||||
return Response{
|
||||
jsonrpc: jsonrpc_version
|
||||
@@ -44,17 +81,29 @@ pub fn new_error(id string, error RPCError) Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the error message.
|
||||
// msg returns the error message.
|
||||
// This is a convenience method to access the message field.
|
||||
//
|
||||
// Returns:
|
||||
// - The error message string
|
||||
pub fn (err RPCError) msg() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
// Returns the error code.
|
||||
// code returns the error code.
|
||||
// This is a convenience method to access the code field.
|
||||
//
|
||||
// Returns:
|
||||
// - The numeric error code
|
||||
pub fn (err RPCError) code() int {
|
||||
return err.code
|
||||
}
|
||||
|
||||
// Checks if the error object is empty (i.e., uninitialized).
|
||||
// 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
|
||||
pub fn (err RPCError) is_empty() bool {
|
||||
return err.code == 0
|
||||
}
|
||||
@@ -3,36 +3,68 @@ module jsonrpc
|
||||
import x.json2
|
||||
import rand
|
||||
|
||||
// Represents a JSON-RPC request with essential fields.
|
||||
// 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
|
||||
pub struct Request {
|
||||
pub mut:
|
||||
jsonrpc string @[required] // JSON-RPC version, e.g., "2.0"
|
||||
method string @[required] // Method to invoke
|
||||
params string // JSON-encoded parameters
|
||||
id string @[required] // Unique request ID
|
||||
// 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 string @[required]
|
||||
}
|
||||
|
||||
// Creates a new JSON-RPC request with the specified method and parameters.
|
||||
// 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
|
||||
pub fn new_request(method string, params string) Request {
|
||||
return Request{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
id: rand.uuid_v4() // Automatically generate a unique ID
|
||||
id: rand.uuid_v4() // Automatically generate a unique ID using UUID v4
|
||||
}
|
||||
}
|
||||
|
||||
// Decodes a JSON string into a `Request` object.
|
||||
// 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
|
||||
pub fn decode_request(data string) !Request {
|
||||
return json2.decode[Request](data)!
|
||||
}
|
||||
|
||||
// Encodes the `Request` object into a JSON string.
|
||||
// encode serializes the Request object into a JSON string.
|
||||
//
|
||||
// Returns:
|
||||
// - A JSON string representation of the Request
|
||||
pub fn (req Request) encode() string {
|
||||
return json2.encode(req)
|
||||
}
|
||||
|
||||
// Validates that the response does not contain both `result` and `error`.
|
||||
// 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
|
||||
pub fn (req Request) validate() ! {
|
||||
if req.jsonrpc == '' {
|
||||
return error('request jsonrpc version not specified')
|
||||
@@ -43,16 +75,33 @@ pub fn (req Request) validate() ! {
|
||||
}
|
||||
}
|
||||
|
||||
// A generic JSON-RPC request struct allowing strongly-typed parameters.
|
||||
// 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.
|
||||
pub 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 string @[required]
|
||||
}
|
||||
|
||||
// Creates a new generic JSON-RPC request.
|
||||
// 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
|
||||
pub fn new_request_generic[T](method string, params T) RequestGeneric[T] {
|
||||
return RequestGeneric[T]{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
@@ -62,8 +111,14 @@ pub fn new_request_generic[T](method string, params T) RequestGeneric[T] {
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the `id` field from a JSON string.
|
||||
// Returns an error if the field is missing.
|
||||
// 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
|
||||
pub fn decode_request_id(data string) !string {
|
||||
data_any := json2.raw_decode(data)!
|
||||
data_map := data_any.as_map()
|
||||
@@ -71,8 +126,14 @@ pub fn decode_request_id(data string) !string {
|
||||
return id_any.str()
|
||||
}
|
||||
|
||||
// Extracts the `method` field from a JSON string.
|
||||
// Returns an error if the field is missing.
|
||||
// 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
|
||||
pub fn decode_request_method(data string) !string {
|
||||
data_any := json2.raw_decode(data)!
|
||||
data_map := data_any.as_map()
|
||||
@@ -80,12 +141,21 @@ pub fn decode_request_method(data string) !string {
|
||||
return method_any.str()
|
||||
}
|
||||
|
||||
// Decodes a JSON string into a generic `Request` object.
|
||||
// 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
|
||||
pub fn decode_request_generic[T](data string) !RequestGeneric[T] {
|
||||
return json2.decode[RequestGeneric[T]](data)!
|
||||
}
|
||||
|
||||
// Encodes a generic `Request` object into a JSON string.
|
||||
// encode serializes the RequestGeneric object into a JSON string.
|
||||
//
|
||||
// Returns:
|
||||
// - A JSON string representation of the RequestGeneric object
|
||||
pub fn (req RequestGeneric[T]) encode[T]() string {
|
||||
return json2.encode(req)
|
||||
}
|
||||
@@ -3,19 +3,35 @@ module jsonrpc
|
||||
import x.json2
|
||||
import json
|
||||
|
||||
// The JSON-RPC version used for responses.
|
||||
// The JSON-RPC version used for all requests and responses according to the specification.
|
||||
const jsonrpc_version = '2.0'
|
||||
|
||||
// Represents a JSON-RPC response, which includes a result, an error, or both.
|
||||
// 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:
|
||||
jsonrpc string @[required] // JSON-RPC version, e.g., "2.0"
|
||||
result ?string // JSON-encoded result (optional)
|
||||
error_ ?RPCError @[json: 'error'] // Error object if the request failed (optional)
|
||||
id string @[required] // Matches the request ID
|
||||
// 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 string @[required]
|
||||
}
|
||||
|
||||
// Creates a successful response with the given result.
|
||||
// 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 string, result string) Response {
|
||||
return Response{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
@@ -24,7 +40,14 @@ pub fn new_response(id string, result string) Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Creates an error response with the given error object.
|
||||
// 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 string, error RPCError) Response {
|
||||
return Response{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
@@ -33,17 +56,26 @@ pub fn new_error_response(id string, error RPCError) Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Decodes a JSON string into a `Response` object.
|
||||
// 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 {
|
||||
@@ -53,6 +85,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()
|
||||
jsonrpc: jsonrpc_version
|
||||
@@ -60,29 +93,46 @@ pub fn decode_response(data string) !Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Encodes the `Response` object into a JSON string.
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Validates that the response does not contain both `result` and `error`.
|
||||
// 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}')
|
||||
// }
|
||||
}
|
||||
|
||||
// Returns the error if present in the response.
|
||||
// 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
|
||||
}
|
||||
|
||||
// Returns the error if present in the response.
|
||||
// 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
|
||||
}
|
||||
|
||||
// Returns the error if present in the response.
|
||||
// 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
|
||||
@@ -90,7 +140,11 @@ pub fn (resp Response) error() ?RPCError {
|
||||
return none
|
||||
}
|
||||
|
||||
// Returns the result if no error is present.
|
||||
// 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
|
||||
@@ -98,16 +152,32 @@ pub fn (resp Response) result() !string {
|
||||
return resp.result or {''}
|
||||
}
|
||||
|
||||
// A generic JSON-RPC response, allowing strongly-typed results.
|
||||
// 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_ ?RPCError @[json: 'error'] // Error object if the request failed (optional)
|
||||
|
||||
// Error object if the request failed
|
||||
error_ ?RPCError @[json: 'error']
|
||||
|
||||
// Must match the id of the request that generated this response
|
||||
id string @[required]
|
||||
}
|
||||
|
||||
// Creates a successful generic response with the given result.
|
||||
// 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 string, result D) ResponseGeneric[D] {
|
||||
return ResponseGeneric[D]{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
@@ -116,18 +186,29 @@ pub fn new_response_generic[D](id string, result D) ResponseGeneric[D] {
|
||||
}
|
||||
}
|
||||
|
||||
// Decodes a JSON string into a generic `ResponseGeneric` object.
|
||||
// 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
|
||||
println('respodata ${data}')
|
||||
|
||||
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')}.str()
|
||||
@@ -136,6 +217,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()
|
||||
@@ -144,35 +226,54 @@ pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
|
||||
}
|
||||
}
|
||||
|
||||
// Encodes the generic `ResponseGeneric` object into a JSON string.
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Validates that the generic response does not contain both `result` and `error`.
|
||||
// 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}')
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the error if present in the response.
|
||||
// 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
|
||||
}
|
||||
|
||||
// Returns the error if present in the response.
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
// Returns the error if present in the generic response.
|
||||
// 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_?
|
||||
}
|
||||
|
||||
// Returns the result if no error is present in the generic response.
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user