This commit is contained in:
2025-09-02 07:49:10 +02:00
parent b0f82ac834
commit 18f4471d3f
15 changed files with 1758 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
{
"openrpc": "1.0.0-rc1",
"info": {
"title": "Simple RPC overview",
"version": "2.0.0"
},
"methods": [
{
"name": "get_versions",
"summary": "List API versions",
"params": [],
"result": {
"name": "get_version_result",
"schema": {
"type": "object"
}
},
"examples": [
{
"name": "v2",
"summary": "its a v2 example pairing!",
"description": "aight so this is how it works. You foo the bar then you baz the razmataz",
"params": [],
"result": {
"name": "versionsExample",
"value": {
"versions": [
{
"status": "CURRENT",
"updated": "2011-01-21T11:33:21Z",
"id": "v2.0",
"urls": [
{
"href": "http://127.0.0.1:8774/v2/",
"rel": "self"
}
]
},
{
"status": "EXPERIMENTAL",
"updated": "2013-07-23T11:33:21Z",
"id": "v3.0",
"urls": [
{
"href": "http://127.0.0.1:8774/v3/",
"rel": "self"
}
]
}
]
}
}
}
]
},
{
"name": "get_version_details",
"summary": "Show API version details",
"params": [],
"result": {
"name": "foo",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "stringifiedVersionsExample",
"params": [],
"result": {
"name": "bliggityblaow",
"value": "{\n \"versions\": [\n {\n \"status\": \"CURRENT\",\n \"updated\": \"2011-01-21T11:33:21Z\",\n \"id\": \"v2.0\",\n \"urls\": [\n {\n \"href\": \"http://127.0.0.1:8774/v2/\",\n \"rel\": \"self\"\n }\n ]\n },\n {\n \"status\": \"EXPERIMENTAL\",\n \"updated\": \"2013-07-23T11:33:21Z\",\n \"id\": \"v3.0\",\n \"urls\": [\n {\n \"href\": \"http://127.0.0.1:8774/v3/\",\n \"rel\": \"self\"\n }\n ]\n }\n ]\n}\n"
}
}
]
}
]
}

View File

@@ -0,0 +1,212 @@
# module cjson
## Contents
- [create_array](#create_array)
- [create_bool](#create_bool)
- [create_false](#create_false)
- [create_null](#create_null)
- [create_number](#create_number)
- [create_object](#create_object)
- [create_raw](#create_raw)
- [create_string](#create_string)
- [create_true](#create_true)
- [delete](#delete)
- [version](#version)
- [Node](#Node)
- [add_item_to_object](#add_item_to_object)
- [add_item_to_array](#add_item_to_array)
- [print](#print)
- [print_unformatted](#print_unformatted)
- [str](#str)
- [CJsonType](#CJsonType)
- [C.cJSON](#C.cJSON)
## create_array
```v
fn create_array() &Node
```
create_array creates a new JSON array item. Use .add_item_to_array(value) calls, to add items to it later.
[[Return to contents]](#Contents)
## create_bool
```v
fn create_bool(val bool) &Node
```
create_bool creates a new JSON boolean item.
[[Return to contents]](#Contents)
## create_false
```v
fn create_false() &Node
```
create_false creates a new JSON boolean item, with value `false`.
[[Return to contents]](#Contents)
## create_null
```v
fn create_null() &Node
```
create_null creates a new JSON NULL item, with the value `null`. It symbolises a missing value for a given key in an object.
[[Return to contents]](#Contents)
## create_number
```v
fn create_number(val f64) &Node
```
create_number creates a new JSON number item.
[[Return to contents]](#Contents)
## create_object
```v
fn create_object() &Node
```
create_object creates a new JSON object/map item. Use .add_item_to_object(key, value) calls, to add other items to it later.
[[Return to contents]](#Contents)
## create_raw
```v
fn create_raw(const_val string) &Node
```
create_raw creates a new JSON RAW string item.
[[Return to contents]](#Contents)
## create_string
```v
fn create_string(val string) &Node
```
create_string creates a new JSON string item.
[[Return to contents]](#Contents)
## create_true
```v
fn create_true() &Node
```
create_true creates a new JSON boolean item, with value `true`.
[[Return to contents]](#Contents)
## delete
```v
fn delete(node &Node)
```
delete removes the given node from memory. NB: DO NOT USE that node, after you have called `unsafe { delete(node) }` !
[[Return to contents]](#Contents)
## version
```v
fn version() string
```
version returns the version of cJSON as a string.
[[Return to contents]](#Contents)
## Node
```v
type Node = C.cJSON
```
[[Return to contents]](#Contents)
## add_item_to_object
```v
fn (mut obj Node) add_item_to_object(key string, item &Node)
```
add_item_to_array adds the given item to the object, under the given `key`.
[[Return to contents]](#Contents)
## add_item_to_array
```v
fn (mut obj Node) add_item_to_array(item &Node)
```
add_item_to_array append the given item to the object.
[[Return to contents]](#Contents)
## print
```v
fn (mut obj Node) print() string
```
print serialises the node to a string, formatting its structure, so the resulting string is more prettier/human readable.
[[Return to contents]](#Contents)
## print_unformatted
```v
fn (mut obj Node) print_unformatted() string
```
print serialises the node to a string, without formatting its structure, so the resulting string is shorter/cheaper to transmit.
[[Return to contents]](#Contents)
## str
```v
fn (mut obj Node) str() string
```
str returns the unformatted serialisation to string of the given Node.
[[Return to contents]](#Contents)
## CJsonType
```v
enum CJsonType {
t_false
t_true
t_null
t_number
t_string
t_array
t_object
t_raw
}
```
[[Return to contents]](#Contents)
## C.cJSON
```v
struct C.cJSON {
pub:
next &C.cJSON // next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem
prev &C.cJSON
child &C.cJSON // An array or object item will have a child pointer pointing to a chain of the items in the array/object
type CJsonType // The type of the item, as above
valueint int // writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead
valuedouble f64 // The item's number, if type==cJSON_Number
valuestring &char // The item's string, if type==cJSON_String and type == cJSON_Raw
// @string &char // The item's name string, if this item is the child of, or is in the list of subitems of an object
// TODO: `@string &char` from above does not work. It should be fixed, at least inside `struct C.`.
}
```
[[Return to contents]](#Contents)
#### Powered by vdoc. Generated on: 2 Sep 2025 07:37:38

View File

@@ -0,0 +1,48 @@
# module json
## Contents
- [decode](#decode)
- [encode](#encode)
- [encode_pretty](#encode_pretty)
- [C.cJSON](#C.cJSON)
## decode
```v
fn decode(typ voidptr, s string) !voidptr
```
decode tries to decode the provided JSON string, into a V structure. If it can not do that, it returns an error describing the reason for the parsing failure.
[[Return to contents]](#Contents)
## encode
```v
fn encode(x voidptr) string
```
encode serialises the provided V value as a JSON string, optimised for shortness.
[[Return to contents]](#Contents)
## encode_pretty
```v
fn encode_pretty(x voidptr) string
```
encode_pretty serialises the provided V value as a JSON string, in a formatted way, optimised for viewing by humans.
[[Return to contents]](#Contents)
## C.cJSON
```v
struct C.cJSON {
valueint int
valuedouble f64
valuestring &char
}
```
[[Return to contents]](#Contents)
#### Powered by vdoc. Generated on: 2 Sep 2025 07:37:38

View File

@@ -0,0 +1,100 @@
# module decoder2
## Contents
- [decode](#decode)
- [decode_array](#decode_array)
- [BooleanDecoder](#BooleanDecoder)
- [NullDecoder](#NullDecoder)
- [NumberDecoder](#NumberDecoder)
- [StringDecoder](#StringDecoder)
- [JsonDecodeError](#JsonDecodeError)
## decode
```v
fn decode[T](val string) !T
```
decode decodes a JSON string into a specified type.
[[Return to contents]](#Contents)
## decode_array
```v
fn decode_array[T](src string) !T
```
decode_array is a generic function that decodes a JSON string into the array target type.
[[Return to contents]](#Contents)
## BooleanDecoder
```v
interface BooleanDecoder {
mut:
// called with converted bool
// already checked so no error needed
from_json_boolean(boolean_value bool)
}
```
implements decoding json true/false
[[Return to contents]](#Contents)
## NullDecoder
```v
interface NullDecoder {
mut:
// only has one value
// already checked so no error needed
from_json_null()
}
```
implements decoding json null
[[Return to contents]](#Contents)
## NumberDecoder
```v
interface NumberDecoder {
mut:
// called with raw string of number e.g. '-1.234e23'
from_json_number(raw_number string) !
}
```
implements decoding json numbers, e.g. -1.234e23
[[Return to contents]](#Contents)
## StringDecoder
```v
interface StringDecoder {
mut:
// called with raw string (minus apostrophes) e.g. 'hello, \u2164!'
from_json_string(raw_string string) !
}
```
implements decoding json strings, e.g. "hello, \u2164!"
[[Return to contents]](#Contents)
## JsonDecodeError
```v
struct JsonDecodeError {
Error
context string
pub:
message string
line int
character int
}
```
[[Return to contents]](#Contents)
#### Powered by vdoc. Generated on: 2 Sep 2025 07:37:54

View File

@@ -0,0 +1,63 @@
# module strict
## Contents
- [get_keys_from_json](#get_keys_from_json)
- [strict_check](#strict_check)
- [KeyType](#KeyType)
- [KeyStruct](#KeyStruct)
- [StructCheckResult](#StructCheckResult)
## get_keys_from_json
```v
fn get_keys_from_json(tokens []string) []KeyStruct
```
get_keys_from_json .
[[Return to contents]](#Contents)
## strict_check
```v
fn strict_check[T](json_data string) StructCheckResult
```
strict_check .
[[Return to contents]](#Contents)
## KeyType
```v
enum KeyType {
literal
map
array
}
```
[[Return to contents]](#Contents)
## KeyStruct
```v
struct KeyStruct {
pub:
key string
value_type KeyType
token_pos int // the position of the token
}
```
[[Return to contents]](#Contents)
## StructCheckResult
```v
struct StructCheckResult {
pub:
duplicates []string
superfluous []string
}
```
[[Return to contents]](#Contents)
#### Powered by vdoc. Generated on: 2 Sep 2025 07:37:54

View File

@@ -0,0 +1,490 @@
# module x.json2
## Contents
- [Constants](#Constants)
- [decode](#decode)
- [decode_array](#decode_array)
- [encode](#encode)
- [encode_pretty](#encode_pretty)
- [fast_raw_decode](#fast_raw_decode)
- [map_from](#map_from)
- [raw_decode](#raw_decode)
- [Encodable](#Encodable)
- [Any](#Any)
- [arr](#arr)
- [as_map](#as_map)
- [as_map_of_strings](#as_map_of_strings)
- [bool](#bool)
- [f32](#f32)
- [f64](#f64)
- [i16](#i16)
- [i32](#i32)
- [i64](#i64)
- [i8](#i8)
- [int](#int)
- [json_str](#json_str)
- [prettify_json_str](#prettify_json_str)
- [str](#str)
- [to_time](#to_time)
- [u16](#u16)
- [u32](#u32)
- [u64](#u64)
- [u8](#u8)
- [Parser](#Parser)
- [decode](#decode)
- [[]Any](#[]Any)
- [str](#str)
- [map[string]Any](#map[string]Any)
- [str](#str)
- [DecodeError](#DecodeError)
- [code](#code)
- [msg](#msg)
- [Encoder](#Encoder)
- [encode_value](#encode_value)
- [InvalidTokenError](#InvalidTokenError)
- [code](#code)
- [msg](#msg)
- [Null](#Null)
- [from_json_null](#from_json_null)
- [Token](#Token)
- [full_col](#full_col)
- [UnknownTokenError](#UnknownTokenError)
- [code](#code)
- [msg](#msg)
## Constants
```v
const null = Null{}
```
null is an instance of the Null type, to ease comparisons with it.
[[Return to contents]](#Contents)
## decode
```v
fn decode[T](src string) !T
```
decode is a generic function that decodes a JSON string into the target type.
[[Return to contents]](#Contents)
## decode_array
```v
fn decode_array[T](src string) ![]T
```
decode_array is a generic function that decodes a JSON string into the array target type.
[[Return to contents]](#Contents)
## encode
```v
fn encode[T](val T) string
```
encode is a generic function that encodes a type into a JSON string.
[[Return to contents]](#Contents)
## encode_pretty
```v
fn encode_pretty[T](typed_data T) string
```
encode_pretty ...
[[Return to contents]](#Contents)
## fast_raw_decode
```v
fn fast_raw_decode(src string) !Any
```
Same with `raw_decode`, but skips the type conversion for certain types when decoding a certain value.
[[Return to contents]](#Contents)
## map_from
```v
fn map_from[T](t T) map[string]Any
```
map_from converts a struct to a map of Any.
[[Return to contents]](#Contents)
## raw_decode
```v
fn raw_decode(src string) !Any
```
Decodes a JSON string into an `Any` type. Returns an option.
[[Return to contents]](#Contents)
## Encodable
```v
interface Encodable {
json_str() string
}
```
Encodable is an interface, that allows custom implementations for encoding structs to their string based JSON representations.
[[Return to contents]](#Contents)
## Any
## arr
```v
fn (f Any) arr() []Any
```
arr uses `Any` as an array.
[[Return to contents]](#Contents)
## as_map
```v
fn (f Any) as_map() map[string]Any
```
as_map uses `Any` as a map.
[[Return to contents]](#Contents)
## as_map_of_strings
```v
fn (f Any) as_map_of_strings() map[string]string
```
[[Return to contents]](#Contents)
## bool
```v
fn (f Any) bool() bool
```
bool uses `Any` as a bool.
[[Return to contents]](#Contents)
## f32
```v
fn (f Any) f32() f32
```
f32 uses `Any` as a 32-bit float.
[[Return to contents]](#Contents)
## f64
```v
fn (f Any) f64() f64
```
f64 uses `Any` as a 64-bit float.
[[Return to contents]](#Contents)
## i16
```v
fn (f Any) i16() i16
```
i16 uses `Any` as a 16-bit integer.
[[Return to contents]](#Contents)
## i32
```v
fn (f Any) i32() i32
```
i32 uses `Any` as a 32-bit integer.
[[Return to contents]](#Contents)
## i64
```v
fn (f Any) i64() i64
```
i64 uses `Any` as a 64-bit integer.
[[Return to contents]](#Contents)
## i8
```v
fn (f Any) i8() i8
```
i8 uses `Any` as a 16-bit integer.
[[Return to contents]](#Contents)
## int
```v
fn (f Any) int() int
```
int uses `Any` as an integer.
[[Return to contents]](#Contents)
## json_str
```v
fn (f Any) json_str() string
```
json_str returns the JSON string representation of the `Any` type.
[[Return to contents]](#Contents)
## prettify_json_str
```v
fn (f Any) prettify_json_str() string
```
prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type.
[[Return to contents]](#Contents)
## str
```v
fn (f Any) str() string
```
str returns the string representation of the `Any` type. Use the `json_str` method. If you want to use the escaped str() version of the `Any` type.
[[Return to contents]](#Contents)
## to_time
```v
fn (f Any) to_time() !time.Time
```
to_time uses `Any` as a time.Time.
[[Return to contents]](#Contents)
## u16
```v
fn (f Any) u16() u16
```
u16 uses `Any` as a 16-bit unsigned integer.
[[Return to contents]](#Contents)
## u32
```v
fn (f Any) u32() u32
```
u32 uses `Any` as a 32-bit unsigned integer.
[[Return to contents]](#Contents)
## u64
```v
fn (f Any) u64() u64
```
u64 uses `Any` as a 64-bit unsigned integer.
[[Return to contents]](#Contents)
## u8
```v
fn (f Any) u8() u8
```
u8 uses `Any` as a 8-bit unsigned integer.
[[Return to contents]](#Contents)
## Parser
## decode
```v
fn (mut p Parser) decode() !Any
```
decode - decodes provided JSON
[[Return to contents]](#Contents)
## []Any
## str
```v
fn (f []Any) str() string
```
str returns the JSON string representation of the `[]Any` type.
[[Return to contents]](#Contents)
## map[string]Any
## str
```v
fn (f map[string]Any) str() string
```
str returns the JSON string representation of the `map[string]Any` type.
[[Return to contents]](#Contents)
## DecodeError
```v
struct DecodeError {
line int
column int
message string
}
```
[[Return to contents]](#Contents)
## code
```v
fn (err DecodeError) code() int
```
code returns the error code of DecodeError
[[Return to contents]](#Contents)
## msg
```v
fn (err DecodeError) msg() string
```
msg returns the message of the DecodeError
[[Return to contents]](#Contents)
## Encoder
```v
struct Encoder {
pub:
newline u8
newline_spaces_count int
escape_unicode bool = true
}
```
Encoder encodes the an `Any` type into JSON representation. It provides parameters in order to change the end result.
[[Return to contents]](#Contents)
## encode_value
```v
fn (e &Encoder) encode_value[T](val T, mut buf []u8) !
```
encode_value encodes a value to the specific buffer.
[[Return to contents]](#Contents)
## InvalidTokenError
```v
struct InvalidTokenError {
DecodeError
token Token
expected TokenKind
}
```
[[Return to contents]](#Contents)
## code
```v
fn (err InvalidTokenError) code() int
```
code returns the error code of the InvalidTokenError
[[Return to contents]](#Contents)
## msg
```v
fn (err InvalidTokenError) msg() string
```
msg returns the message of the InvalidTokenError
[[Return to contents]](#Contents)
## Null
```v
struct Null {
is_null bool = true
}
```
Null is a simple representation of the `null` value in JSON.
[[Return to contents]](#Contents)
## from_json_null
```v
fn (mut n Null) from_json_null()
```
from_json_null implements a custom decoder for json2
[[Return to contents]](#Contents)
## Token
```v
struct Token {
lit []u8 // literal representation of the token
kind TokenKind // the token number/enum; for quick comparisons
line int // the line in the source where the token occurred
col int // the column in the source where the token occurred
}
```
[[Return to contents]](#Contents)
## full_col
```v
fn (t Token) full_col() int
```
full_col returns the full column information which includes the length.
[[Return to contents]](#Contents)
## UnknownTokenError
```v
struct UnknownTokenError {
DecodeError
token Token
kind ValueKind = .unknown
}
```
[[Return to contents]](#Contents)
## code
```v
fn (err UnknownTokenError) code() int
```
code returns the error code of the UnknownTokenError
[[Return to contents]](#Contents)
## msg
```v
fn (err UnknownTokenError) msg() string
```
msg returns the error message of the UnknownTokenError
[[Return to contents]](#Contents)
#### Powered by vdoc. Generated on: 2 Sep 2025 07:37:54

View File

@@ -0,0 +1,27 @@
lets make an openrpc server
over unixsocker
on /tmp/heromodels
put code in lib/hero/heromodels/openrpc
do example for comment.v
make struct called RPCServer
put as methods
- comment_get(args CommentGetArgs)[]Comment! //chose the params well is @[params] struct CommentGetArgs always with id… in this case maybe author. …
- walk over the hset with data, find the one we are looking for based on the args
- comment_set(obj Comment)!
- comment_delete(id…)
- comment_list() ![]u32
- discover()!string //returns a full openrpc spec
make one .v file per type of object now comment_…
we will then do for the other objects too
also generate the openrpc spec based on the methods we have and the objects we return

View File

@@ -0,0 +1,130 @@
# HeroModels OpenRPC Server
This module provides an OpenRPC server for HeroModels that runs over Unix domain sockets. It exposes comment management functionality through a JSON-RPC 2.0 interface.
## Features
- **Unix Socket Communication**: Efficient local communication via Unix domain sockets
- **JSON-RPC 2.0 Protocol**: Standard JSON-RPC 2.0 implementation
- **Comment Management**: Full CRUD operations for comments
- **OpenRPC Specification**: Auto-generated OpenRPC spec via `discover` method
- **Concurrent Handling**: Multiple client connections supported
## API Methods
### comment_get
Retrieve comments by ID, author, or parent.
**Parameters:**
- `id` (optional): Comment ID to retrieve
- `author` (optional): Author ID to filter by
- `parent` (optional): Parent comment ID to filter by
**Returns:** Comment object or array of comments
### comment_set
Create a new comment.
**Parameters:**
- `comment`: Comment text content
- `parent`: Parent comment ID (0 for top-level)
- `author`: Author user ID
**Returns:** Object with created comment ID
### comment_delete
Delete a comment by ID.
**Parameters:**
- `id`: Comment ID to delete
**Returns:** Success status and deleted comment ID
### comment_list
List all comment IDs.
**Parameters:** None
**Returns:** Array of all comment IDs
### discover
Get the OpenRPC specification for this service.
**Parameters:** None
**Returns:** Complete OpenRPC specification object
## Usage
### Starting the Server
```v
import freeflowuniverse.herolib.hero.heromodels.openrpc
mut server := openrpc.new_rpc_server(socket_path: '/tmp/heromodels')!
server.start()! // Blocks and serves requests
```
### Example Client
```v
import net.unix
import json
import freeflowuniverse.herolib.hero.heromodels.openrpc
// Connect to server
mut conn := unix.connect_stream('/tmp/heromodels')!
// Create a comment
request := openrpc.JsonRpcRequest{
jsonrpc: '2.0'
method: 'comment_set'
params: json.encode({
'comment': 'Hello World'
'parent': 0
'author': 1
})
id: 1
}
// Send request
conn.write_string(json.encode(request))!
// Read response
mut buffer := []u8{len: 4096}
bytes_read := conn.read(mut buffer)!
response := buffer[..bytes_read].bytestr()
```
## Files
- `server.v` - Main RPC server implementation
- `types.v` - JSON-RPC and parameter type definitions
- `comment.v` - Comment-specific RPC method implementations
- `discover.v` - OpenRPC specification generation
- `example.vsh` - Server example script
- `client_example.vsh` - Client example script
## Running Examples
Start the server:
```bash
vrun lib/hero/heromodels/openrpc/example.vsh
```
Test with client (in another terminal):
```bash
vrun lib/hero/heromodels/openrpc/client_example.vsh
```
## Dependencies
- Redis (for data storage via heromodels)
- Unix domain socket support
- JSON encoding/decoding
## Socket Path
Default socket path: `/tmp/heromodels`
The socket file is automatically cleaned up when the server starts and stops.

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import net.unix
import x.json2
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.hero.heromodels.openrpc
// Example client to test the HeroModels OpenRPC server
fn main() {
console.print_header('HeroModels OpenRPC Client Example')
// Connect to the server
mut conn := unix.connect_stream('/tmp/heromodels')!
defer {
conn.close() or {}
}
console.print_item('Connected to server')
// Test 1: Get OpenRPC specification
console.print_header('Test 1: Discover OpenRPC Specification')
discover_request := openrpc.JsonRpcRequest{
jsonrpc: '2.0'
method: 'discover'
params: json2.null
id: json2.Any(1)
}
send_request(mut conn, discover_request)!
response := read_response(mut conn)!
console.print_item('OpenRPC Spec received: ${response.len} characters')
// Test 2: Create a comment
console.print_header('Test 2: Create Comment')
comment_json := '{"comment": "This is a test comment from OpenRPC client", "parent": 0, "author": 1}'
create_request := openrpc.JsonRpcRequest{
jsonrpc: '2.0'
method: 'comment_set'
params: json2.raw_decode(comment_json)!
id: json2.Any(2)
}
send_request(mut conn, create_request)!
create_response := read_response(mut conn)!
console.print_item('Comment created: ${create_response}')
// Test 3: List all comments
console.print_header('Test 3: List All Comments')
list_request := openrpc.JsonRpcRequest{
jsonrpc: '2.0'
method: 'comment_list'
params: json2.null
id: json2.Any(3)
}
send_request(mut conn, list_request)!
list_response := read_response(mut conn)!
console.print_item('Comment list: ${list_response}')
// Test 4: Get comment by author
console.print_header('Test 4: Get Comments by Author')
get_args_json := '{"author": 1}'
get_request := openrpc.JsonRpcRequest{
jsonrpc: '2.0'
method: 'comment_get'
params: json2.raw_decode(get_args_json)!
id: json2.Any(4)
}
send_request(mut conn, get_request)!
get_response := read_response(mut conn)!
console.print_item('Comments by author: ${get_response}')
console.print_header('All tests completed successfully!')
}
fn send_request(mut conn unix.StreamConn, request openrpc.JsonRpcRequest) ! {
request_json := json2.encode(request)
conn.write_string(request_json)!
}
fn read_response(mut conn unix.StreamConn) !string {
mut buffer := []u8{len: 8192}
bytes_read := conn.read(mut buffer)!
return buffer[..bytes_read].bytestr()
}

View File

@@ -0,0 +1,97 @@
module openrpc
import x.json2
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.core.redisclient
// comment_get retrieves comments based on the provided arguments
fn (mut server RPCServer) comment_get(params json2.Any) !json2.Any {
args := json2.decode[CommentGetArgs](params.json_str())!
// If ID is provided, get specific comment
if id := args.id {
comment := heromodels.comment_get(id)!
return json2.encode(comment)
}
// If author is provided, find comments by author
if author := args.author {
return server.get_comments_by_author(author)!
}
// If parent is provided, find child comments
if parent := args.parent {
return server.get_comments_by_parent(parent)!
}
return error('No valid search criteria provided. Please specify id, author, or parent.')
}
// comment_set creates or updates a comment
fn (mut server RPCServer) comment_set(params json2.Any) !json2.Any {
comment_arg := json2.decode[heromodels.CommentArg](params.json_str())!
id := heromodels.comment_set(comment_arg)!
return json2.encode({'id': id})
}
// comment_delete removes a comment by ID
fn (mut server RPCServer) comment_delete(params json2.Any) !json2.Any {
args := json2.decode[CommentDeleteArgs](params.json_str())!
// Check if comment exists
if !heromodels.comment_exist(args.id)! {
return error('Comment with id ${args.id} does not exist')
}
// Delete from Redis
mut redis := redisclient.core_get()!
redis.hdel('db:comments:data', args.id.str())!
result_json := '{"success": true, "id": ${args.id}}'
return json2.raw_decode(result_json)!
}
// comment_list returns all comment IDs
fn (mut server RPCServer) comment_list() !json2.Any {
mut redis := redisclient.core_get()!
keys := redis.hkeys('db:comments:data')!
mut ids := []u32{}
for key in keys {
ids << key.u32()
}
return json2.encode(ids)
}
// Helper function to get comments by author
fn (mut server RPCServer) get_comments_by_author(author u32) !json2.Any {
mut redis := redisclient.core_get()!
all_data := redis.hgetall('db:comments:data')!
mut matching_comments := []heromodels.Comment{}
for _, data in all_data {
comment := heromodels.comment_load(data.bytes())!
if comment.author == author {
matching_comments << comment
}
}
return json2.encode(matching_comments)
}
// Helper function to get comments by parent
fn (mut server RPCServer) get_comments_by_parent(parent u32) !json2.Any {
mut redis := redisclient.core_get()!
all_data := redis.hgetall('db:comments:data')!
mut matching_comments := []heromodels.Comment{}
for _, data in all_data {
comment := heromodels.comment_load(data.bytes())!
if comment.parent == parent {
matching_comments << comment
}
}
return json2.encode(matching_comments)
}

View File

@@ -0,0 +1,9 @@
module openrpc
import x.json2
// discover returns the OpenRPC specification for the HeroModels service
fn (mut server RPCServer) discover() !string {
spec_json := $tmpl("openrpc.json")
return spec_json
}

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.hero.heromodels.openrpc
import freeflowuniverse.herolib.hero.heromodels
import freeflowuniverse.herolib.ui.console
import time
import os
// Example usage of the HeroModels OpenRPC server
fn main() {
console.print_header('HeroModels OpenRPC Server Example')
// Create and start the server
mut server := openrpc.new_rpc_server(socket_path: '/tmp/heromodels')!
// Start server in a separate thread
spawn server.start()
console.print_item('Server started on /tmp/heromodels')
console.print_item('Press Ctrl+C to stop the server')
// Keep the main thread alive
for {
time.sleep(1 * time.second)
}
}

View File

@@ -0,0 +1,213 @@
{
"openrpc": "1.0.0-rc1",
"info": {
"version": "1.0.0",
"title": "HeroModels OpenRPC API",
"description": "OpenRPC API for HeroModels comment management over Unix socket",
"contact": {
"name": "HeroLib Team",
"url": "https://github.com/freeflowuniverse/herolib"
}
},
"servers": [
{
"name": "Unix Socket Server",
"url": "${server.socket_path}",
"description": "Unix domain socket server for HeroModels"
}
],
"methods": [
{
"name": "comment_get",
"description": "Retrieve comments by ID, author, or parent",
"params": [
{
"name": "args",
"description": "Comment search arguments",
"required": true,
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Comment ID to retrieve"
},
"author": {
"type": "integer",
"description": "Author ID to filter by"
},
"parent": {
"type": "integer",
"description": "Parent comment ID to filter by"
}
}
}
}
],
"result": {
"name": "comments",
"description": "Comment(s) matching the criteria",
"schema": {
"oneOf": [
{
"\\$ref": "#/components/schemas/Comment"
},
{
"type": "array",
"items": {
"\\$ref": "#/components/schemas/Comment"
}
}
]
}
}
},
{
"name": "comment_set",
"description": "Create a new comment",
"params": [
{
"name": "comment",
"description": "Comment data to create",
"required": true,
"schema": {
"\\$ref": "#/components/schemas/CommentArg"
}
}
],
"result": {
"name": "result",
"description": "Created comment ID",
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "ID of the created comment"
}
}
}
}
},
{
"name": "comment_delete",
"description": "Delete a comment by ID",
"params": [
{
"name": "args",
"description": "Comment deletion arguments",
"required": true,
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "ID of comment to delete",
"required": true
}
}
}
}
],
"result": {
"name": "result",
"description": "Deletion result",
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "integer"
}
}
}
}
},
{
"name": "comment_list",
"description": "List all comment IDs",
"params": [],
"result": {
"name": "ids",
"description": "Array of all comment IDs",
"schema": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
{
"name": "discover",
"description": "Get the OpenRPC specification for this service",
"params": [],
"result": {
"name": "spec",
"description": "OpenRPC specification",
"schema": {
"type": "object"
}
}
}
],
"components": {
"schemas": {
"Comment": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Unique comment identifier"
},
"comment": {
"type": "string",
"description": "Comment text content"
},
"parent": {
"type": "integer",
"description": "Parent comment ID (0 if top-level)"
},
"updated_at": {
"type": "integer",
"description": "Unix timestamp of last update"
},
"author": {
"type": "integer",
"description": "Author user ID"
}
},
"required": [
"id",
"comment",
"parent",
"updated_at",
"author"
]
},
"CommentArg": {
"type": "object",
"properties": {
"comment": {
"type": "string",
"description": "Comment text content"
},
"parent": {
"type": "integer",
"description": "Parent comment ID (0 if top-level)"
},
"author": {
"type": "integer",
"description": "Author user ID"
}
},
"required": [
"comment",
"author"
]
}
}
}
}

View File

@@ -0,0 +1,133 @@
module openrpc
import x.json2
import net.unix
import os
import freeflowuniverse.herolib.ui.console
pub struct RPCServer {
mut:
listener &unix.StreamListener
socket_path string
}
@[params]
pub struct RPCServerArgs {
pub mut:
socket_path string = '/tmp/heromodels'
}
pub fn new_rpc_server(args RPCServerArgs) !&RPCServer {
// Remove existing socket file if it exists
if os.exists(args.socket_path) {
os.rm(args.socket_path)!
}
listener := unix.listen_stream(args.socket_path, unix.ListenOptions{})!
return &RPCServer{
listener: listener
socket_path: args.socket_path
}
}
pub fn (mut server RPCServer) start() ! {
console.print_header('Starting HeroModels OpenRPC Server on ${server.socket_path}')
for {
mut conn := server.listener.accept()!
spawn server.handle_connection(mut conn)
}
}
pub fn (mut server RPCServer) close() ! {
server.listener.close()!
if os.exists(server.socket_path) {
os.rm(server.socket_path)!
}
}
fn (mut server RPCServer) handle_connection(mut conn unix.StreamConn) {
defer {
conn.close() or { console.print_stderr('Error closing connection: ${err}') }
}
for {
// Read JSON-RPC request
mut buffer := []u8{len: 4096}
bytes_read := conn.read(mut buffer) or {
console.print_debug('Connection closed or error reading: ${err}')
break
}
if bytes_read == 0 {
break
}
request_data := buffer[..bytes_read].bytestr()
console.print_debug('Received request: ${request_data}')
// Process the JSON-RPC request
response := server.process_request(request_data) or {
server.create_error_response(-32603, 'Internal error: ${err}', json2.null)
}
// Send response
conn.write_string(response) or {
console.print_stderr('Error writing response: ${err}')
break
}
}
}
fn (mut server RPCServer) process_request(request_data string) !string {
// Parse JSON-RPC request
request := json2.decode[JsonRpcRequest](request_data)!
// Route to appropriate method
result := match request.method {
'comment_get' {
server.comment_get(request.params)!
}
'comment_set' {
server.comment_set(request.params)!
}
'comment_delete' {
server.comment_delete(request.params)!
}
'comment_list' {
server.comment_list()!
}
'discover' {
server.discover()!
}
else {
return server.create_error_response(-32601, 'Method not found', request.id)
}
}
return server.create_success_response(result, request.id)
}
fn (mut server RPCServer) create_success_response(result json2.Any, id json2.Any) string {
response := JsonRpcResponse{
jsonrpc: '2.0'
result: result
id: id
}
return json2.encode(response)
}
fn (mut server RPCServer) create_error_response(code int, message string, id json2.Any) string {
error := JsonRpcError{
code: code
message: message
data: json2.null
}
response := JsonRpcResponse{
jsonrpc: '2.0'
error: error
id: id
}
return json2.encode(response)
}

View File

@@ -0,0 +1,44 @@
module openrpc
import x.json2
// JSON-RPC 2.0 request structure
pub struct JsonRpcRequest {
pub:
jsonrpc string = '2.0'
method string
params json2.Any
id json2.Any
}
// JSON-RPC 2.0 response structure
pub struct JsonRpcResponse {
pub:
jsonrpc string = '2.0'
result json2.Any
error ?JsonRpcError
id json2.Any
}
// JSON-RPC 2.0 error structure
pub struct JsonRpcError {
pub:
code int
message string
data json2.Any
}
// Comment-specific argument structures
@[params]
pub struct CommentGetArgs {
pub mut:
id ?u32
author ?u32
parent ?u32
}
@[params]
pub struct CommentDeleteArgs {
pub mut:
id u32
}