Merge branch 'development' of github.com:freeflowuniverse/herolib into development

This commit is contained in:
Timur Gordon
2025-08-29 10:18:25 +02:00
302 changed files with 8130 additions and 3647 deletions

View File

@@ -14,6 +14,7 @@ mut:
// Returns:
// - The response string or an error if the send operation fails
send(request string, params SendParams) !string
url() string
}
// SendParams defines configuration options for sending JSON-RPC requests.
@@ -65,12 +66,19 @@ pub fn new_client(transport IRPCTransportClient) &Client {
// - 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 {
// Send the encoded request through the transport layer
console.print_debug('Sending request: ${request.encode()}')
response_json := c.transport.send(request.encode(), params)!
console.print_debug('Sending request: \n*****\n${request.encode()}\n*****\n')
response_json := c.transport.send(request.encode(), params) or {
if err.msg().contains('net: op timed out') {
console.print_debug('time out')
}
// print_backtrace()
// println(err)
// $dbg;
return error('Failed to send request for jsonrpc:\n${request.encode()}\n${err}')
}
// 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}')
return error('Unable to decode response.\nRequest: ${request.encode()}\nResponse: ${response_json}\nError: ${err}')
}
// Validate the response according to the JSON-RPC specification
@@ -82,7 +90,19 @@ pub fn (mut c Client) send[T, D](request RequestGeneric[T], params SendParams) !
}
// Return the result or propagate any error from the response
return response.result()!
return response.result() or {
myerror := response.error_ or {
return error('Failed to get error from response:\nRequest: ${request.encode()}\nResponse: ${response_json}\n${err}')
}
// print_backtrace()
mut myreq := request.encode()
if c.transport is UnixSocketTransport {
myreq = "To Test:\n**********\necho '\n${myreq}\n' | nc -U ${c.transport.url()}\n**********"
} else {
myreq = 'Path:${c.transport.url()}\nRequest:\n${myreq}'
}
return error('\nRPC Request Failed.\n${myreq}\n${myerror}')
}
}
pub fn (mut c Client) send_str(request Request, params SendParams) !string {

View File

@@ -58,7 +58,7 @@ pub fn decode_request(data string) !Request {
// Returns:
// - A JSON string representation of the Request
pub fn (req Request) encode() string {
return json2.encode(req)
return json2.encode_pretty(req)
}
// validate checks if the Request object contains all required fields
@@ -158,5 +158,5 @@ pub fn decode_request_generic[T](data string) !RequestGeneric[T] {
// Returns:
// - A JSON string representation of the RequestGeneric object
pub fn (req RequestGeneric[T]) encode[T]() string {
return json2.encode(req)
return json2.encode_pretty(req).replace('\\/', '/')
}

View File

@@ -70,8 +70,10 @@ pub fn decode_response(data string) !Response {
// 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() {
print_backtrace()
return error('Invalid JSONRPC response, no error and result found.')
} else if 'error' in raw_map.keys() && 'result' in raw_map.keys() {
print_backtrace()
return error('Invalid JSONRPC response, both error and result found.')
}
@@ -87,7 +89,10 @@ 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') }.int()
id: raw_map['id'] or {
print_backtrace()
return error('Invalid JSONRPC response, no ID Field found')
}.int()
jsonrpc: jsonrpc_version
result: raw_map['result']!.str()
}
@@ -113,10 +118,12 @@ pub fn (resp Response) encode() string {
// 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}')
// }
if resp.error_ != none && resp.result != none {
return error('Response contains both error and result.\n- Error: ${resp.error_.str()}\n- Result: ${resp.result}')
}
if resp.error_ == none && resp.result == none {
return error('Response contains neither error nor result.')
}
}
// is_error checks if the response contains an error.
@@ -202,15 +209,15 @@ pub fn new_response_generic[D](id int, result D) ResponseGeneric[D] {
// 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() {
print_backtrace()
return error('Invalid JSONRPC response, no error and result found.')
} else if 'error' in raw_map.keys() && 'result' in raw_map.keys() {
print_backtrace()
return error('Invalid JSONRPC response, both error and result found.')
}
@@ -218,6 +225,7 @@ pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
if err := raw_map['error'] {
return ResponseGeneric[D]{
id: raw_map['id'] or {
print_backtrace()
return error('Invalid JSONRPC response, no ID Field found')
}.int()
jsonrpc: jsonrpc_version
@@ -228,7 +236,10 @@ 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') }.int()
id: raw_map['id'] or {
print_backtrace()
return error('Invalid JSONRPC response, no ID Field found')
}.int()
jsonrpc: jsonrpc_version
result: resp.result
}
@@ -249,6 +260,7 @@ pub fn (resp ResponseGeneric[D]) encode() string {
// - An error if validation fails, otherwise nothing
pub fn (resp ResponseGeneric[D]) validate() ! {
if resp.is_error() && resp.is_result() {
print_backtrace()
return error('Response contains both error and result.\n- Error: ${resp.error.str()}\n- Result: ${resp.result}')
}
}

View File

@@ -3,6 +3,7 @@ module jsonrpc
import net.unix
import time
import net
import freeflowuniverse.herolib.ui.console
// UnixSocketTransport implements the IRPCTransportClient interface for Unix domain sockets
struct UnixSocketTransport {
@@ -17,6 +18,10 @@ pub fn new_unix_socket_transport(socket_path string) &UnixSocketTransport {
}
}
pub fn (mut t UnixSocketTransport) url() string {
return '${t.socket_path}'
}
// send implements the IRPCTransportClient interface
pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !string {
// Create a Unix domain socket client
@@ -28,7 +33,8 @@ pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !stri
// Close the socket explicitly
unix.shutdown(socket.sock.handle)
socket.close() or {}
// console.print_debug('Socket closed')
print_backtrace()
console.print_debug('The server did not close the socket, we did timeout or there was other error.')
}
// Set timeout if specified
@@ -51,19 +57,35 @@ pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !stri
// console.print_debug('Reading response from socket...')
// Read up to 64000 bytes
mut res := []u8{len: 64000, cap: 64000}
n := socket.read(mut res)!
n := socket.read(mut res) or {
// can be timeout
if err.code() == 11 { // Resource temporarily unavailable (EWOULDBLOCK)
console.print_debug('Resource temporarily unavailable, retrying...')
time.sleep(100 * time.millisecond)
continue
}
if err.code() == 9 {
console.print_debug('Timeout...')
break
}
return err
}
// console.print_debug('Read ${n} bytes from socket')
if n == 0 {
// console.print_debug('No more data to read, breaking loop')
// breaking loop')
break
}
// Append the newly read data to the total response
res_total << res[..n]
if n < 8192 {
// console.print_debug('No more data to read, breaking loop after ${n} bytes')
// TODO: this seems weird
break
}
}
unix.shutdown(socket.sock.handle)
socket.close() or {}
// println(res_total.bytestr().trim_space())
// println(19)

View File

@@ -1,40 +0,0 @@
module jsonrpcmodel
// OpenRPCSpec represents the OpenRPC specification structure
pub struct OpenRPCSpec {
pub mut:
openrpc string @[json: 'openrpc'] // OpenRPC version
info OpenRPCInfo @[json: 'info'] // API information
methods []OpenRPCMethod @[json: 'methods'] // Available methods
servers []OpenRPCServer @[json: 'servers'] // Server information
}
// OpenRPCInfo represents API information
pub struct OpenRPCInfo {
pub mut:
version string @[json: 'version'] // API version
title string @[json: 'title'] // API title
description string @[json: 'description'] // API description
license OpenRPCLicense @[json: 'license'] // License information
}
// OpenRPCLicense represents license information
pub struct OpenRPCLicense {
pub mut:
name string @[json: 'name'] // License name
}
// OpenRPCMethod represents an RPC method
pub struct OpenRPCMethod {
pub mut:
name string @[json: 'name'] // Method name
description string @[json: 'description'] // Method description
// Note: params and result are dynamic and would need more complex handling
}
// OpenRPCServer represents server information
pub struct OpenRPCServer {
pub mut:
name string @[json: 'name'] // Server name
url string @[json: 'url'] // Server URL
}

View File

@@ -5,9 +5,7 @@ OpenRPC V library. Model for OpenRPC, client code generation, and specification
## Definitions
- OpenRPC Specifications: Specifications that define standards for describing JSON-RPC API's.
- [OpenRPC Document](https://spec.open-rpc.org/#openrpc-document): "A document that defines or describes an API conforming to the OpenRPC Specification."
- OpenRPC Client: An API Client (using either HTTP or Websocket) that governs functions (one per RPC Method defined in OpenRPC Document) to communicate with RPC servers and perform RPCs.
## OpenRPC Document Generation