diff --git a/examples/schemas/openrpc/zinit_rpc_example b/examples/schemas/openrpc/zinit_rpc_example deleted file mode 100755 index d37b9c72..00000000 Binary files a/examples/schemas/openrpc/zinit_rpc_example and /dev/null differ diff --git a/examples/schemas/openrpc/zinit_rpc_example.vsh b/examples/schemas/openrpc/zinit_rpc_example.vsh index c84aa9e0..aa8ae2f1 100755 --- a/examples/schemas/openrpc/zinit_rpc_example.vsh +++ b/examples/schemas/openrpc/zinit_rpc_example.vsh @@ -2,40 +2,73 @@ import freeflowuniverse.herolib.schemas.jsonrpc -mut cl:=jsonrpc.new_unix_socket_client("/tmp/zinit.sock") - - -send_params := jsonrpc.SendParams{ - timeout: 30 - retry: 1 +// Define the service status response structure based on the OpenRPC schema +struct ServiceStatus { + name string + pid int + state string + target string + after map[string]string } -//[]string{} = T is the generic type for the request, which can be any type -request := jsonrpc.new_request_generic('service_list', []string{}) -// 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 { -result := cl.send[[]string, map[string]string](request, send_params)! +// Create a client using the Unix socket transport +mut cl := jsonrpc.new_unix_socket_client("/tmp/zinit.sock") -// println('Service List:') -// for service in result { -// println(service) -// } - - - -// user := client.send[UserParams, UserResult](request, send_params) or { -// eprintln('Error sending request: $err') -// return +// Example 1: List all services +// Create a request for service_list method with empty parameters +list_request := jsonrpc.new_request_generic('service_list', []string{}) + +// Send the request and receive a map of service names to states +println('Sending service_list request...') +service_list := cl.send[[]string, map[string]string](list_request)! + +// Display the service list +println('Service List:') +println(service_list) + +// Example 2: Get status of a specific service +// First, check if we have any services to query +if service_list.len > 0 { + // Get the first service name from the list + service_name := service_list.keys()[0] + + // Create a request for service_status method with the service name as parameter + // The parameter for service_status is a single string (service name) + status_request := jsonrpc.new_request_generic('service_status', {"name": service_name}) + + // Send the request and receive a ServiceStatus object + println('\nSending service_status request for service: $service_name') + service_status := cl.send[ map[string]string, ServiceStatus](status_request)! + + // Display the service status details + println('Service Status:') + println('- Name: ${service_status.name}') + println('- PID: ${service_status.pid}') + println('- State: ${service_status.state}') + println('- Target: ${service_status.target}') + println('- Dependencies:') + for dep_name, dep_state in service_status.after { + println(' - $dep_name: $dep_state') + } +} else { + println('\nNo services found to query status') +} + +// // Example 3: Alternative approach using a string array for the parameter +// // Some JSON-RPC servers expect parameters as an array, even for a single parameter +// println('\nAlternative approach using array parameter:') +// if service_list.len > 0 { +// service_name := service_list.keys()[0] + +// // Create a request with the service name in an array +// status_request_alt := jsonrpc.new_request_generic('service_status', service_name) + +// // Send the request and receive a ServiceStatus object +// println('Sending service_status request for service: $service_name (array parameter)') +// service_status_alt := cl.send[[]string, ServiceStatus](status_request_alt)! + +// // Display the service status details +// println('Service Status (alternative):') +// println('- Name: ${service_status_alt.name}') +// println('- State: ${service_status_alt.state}') // } diff --git a/lib/schemas/jsonrpc/client.v b/lib/schemas/jsonrpc/client.v index eee14433..e4d910dd 100644 --- a/lib/schemas/jsonrpc/client.v +++ b/lib/schemas/jsonrpc/client.v @@ -20,8 +20,8 @@ mut: @[params] pub struct SendParams { pub: - // Maximum time in seconds to wait for a response (default: 60) - timeout int = 60 + // 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 diff --git a/lib/schemas/jsonrpc/model_request.v b/lib/schemas/jsonrpc/model_request.v index 493bf638..db808804 100644 --- a/lib/schemas/jsonrpc/model_request.v +++ b/lib/schemas/jsonrpc/model_request.v @@ -38,7 +38,7 @@ pub fn new_request(method string, params string) Request { jsonrpc: jsonrpc_version method: method params: params - id: rand.i32() // Automatically generate a unique ID using UUID v4 + id: rand.int_in_range(1, 1000000) or {panic("Failed to generate unique ID")} } } @@ -108,7 +108,7 @@ pub fn new_request_generic[T](method string, params T) RequestGeneric[T] { jsonrpc: jsonrpc_version method: method params: params - id: rand.i32() + id: rand.int_in_range(1, 1000000000) or { panic("Failed to generate unique ID") } } } diff --git a/lib/schemas/jsonrpc/troubleshoot_unix_socket.md b/lib/schemas/jsonrpc/troubleshoot_unix_socket.md new file mode 100644 index 00000000..5027d7fb --- /dev/null +++ b/lib/schemas/jsonrpc/troubleshoot_unix_socket.md @@ -0,0 +1,12 @@ +use netcat: + +nc -U /tmp/zinit.sock + +now copy following +{"jsonrpc":"2.0","method":"service_list","params":[],"id":286703868} + +should return something like this: +{"jsonrpc":"2.0","id":286703868,"result":{"test_service":"Running"}} + + + diff --git a/lib/schemas/jsonrpc/unixsocket.v b/lib/schemas/jsonrpc/unixsocket.v index 5507ab9d..e2aef20b 100644 --- a/lib/schemas/jsonrpc/unixsocket.v +++ b/lib/schemas/jsonrpc/unixsocket.v @@ -2,7 +2,7 @@ module jsonrpc import net.unix import time -import json +import freeflowuniverse.herolib.ui.console // UnixSocketTransport implements the IRPCTransportClient interface for Unix domain sockets struct UnixSocketTransport { @@ -20,56 +20,51 @@ pub fn new_unix_socket_transport(socket_path string) &UnixSocketTransport { // send implements the IRPCTransportClient interface pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !string { // Create a Unix domain socket client + // console.print_debug('Connecting to Unix socket at: $t.socket_path') mut socket := unix.connect_stream(t.socket_path)! - defer { socket.close() or {} } + + // Ensure socket is always closed, even if there's an error + defer { + // Close the socket explicitly + unix.shutdown(socket.sock.handle) + socket.close() or {} + // console.print_debug('Socket closed') + } // Set timeout if specified if params.timeout > 0 { socket.set_read_timeout(params.timeout * time.second) socket.set_write_timeout(params.timeout * time.second) + // console.print_debug('Set socket timeout to ${params.timeout} seconds') } // Send the request + // console.print_debug('Sending request: $request') socket.write_string(request + '\n')! + // println(18) - // Read the response - mut response := '' - mut buf := []u8{len: 4096} + // Read the response in a single call with a larger buffer + mut res := []u8{len: 8192, cap: 8192} + n := socket.read(mut res)! + // println(19) - 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 - if response.ends_with('}') { - break - } + // Convert response to string and trim whitespace + mut response := res[..n].bytestr().trim_space() + // console.print_debug('Received ${n} bytes') + + // Basic validation + if response.len == 0 { + return error('Empty response received from server') } + // console.print_debug('Response: $response') return response } -// Client provides a client interface to the zinit JSON-RPC API over Unix socket -// @[heap] -// pub struct UnixSocketClient { -// mut: -// socket_path string -// rpc_client &Client -// request_id int -// } - // new_client creates a new zinit client instance // socket_path: path to the Unix socket (default: /tmp/zinit.sock) pub fn new_unix_socket_client(socket_path string) &Client { mut transport := new_unix_socket_transport(socket_path) mut rpc_client := new_client(transport) - // return &UnixSocketClient{ - // socket_path: socket_path - // rpc_client: rpc_client - // request_id: 0 - // } return rpc_client }