Compare commits

..

19 Commits

Author SHA1 Message Date
8965f7ae89 bump version to 1.0.10 2025-02-10 12:08:03 +03:00
9a931b65e2 bump version to 1.0.9 2025-02-10 09:42:52 +03:00
2c149507f6 fixes 2025-02-10 09:42:09 +03:00
timurgordon
34dea39c52 ... 2025-02-09 20:13:18 +00:00
timurgordon
f1a4547961 Merge branch 'development' of https://github.com/freeflowuniverse/herolib into development 2025-02-09 17:55:25 +00:00
timurgordon
8ae56a8df6 new 2025-02-09 17:53:16 +00:00
b731c4c388 bump version to 1.0.8 2025-02-09 17:45:34 +03:00
e929ce029d Merge branch 'development_fix' of github.com:freeflowuniverse/herolib into development_fix 2025-02-09 13:51:43 +01:00
5160096a1a ... 2025-02-09 13:51:40 +01:00
f219a4041a ... 2025-02-09 15:50:56 +03:00
674eae1c11 ... 2025-02-09 13:33:21 +01:00
f62369bd01 ... 2025-02-09 13:32:44 +01:00
7a6660ebd8 ... 2025-02-09 13:32:34 +01:00
e20d1bdcc5 traeffik first version, coredns 2025-02-09 13:32:11 +01:00
3e309b6379 ... 2025-02-09 12:24:12 +01:00
ae4e92e090 ... 2025-02-09 08:55:41 +01:00
7b69719f0e ... 2025-02-09 08:55:01 +01:00
1d631fec21 ... 2025-02-09 08:52:42 +01:00
690b1b68c3 bump version to 1.0.7 2025-02-08 15:17:47 +01:00
333 changed files with 8964 additions and 14294 deletions

187
aiprompts/reflection.md Normal file
View File

@@ -0,0 +1,187 @@
## Compile time reflection
$ is used as a prefix for compile time (also referred to as 'comptime') operations.
Having built-in JSON support is nice, but V also allows you to create efficient serializers for any data format. V has compile time if and for constructs:
.fields
You can iterate over struct fields using .fields, it also works with generic types (e.g. T.fields) and generic arguments (e.g. param.fields where fn gen[T](param T) {).
struct User {
name string
age int
}
fn main() {
$for field in User.fields {
$if field.typ is string {
println('${field.name} is of type string')
}
}
}
// Output:
// name is of type string
.values
You can read Enum values and their attributes.
enum Color {
red @[RED] // first attribute
blue @[BLUE] // second attribute
}
fn main() {
$for e in Color.values {
println(e.name)
println(e.attrs)
}
}
// Output:
// red
// ['RED']
// blue
// ['BLUE']
.attributes
You can read Struct attributes.
@[COLOR]
struct Foo {
a int
}
fn main() {
$for e in Foo.attributes {
println(e)
}
}
// Output:
// StructAttribute{
// name: 'COLOR'
// has_arg: false
// arg: ''
// kind: plain
// }
.variants
You can read variant types from Sum type.
type MySum = int | string
fn main() {
$for v in MySum.variants {
$if v.typ is int {
println('has int type')
} $else $if v.typ is string {
println('has string type')
}
}
}
// Output:
// has int type
// has string type
.methods
You can retrieve information about struct methods.
struct Foo {
}
fn (f Foo) test() int {
return 123
}
fn (f Foo) test2() string {
return 'foo'
}
fn main() {
foo := Foo{}
$for m in Foo.methods {
$if m.return_type is int {
print('${m.name} returns int: ')
println(foo.$method())
} $else $if m.return_type is string {
print('${m.name} returns string: ')
println(foo.$method())
}
}
}
// Output:
// test returns int: 123
// test2 returns string: foo
.params
You can retrieve information about struct method params.
struct Test {
}
fn (t Test) foo(arg1 int, arg2 string) {
}
fn main() {
$for m in Test.methods {
$for param in m.params {
println('${typeof(param.typ).name}: ${param.name}')
}
}
}
// Output:
// int: arg1
// string: arg2
## Example
```v
// An example deserializer implementation
struct User {
name string
age int
}
fn main() {
data := 'name=Alice\nage=18'
user := decode[User](data)
println(user)
}
fn decode[T](data string) T {
mut result := T{}
// compile-time `for` loop
// T.fields gives an array of a field metadata type
$for field in T.fields {
$if field.typ is string {
// $(string_expr) produces an identifier
result.$(field.name) = get_string(data, field.name)
} $else $if field.typ is int {
result.$(field.name) = get_int(data, field.name)
}
}
return result
}
fn get_string(data string, field_name string) string {
for line in data.split_into_lines() {
key_val := line.split('=')
if key_val[0] == field_name {
return key_val[1]
}
}
return ''
}
fn get_int(data string, field string) int {
return get_string(data, field).int()
}
// `decode<User>` generates:
// fn decode_User(data string) User {
// mut result := User{}
// result.name = get_string(data, 'name')
// result.age = get_int(data, 'age')
// return result
// }
```

2
cli/.gitignore vendored
View File

@@ -1 +1,3 @@
hero hero
compile
compile_upload

View File

@@ -19,6 +19,26 @@ fn playcmds_do(path string) ! {
} }
fn do() ! { fn do() ! {
if ! core.is_osx()! {
if os.getenv('SUDO_COMMAND') != '' || os.getenv('SUDO_USER') != '' {
println('Error: Please do not run this program with sudo!')
exit(1) // Exit with error code
}
}
if os.getuid() == 0 {
if core.is_osx()! {
eprintln("please do not run hero as root in osx.")
exit(1)
}
} else {
if ! core.is_osx()! {
eprintln("please do run hero as root, don't use sudo.")
exit(1)
}
}
if os.args.len == 2 { if os.args.len == 2 {
mypath := os.args[1] mypath := os.args[1]
if mypath.to_lower().ends_with('.hero') { if mypath.to_lower().ends_with('.hero') {
@@ -31,7 +51,7 @@ fn do() ! {
mut cmd := Command{ mut cmd := Command{
name: 'hero' name: 'hero'
description: 'Your HERO toolset.' description: 'Your HERO toolset.'
version: '1.0.11' version: '1.0.10'
} }
// herocmds.cmd_run_add_flags(mut cmd) // herocmds.cmd_run_add_flags(mut cmd)

View File

@@ -1,3 +0,0 @@
methods.v
pet_store_actor
docs

View File

@@ -1,9 +0,0 @@
# Actor Generation Examples
## `generate_methods.vsh`
This example generates actor method prototypes from an actor specification.
## `generate_actor_module.vsh`
This example generates an entire actor module from an actor specification with the support for the specified interfaces.

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import freeflowuniverse.herolib.baobab.generator
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openrpc
import os
const example_dir = os.dir(@FILE)
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
// the actor specification obtained from the OpenRPC Specification
openrpc_spec := openrpc.new(path: openrpc_spec_path)!
actor_spec := specification.from_openrpc(openrpc_spec)!
actor_module := generator.generate_actor_module(
actor_spec,
interfaces: [.openrpc]
)!
actor_module.write(example_dir,
format: true
overwrite: true
)!

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import freeflowuniverse.herolib.baobab.generator
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openrpc
import os
const example_dir = os.dir(@FILE)
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
// the actor specification obtained from the OpenRPC Specification
openrpc_spec := openrpc.new(path: openrpc_spec_path)!
actor_spec := specification.from_openrpc(openrpc_spec)!
methods_file := generator.generate_methods_file(actor_spec)!
methods_file.write(example_dir,
format: true
overwrite: true
)!

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import freeflowuniverse.herolib.baobab.generator
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openrpc
import os
const example_dir = os.dir(@FILE)
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
// the actor specification obtained from the OpenRPC Specification
openrpc_spec_ := openrpc.new(path: openrpc_spec_path)!
actor_spec := specification.from_openrpc(openrpc_spec_)!
openrpc_spec := actor_spec.to_openrpc()
openrpc_file := generator.generate_openrpc_file(openrpc_spec)!
openrpc_file.write(os.join_path(example_dir,'docs'),
overwrite: true
)!

View File

@@ -1,2 +0,0 @@
methods.v
meeting_scheduler_actor

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import freeflowuniverse.herolib.baobab.generator
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openapi
import os
const example_dir = os.dir(@FILE)
const openapi_spec_path = os.join_path(example_dir, 'openapi.json')
// the actor specification obtained from the OpenRPC Specification
openapi_spec := openapi.new(path: openapi_spec_path)!
actor_spec := specification.from_openapi(openapi_spec)!
actor_module := generator.generate_actor_module(
actor_spec,
interfaces: [.openapi, .http]
)!
actor_module.write(example_dir,
format: true
overwrite: true
compile: true
)!
os.execvp('bash', ['${example_dir}/meeting_scheduler_actor/scripts/run.sh'])!

View File

@@ -1,311 +0,0 @@
{
"openapi": "3.0.0",
"info": {
"title": "Meeting Scheduler",
"version": "1.0.0",
"description": "An API for managing meetings, availability, and scheduling."
},
"servers": [
{
"url": "http://localhost:8080/openapi/v1",
"description": "Production server"
},
{
"url": "http://localhost:8081/openapi/v1",
"description": "Example server"
}
],
"paths": {
"/users": {
"get": {
"summary": "List all users",
"responses": {
"200": {
"description": "A list of users",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
},
"example": [
{
"id": "1",
"name": "Alice",
"email": "alice@example.com"
},
{
"id": "2",
"name": "Bob",
"email": "bob@example.com"
}
]
}
}
}
}
}
},
"/users/{userId}": {
"get": {
"operationId": "get_user",
"summary": "Get user by ID",
"parameters": [
{
"name": "userId",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"format": "uint32"
},
"description": "The ID of the user",
"example": 1
}
],
"responses": {
"200": {
"description": "User details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
},
"example": {
"id": "1",
"name": "Alice",
"email": "alice@example.com"
}
}
}
},
"404": {
"description": "User not found"
}
}
}
},
"/events": {
"post": {
"summary": "Create an event",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Event"
},
"example": {
"title": "Team Meeting",
"description": "Weekly sync",
"startTime": "2023-10-10T10:00:00Z",
"endTime": "2023-10-10T11:00:00Z",
"userId": "1"
}
}
}
},
"responses": {
"201": {
"description": "Event created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Event"
},
"example": {
"id": "101",
"title": "Team Meeting",
"description": "Weekly sync",
"startTime": "2023-10-10T10:00:00Z",
"endTime": "2023-10-10T11:00:00Z",
"userId": "1"
}
}
}
}
}
}
},
"/availability": {
"get": {
"summary": "Get availability for a user",
"parameters": [
{
"name": "userId",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the user",
"example": "1"
},
{
"name": "date",
"in": "query",
"required": false,
"schema": {
"type": "string",
"format": "date"
},
"description": "The date to check availability (YYYY-MM-DD)",
"example": "2023-10-10"
}
],
"responses": {
"200": {
"description": "Availability details",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TimeSlot"
}
},
"example": [
{
"startTime": "10:00:00",
"endTime": "11:00:00",
"available": true
},
{
"startTime": "11:00:00",
"endTime": "12:00:00",
"available": false
}
]
}
}
}
}
}
},
"/bookings": {
"post": {
"summary": "Book a meeting",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Booking"
},
"example": {
"userId": "1",
"eventId": "101",
"timeSlot": {
"startTime": "10:00:00",
"endTime": "11:00:00",
"available": true
}
}
}
}
},
"responses": {
"201": {
"description": "Booking created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Booking"
},
"example": {
"id": "5001",
"userId": "1",
"eventId": "101",
"timeSlot": {
"startTime": "10:00:00",
"endTime": "11:00:00",
"available": true
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
}
}
},
"Event": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"startTime": {
"type": "string",
"format": "date-time"
},
"endTime": {
"type": "string",
"format": "date-time"
},
"userId": {
"type": "string"
}
}
},
"TimeSlot": {
"type": "object",
"properties": {
"startTime": {
"type": "string",
"format": "time"
},
"endTime": {
"type": "string",
"format": "time"
},
"available": {
"type": "boolean"
}
}
},
"Booking": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"userId": {
"type": "string"
},
"eventId": {
"type": "string"
},
"timeSlot": {
"$ref": "#/components/schemas/TimeSlot"
}
}
}
}
}
}

View File

@@ -1,132 +0,0 @@
{
"openrpc": "1.0.0",
"info": {
"title": "PetStore",
"version": "1.0.0"
},
"methods": [
{
"name": "GetPets",
"description": "finds pets in the system that the user has access to by tags and within a limit",
"params": [
{
"name": "tags",
"description": "tags to filter by",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "limit",
"description": "maximum number of results to return",
"schema": {
"type": "integer"
}
}
],
"result": {
"name": "pet_list",
"description": "all pets from the system, that mathes the tags",
"schema": {
"$ref": "#\/components\/schemas\/Pet"
}
}
},
{
"name": "CreatePet",
"description": "creates a new pet in the store. Duplicates are allowed.",
"params": [
{
"name": "new_pet",
"description": "Pet to add to the store.",
"schema": {
"$ref": "#\/components\/schemas\/NewPet"
}
}
],
"result": {
"name": "pet",
"description": "the newly created pet",
"schema": {
"$ref": "#\/components\/schemas\/Pet"
}
}
},
{
"name": "GetPetById",
"description": "gets a pet based on a single ID, if the user has access to the pet",
"params": [
{
"name": "id",
"description": "ID of pet to fetch",
"schema": {
"type": "integer"
}
}
],
"result": {
"name": "pet",
"description": "pet response",
"schema": {
"$ref": "#\/components\/schemas\/Pet"
}
}
},
{
"name": "DeletePetById",
"description": "deletes a single pet based on the ID supplied",
"params": [
{
"name": "id",
"description": "ID of pet to delete",
"schema": {
"type": "integer"
}
}
],
"result": {
"name": "pet",
"description": "pet deleted",
"schema": {
"type": "null"
}
}
}
],
"components": {
"schemas": {
"NewPet": {
"title": "NewPet",
"properties": {
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"Pet": {
"title": "Pet",
"description": "a pet struct that represents a pet",
"properties": {
"name": {
"description": "name of the pet",
"type": "string"
},
"tag": {
"description": "a tag of the pet, helps finding pet",
"type": "string"
},
"id": {
"description": "unique indentifier",
"type": "integer"
}
}
}
}
}
}

View File

@@ -1,3 +0,0 @@
# Actor Specification Examples
These examples show how `OpenRPC` and `OpenAPI` specifications can be translated back and forth into an `ActorSpecification`. This is an important step of actor generation as actor code is generated from actor specification.

View File

@@ -1,346 +0,0 @@
{
"openapi": "3.0.3",
"info": {
"title": "Pet Store API",
"description": "A sample API for a pet store",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.petstore.example.com/v1",
"description": "Production server"
},
{
"url": "https://staging.petstore.example.com/v1",
"description": "Staging server"
}
],
"paths": {
"/pets": {
"get": {
"summary": "List all pets",
"operationId": "listPets",
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of pets to return",
"required": false,
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "A paginated list of pets",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pets"
}
}
}
},
"400": {
"description": "Invalid request"
}
}
},
"post": {
"summary": "Create a new pet",
"operationId": "createPet",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NewPet"
}
}
}
},
"responses": {
"201": {
"description": "Pet created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"400": {
"description": "Invalid input"
}
}
}
},
"/pets/{petId}": {
"get": {
"summary": "Get a pet by ID",
"operationId": "getPet",
"parameters": [
{
"name": "petId",
"in": "path",
"description": "ID of the pet to retrieve",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "A pet",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"404": {
"description": "Pet not found"
}
}
},
"delete": {
"summary": "Delete a pet by ID",
"operationId": "deletePet",
"parameters": [
{
"name": "petId",
"in": "path",
"description": "ID of the pet to delete",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"204": {
"description": "Pet deleted"
},
"404": {
"description": "Pet not found"
}
}
}
},
"/orders": {
"get": {
"summary": "List all orders",
"operationId": "listOrders",
"responses": {
"200": {
"description": "A list of orders",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Order"
}
}
}
}
}
}
}
},
"/orders/{orderId}": {
"get": {
"summary": "Get an order by ID",
"operationId": "getOrder",
"parameters": [
{
"name": "orderId",
"in": "path",
"description": "ID of the order to retrieve",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "An order",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Order"
}
}
}
},
"404": {
"description": "Order not found"
}
}
},
"delete": {
"summary": "Delete an order by ID",
"operationId": "deleteOrder",
"parameters": [
{
"name": "orderId",
"in": "path",
"description": "ID of the order to delete",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"204": {
"description": "Order deleted"
},
"404": {
"description": "Order not found"
}
}
}
},
"/users": {
"post": {
"summary": "Create a user",
"operationId": "createUser",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NewUser"
}
}
}
},
"responses": {
"201": {
"description": "User created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"NewPet": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"Pets": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
},
"Order": {
"type": "object",
"required": ["id", "petId", "quantity", "shipDate"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"petId": {
"type": "integer",
"format": "int64"
},
"quantity": {
"type": "integer",
"format": "int32"
},
"shipDate": {
"type": "string",
"format": "date-time"
},
"status": {
"type": "string",
"enum": ["placed", "approved", "delivered"]
},
"complete": {
"type": "boolean"
}
}
},
"User": {
"type": "object",
"required": ["id", "username"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"username": {
"type": "string"
},
"email": {
"type": "string"
},
"phone": {
"type": "string"
}
}
},
"NewUser": {
"type": "object",
"required": ["username"],
"properties": {
"username": {
"type": "string"
},
"email": {
"type": "string"
},
"phone": {
"type": "string"
}
}
}
}
}
}

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openapi
import os
const example_dir = os.dir(@FILE)
const openapi_spec_path = os.join_path(example_dir, 'openapi.json')
// the actor specification obtained from the OpenRPC Specification
openapi_spec := openapi.new(path: openapi_spec_path)!
actor_specification := specification.from_openapi(openapi_spec)!
println(actor_specification)

View File

@@ -1,132 +0,0 @@
{
"openrpc": "1.0.0",
"info": {
"title": "PetStore",
"version": "1.0.0"
},
"methods": [
{
"name": "GetPets",
"description": "finds pets in the system that the user has access to by tags and within a limit",
"params": [
{
"name": "tags",
"description": "tags to filter by",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "limit",
"description": "maximum number of results to return",
"schema": {
"type": "integer"
}
}
],
"result": {
"name": "pet_list",
"description": "all pets from the system, that mathes the tags",
"schema": {
"$ref": "#\/components\/schemas\/Pet"
}
}
},
{
"name": "CreatePet",
"description": "creates a new pet in the store. Duplicates are allowed.",
"params": [
{
"name": "new_pet",
"description": "Pet to add to the store.",
"schema": {
"$ref": "#\/components\/schemas\/NewPet"
}
}
],
"result": {
"name": "pet",
"description": "the newly created pet",
"schema": {
"$ref": "#\/components\/schemas\/Pet"
}
}
},
{
"name": "GetPetById",
"description": "gets a pet based on a single ID, if the user has access to the pet",
"params": [
{
"name": "id",
"description": "ID of pet to fetch",
"schema": {
"type": "integer"
}
}
],
"result": {
"name": "pet",
"description": "pet response",
"schema": {
"$ref": "#\/components\/schemas\/Pet"
}
}
},
{
"name": "DeletePetById",
"description": "deletes a single pet based on the ID supplied",
"params": [
{
"name": "id",
"description": "ID of pet to delete",
"schema": {
"type": "integer"
}
}
],
"result": {
"name": "pet",
"description": "pet deleted",
"schema": {
"type": "null"
}
}
}
],
"components": {
"schemas": {
"NewPet": {
"title": "NewPet",
"properties": {
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"Pet": {
"title": "Pet",
"description": "a pet struct that represents a pet",
"properties": {
"name": {
"description": "name of the pet",
"type": "string"
},
"tag": {
"description": "a tag of the pet, helps finding pet",
"type": "string"
},
"id": {
"description": "unique indentifier",
"type": "integer"
}
}
}
}
}
}

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openrpc
import os
const example_dir = os.dir(@FILE)
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
// the actor specification obtained from the OpenRPC Specification
openrpc_spec := openrpc.new(path: openrpc_spec_path)!
actor_specification := specification.from_openrpc(openrpc_spec)!
println(actor_specification)

View File

@@ -1,107 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import json
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.jsonschema
import freeflowuniverse.herolib.schemas.openrpc
import os
const actor_specification = specification.ActorSpecification{
name: 'PetStore'
interfaces: [.openrpc]
methods: [
specification.ActorMethod{
name: 'GetPets'
description: 'finds pets in the system that the user has access to by tags and within a limit'
parameters: [
openrpc.ContentDescriptor{
name: 'tags'
description: 'tags to filter by'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}))
})
},
openrpc.ContentDescriptor{
name: 'limit'
description: 'maximum number of results to return'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet_list'
description: 'all pets from the system, that matches the tags'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
},
specification.ActorMethod{
name: 'CreatePet'
description: 'creates a new pet in the store. Duplicates are allowed.'
parameters: [
openrpc.ContentDescriptor{
name: 'new_pet'
description: 'Pet to add to the store.'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/NewPet'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'the newly created pet'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
},
specification.ActorMethod{
name: 'GetPetById'
description: 'gets a pet based on a single ID, if the user has access to the pet'
parameters: [
openrpc.ContentDescriptor{
name: 'id'
description: 'ID of pet to fetch'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'pet response'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
},
specification.ActorMethod{
name: 'DeletePetById'
description: 'deletes a single pet based on the ID supplied'
parameters: [
openrpc.ContentDescriptor{
name: 'id'
description: 'ID of pet to delete'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'pet deleted'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'null'
})
}
}
]
}
openapi_specification := actor_specification.to_openapi()
println(json.encode_pretty(openapi_specification))

View File

@@ -1,109 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import json
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.schemas.jsonschema
import freeflowuniverse.herolib.schemas.openrpc
import os
const actor_specification = specification.ActorSpecification{
name: 'PetStore'
structure: code.Struct{}
interfaces: [.openrpc]
methods: [
specification.ActorMethod{
name: 'GetPets'
description: 'finds pets in the system that the user has access to by tags and within a limit'
parameters: [
openrpc.ContentDescriptor{
name: 'tags'
description: 'tags to filter by'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}))
})
},
openrpc.ContentDescriptor{
name: 'limit'
description: 'maximum number of results to return'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet_list'
description: 'all pets from the system, that matches the tags'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
},
specification.ActorMethod{
name: 'CreatePet'
description: 'creates a new pet in the store. Duplicates are allowed.'
parameters: [
openrpc.ContentDescriptor{
name: 'new_pet'
description: 'Pet to add to the store.'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/NewPet'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'the newly created pet'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
},
specification.ActorMethod{
name: 'GetPetById'
description: 'gets a pet based on a single ID, if the user has access to the pet'
parameters: [
openrpc.ContentDescriptor{
name: 'id'
description: 'ID of pet to fetch'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'pet response'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
},
specification.ActorMethod{
name: 'DeletePetById'
description: 'deletes a single pet based on the ID supplied'
parameters: [
openrpc.ContentDescriptor{
name: 'id'
description: 'ID of pet to delete'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
})
}
]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'pet deleted'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'null'
})
}
}
]
}
openrpc_specification := actor_specification.to_openrpc()
println(json.encode_pretty(openrpc_specification))

108
examples/clients/mycelium.vsh Executable file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.mycelium
import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer
import freeflowuniverse.herolib.osal
import time
import os
import encoding.base64
const server1_port = 9001
const server2_port = 9002
fn terminate(port int) ! {
// Step 1: Run lsof to get process details
res := os.execute('lsof -i:${port}')
if res.exit_code != 0 {
return error('no service running at port ${port} due to: ${res.output}')
}
// Step 2: Parse the output to extract the PID
lines := res.output.split('\n')
if lines.len < 2 {
return error('no process found running on port ${port}')
}
// The PID is the second column in the output
fields := lines[1].split(' ')
if fields.len < 2 {
return error('failed to parse lsof output')
}
pid := fields[1]
// Step 3: Kill the process using the PID
kill_res := os.execute('kill ${pid}')
if kill_res.exit_code != 0 {
return error('failed to kill process ${pid}: ${kill_res.output}')
}
println('Successfully terminated process ${pid} running on port ${port}')
}
// Check if not installed install it.
mut installer := mycelium_installer.get()!
installer.install()!
mycelium.delete()!
spawn fn () {
os.execute('mkdir -p /tmp/mycelium_server1 && cd /tmp/mycelium_server1 && mycelium --peers tcp://188.40.132.242:9651 quic://[2a01:4f8:212:fa6::2]:9651 tcp://185.69.166.7:9651 quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651 tcp://65.21.231.58:9651 quic://[2a01:4f9:5a:1042::2]:9651 tcp://[2604:a00:50:17b:9e6b:ff:fe1f:e054]:9651 quic://5.78.122.16:9651 tcp://[2a01:4ff:2f0:3621::1]:9651 quic://142.93.217.194:9651 --tun-name tun2 --tcp-listen-port 9652 --quic-listen-port 9653 --api-addr 127.0.0.1:${server1_port}')
}()
spawn fn () {
os.execute('mkdir -p /tmp/mycelium_server2 && cd /tmp/mycelium_server2 && mycelium --peers tcp://188.40.132.242:9651 quic://[2a01:4f8:212:fa6::2]:9651 tcp://185.69.166.7:9651 quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651 tcp://65.21.231.58:9651 quic://[2a01:4f9:5a:1042::2]:9651 tcp://[2604:a00:50:17b:9e6b:ff:fe1f:e054]:9651 quic://5.78.122.16:9651 tcp://[2a01:4ff:2f0:3621::1]:9651 quic://142.93.217.194:9651 --tun-name tun3 --tcp-listen-port 9654 --quic-listen-port 9655 --api-addr 127.0.0.1:${server2_port}')
}()
defer {
terminate(server1_port) or {}
terminate(server2_port) or {}
}
time.sleep(2 * time.second)
mut client1 := mycelium.get()!
client1.server_url = 'http://localhost:${server1_port}'
client1.name = 'client1'
println(client1)
mut client2 := mycelium.get()!
client2.server_url = 'http://localhost:${server2_port}'
client2.name = 'client2'
println(client2)
inspect1 := mycelium.inspect(key_file_path: '/tmp/mycelium_server1/priv_key.bin')!
inspect2 := mycelium.inspect(key_file_path: '/tmp/mycelium_server2/priv_key.bin')!
println('Server 1 public key: ${inspect1.public_key}')
println('Server 2 public key: ${inspect2.public_key}')
// Send a message to a node by public key
// Parameters: public_key, payload, topic, wait_for_reply
msg := client1.send_msg(
public_key: inspect2.public_key // destination public key
payload: 'Sending a message from the client 1 to the client 2' // message payload
topic: 'testing' // optional topic
)!
println('Sent message ID: ${msg.id}')
println('send succeeded')
// Receive messages
// Parameters: wait_for_message, peek_only, topic_filter
received := client2.receive_msg(wait: true, peek: false, topic: 'testing')!
println('Received message from: ${received.src_pk}')
println('Message payload: ${base64.decode_str(received.payload)}')
// Reply to a message
// client1.reply_msg(
// id: received.id
// public_key: received.src_pk
// payload: 'Got your message!'
// topic: 'greetings'
// )!
// // // Check message status
// // status := client.get_msg_status(msg.id)!
// // println('Message status: ${status.state}')
// // println('Created at: ${status.created}')
// // println('Expires at: ${status.deadline}')

View File

@@ -7,7 +7,7 @@ import os
const testpath3 = os.dir(@FILE) + '/../..' const testpath3 = os.dir(@FILE) + '/../..'
// if we return True then it means the dir or file is processed // if we return True then it means the dir or file is processed
fn filter_1(mut path pathlib.Path) !bool { fn filter_1(mut path pathlib.Path, mut params paramsparser.Params) !bool {
if path.is_dir() { if path.is_dir() {
if path.path.ends_with('.dSYM') { if path.path.ends_with('.dSYM') {
return false return false

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal
import time
mut gs_default := gittools.new()!
println(gs_default)
// // Initializes the Git structure with the coderoot path.
// coderoot := '/tmp/code'
// mut gs_tmo := gittools.new(coderoot: coderoot)!
// // Retrieve the specified repository.
// mut repo := gs_default.get_repo(name: 'herolib')!
// println(repo)

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal
import time
mut gs := gittools.new()!
mydocs_path := gs.get_path(
pull: true
reset: false
url: 'https://git.ourworld.tf/tfgrid/info_docs_depin/src/branch/main/docs'
)!
println(mydocs_path)

View File

@@ -1,7 +1,7 @@
module example_actor module example_actor
import os import os
import freeflowuniverse.herolib.hero.baobab.stage {IActor, RunParams} import freeflowuniverse.herolib.hero.baobab.actor { IActor, RunParams }
import freeflowuniverse.herolib.web.openapi import freeflowuniverse.herolib.web.openapi
import time import time
@@ -10,13 +10,11 @@ const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) }
const openapi_specification = openapi.json_decode(openapi_spec_json)! const openapi_specification = openapi.json_decode(openapi_spec_json)!
struct ExampleActor { struct ExampleActor {
stage.Actor actor.Actor
} }
fn new() !ExampleActor { fn new() !ExampleActor {
return ExampleActor{ return ExampleActor{actor.new('example')}
stage.new_actor('example')
}
} }
pub fn run() ! { pub fn run() ! {

View File

@@ -70,74 +70,87 @@ fn (mut actor Actor) listen() ! {
// Handle method invocations // Handle method invocations
fn (mut actor Actor) handle_method(cmd string, data string) !string { fn (mut actor Actor) handle_method(cmd string, data string) !string {
param_anys := json2.raw_decode(data)!.arr() println('debugzo received rpc ${cmd}:${data}')
match cmd { param_anys := json2.raw_decode(data)!.arr()
'listPets' { match cmd {
pets := if param_anys.len == 0 { 'listPets' {
actor.data_store.list_pets() pets := if param_anys.len == 0 {
} else { actor.data_store.list_pets()
params := json.decode(ListPetParams, param_anys[0].str())! } else {
actor.data_store.list_pets(params) params := json.decode(ListPetParams, param_anys[0].str())!
} actor.data_store.list_pets(params)
return json.encode(pets) }
} return json.encode(pets)
'createPet' { }
response := if param_anys.len == 0 { 'createPet' {
return error('at least data expected') response := if param_anys.len == 0 {
} else if param_anys.len == 1 { return error('at least data expected')
payload := json.decode(NewPet, param_anys[0].str())! } else if param_anys.len == 1 {
actor.data_store.create_pet(payload) payload := json.decode(NewPet, param_anys[0].str())!
} else { actor.data_store.create_pet(payload)
return error('expected 1 param, found too many') } else {
} return error('expected 1 param, found too many')
// data := json.decode(NewPet, data) or { return error('Invalid pet data: $err') } }
// created_pet := actor.data_store.create_pet(pet) // data := json.decode(NewPet, data) or { return error('Invalid pet data: $err') }
return json.encode(response) // created_pet := actor.data_store.create_pet(pet)
} return json.encode(response)
'getPet' { }
response := if param_anys.len == 0 { 'getPet' {
return error('at least data expected') response := if param_anys.len == 0 {
} else if param_anys.len == 1 { return error('at least data expected')
payload := param_anys[0].int() } else if param_anys.len == 1 {
actor.data_store.get_pet(payload)! payload := param_anys[0].int()
} else { actor.data_store.get_pet(payload)!
return error('expected 1 param, found too many') } else {
} return error('expected 1 param, found too many')
}
return json.encode(response)
} return json.encode(response)
'deletePet' { }
params := json.decode(map[string]int, data) or { return error('Invalid params: $err') } 'deletePet' {
actor.data_store.delete_pet(params['petId']) or { return error('Pet not found: $err') } params := json.decode(map[string]int, data) or {
return json.encode({'message': 'Pet deleted'}) return error('Invalid params: ${err}')
} }
'listOrders' { actor.data_store.delete_pet(params['petId']) or {
orders := actor.data_store.list_orders() return error('Pet not found: ${err}')
return json.encode(orders) }
} return json.encode({
'getOrder' { 'message': 'Pet deleted'
params := json.decode(map[string]int, data) or { return error('Invalid params: $err') } })
order := actor.data_store.get_order(params['orderId']) or { }
return error('Order not found: $err') 'listOrders' {
} orders := actor.data_store.list_orders()
return json.encode(order) return json.encode(orders)
} }
'deleteOrder' { 'getOrder' {
params := json.decode(map[string]int, data) or { return error('Invalid params: $err') } params := json.decode(map[string]int, data) or {
actor.data_store.delete_order(params['orderId']) or { return error('Invalid params: ${err}')
return error('Order not found: $err') }
} order := actor.data_store.get_order(params['orderId']) or {
return json.encode({'message': 'Order deleted'}) return error('Order not found: ${err}')
} }
'createUser' { return json.encode(order)
user := json.decode(NewUser, data) or { return error('Invalid user data: $err') } }
created_user := actor.data_store.create_user(user) 'deleteOrder' {
return json.encode(created_user) params := json.decode(map[string]int, data) or {
} return error('Invalid params: ${err}')
else { }
return error('Unknown method: $cmd') actor.data_store.delete_order(params['orderId']) or {
} return error('Order not found: ${err}')
} }
return json.encode({
'message': 'Order deleted'
})
}
'createUser' {
user := json.decode(NewUser, data) or { return error('Invalid user data: ${err}') }
created_user := actor.data_store.create_user(user)
return json.encode(created_user)
}
else {
return error('Unknown method: ${cmd}')
}
}
} }
@[params] @[params]

View File

@@ -1,5 +1,8 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
import freeflowuniverse.herolib.osal
coredns_installer.install()! // coredns_installer.delete()!
mut installer := coredns_installer.get()!
installer.build()!

View File

@@ -1,5 +1,40 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer
import freeflowuniverse.herolib.clients.mycelium
mycelium_installer.start()! mut installer := mycelium_installer.get()!
installer.start()!
mut r := mycelium.inspect()!
println(r)
mut client := mycelium.get()!
// Send a message to a node by public key
// Parameters: public_key, payload, topic, wait_for_reply
msg := client.send_msg('abc123...', // destination public key
'Hello World', // message payload
'greetings', // optional topic
true // wait for reply
)!
println('Sent message ID: ${msg.id}')
// Receive messages
// Parameters: wait_for_message, peek_only, topic_filter
received := client.receive_msg(true, false, 'greetings')!
println('Received message from: ${received.src_pk}')
println('Message payload: ${received.payload}')
// Reply to a message
client.reply_msg(received.id, // original message ID
received.src_pk, // sender's public key
'Got your message!', // reply payload
'greetings' // topic
)!
// Check message status
status := client.get_msg_status(msg.id)!
println('Message status: ${status.state}')
println('Created at: ${status.created}')
println('Expires at: ${status.deadline}')

12
examples/installers/traefik.vsh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import os
import freeflowuniverse.herolib.installers.web.traefik as traefik_installer
traefik_installer.delete()!
mut installer := traefik_installer.get()!
installer.password = 'planet'
traefik_installer.set(installer)!
installer.start()!

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.sysadmintools.zinit as zinit_installer
mut installer := zinit_installer.get()!
installer.start()!

24
examples/osal/tun.vsh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tun
// Check if TUN is available
if available := tun.available() {
if available {
println('TUN is available on this system')
// Get a free TUN interface name
if interface_name := tun.free() {
println('Found free TUN interface: ${interface_name}')
// Example: Now you could use this interface name
// to set up your tunnel
} else {
println('Error finding free interface: ${err}')
}
} else {
println('TUN is not available on this system')
}
} else {
println('Error checking TUN availability: ${err}')
}

View File

@@ -1 +0,0 @@
client_typescript

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env -S v -n -w -enable-globals run
// Calendar Typescript Client Generation Example
// This example demonstrates how to generate a typescript client
// from a given OpenAPI Specification using the `openapi/codegen` module.
import os
import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.schemas.openapi.codegen
const dir = os.dir(@FILE)
const specification = openapi.new(path: '${dir}/meeting_api.json') or {
panic('this should never happen ${err}')
}
// generate typescript client folder and write it in dir
codegen.ts_client_folder(specification)!.write(dir, overwrite: true)!

View File

@@ -1,245 +0,0 @@
{
"openapi": "3.0.0",
"info": {
"title": "Meeting Scheduler API",
"version": "1.0.0",
"description": "An API for managing meetings, availability, and scheduling."
},
"servers": [
{
"url": "https://api.meetingscheduler.com/v1",
"description": "Production server"
},
{
"url": "https://sandbox.api.meetingscheduler.com/v1",
"description": "Sandbox server"
}
],
"paths": {
"/users": {
"get": {
"summary": "List all users",
"responses": {
"200": {
"description": "A list of users",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
}
}
},
"/users/{userId}": {
"get": {
"operationId": "get_user",
"summary": "Get user by ID",
"parameters": [
{
"name": "userId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the user"
}
],
"responses": {
"200": {
"description": "User details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"404": {
"description": "User not found"
}
}
}
},
"/events": {
"post": {
"summary": "Create an event",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Event"
}
}
}
},
"responses": {
"201": {
"description": "Event created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Event"
}
}
}
}
}
}
},
"/availability": {
"get": {
"summary": "Get availability for a user",
"parameters": [
{
"name": "userId",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the user"
},
{
"name": "date",
"in": "query",
"required": false,
"schema": {
"type": "string",
"format": "date"
},
"description": "The date to check availability (YYYY-MM-DD)"
}
],
"responses": {
"200": {
"description": "Availability details",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TimeSlot"
}
}
}
}
}
}
}
},
"/bookings": {
"post": {
"summary": "Book a meeting",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Booking"
}
}
}
},
"responses": {
"201": {
"description": "Booking created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Booking"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
}
}
},
"Event": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"startTime": {
"type": "string",
"format": "date-time"
},
"endTime": {
"type": "string",
"format": "date-time"
},
"userId": {
"type": "string"
}
}
},
"TimeSlot": {
"type": "object",
"properties": {
"startTime": {
"type": "string",
"format": "time"
},
"endTime": {
"type": "string",
"format": "time"
},
"available": {
"type": "boolean"
}
}
},
"Booking": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"userId": {
"type": "string"
},
"eventId": {
"type": "string"
},
"timeSlot": {
"$ref": "#/components/schemas/TimeSlot"
}
}
}
}
}
}

View File

@@ -1 +1,4 @@
buildah_example buildah_example
buildah_run_clean
buildah_run_mdbook
buildah_run

View File

@@ -16,13 +16,13 @@ podman_installer0.install()!
mut engine := herocontainers.new(install: true, herocompile: false)! mut engine := herocontainers.new(install: true, herocompile: false)!
engine.reset_all()! // engine.reset_all()!
mut builder_gorust := engine.builder_go_rust()! // mut builder_gorust := engine.builder_go_rust()!
// will build nodejs, python build & herolib, hero // will build nodejs, python build & herolib, hero
// mut builder_hero := engine.builder_hero(reset:true)! // mut builder_hero := engine.builder_hero(reset:true)!
// mut builder_web := engine.builder_heroweb(reset:true)! // mut builder_web := engine.builder_heroweb(reset:true)!
builder_gorust.shell()! // builder_gorust.shell()!

View File

@@ -7,14 +7,22 @@ import freeflowuniverse.herolib.core.base
import time import time
import os import os
mut pm := herocontainers.new(herocompile: true, install: false)! // herocompile means we do it for the host system
mut pm := herocontainers.new(herocompile: false, install: false)!
mut mybuildcontainer := pm.builder_get('builder_heroweb')! // pm.builder_base(reset:true)!
mut builder := pm.builder_get('base')!
builder.shell()!
println(builder)
// builder.install_zinit()!
// bash & python can be executed directly in build container // bash & python can be executed directly in build container
// any of the herocommands can be executed like this // any of the herocommands can be executed like this
mybuildcontainer.run(cmd: 'installers -n heroweb', runtime: .herocmd)! // mybuildcontainer.run(cmd: 'installers -n heroweb', runtime: .herocmd)!
// //following will execute heroscript in the buildcontainer // //following will execute heroscript in the buildcontainer
// mybuildcontainer.run( // mybuildcontainer.run(

View File

@@ -5,16 +5,15 @@ import freeflowuniverse.herolib.web.docusaurus
// Create a new docusaurus factory // Create a new docusaurus factory
mut docs := docusaurus.new( mut docs := docusaurus.new(
// build_path: '/tmp/docusaurus_build' build_path: '/tmp/docusaurus_build'
)! )!
// Create a new docusaurus site // Create a new docusaurus site
mut site := docs.dev( mut site := docs.dev(
url:'https://git.ourworld.tf/despiegk/docs_kristof' url: 'https://git.ourworld.tf/despiegk/docs_kristof'
)! )!
// FOR FUTURE TO ADD CONTENT FROM DOCTREE
//FOR FUTURE TO ADD CONTENT FROM DOCTREE
// Create a doctree for content // Create a doctree for content
// mut tree := doctree.new(name: 'content')! // mut tree := doctree.new(name: 'content')!
@@ -34,10 +33,10 @@ mut site := docs.dev(
// )! // )!
// Build the docusaurus site // Build the docusaurus site
//site.build()! // site.build()!
// Generate the static site // Generate the static site
//site.generate()! // site.generate()!
// Optionally open the site in a browser // Optionally open the site in a browser
// site.open()! // site.open()!

View File

@@ -4,7 +4,7 @@ set -e
os_name="$(uname -s)" os_name="$(uname -s)"
arch_name="$(uname -m)" arch_name="$(uname -m)"
version='1.0.11' version='1.0.10'
# Base URL for GitHub releases # Base URL for GitHub releases

View File

@@ -1,118 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code {Folder, File}
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.jsonschema.codegen { schema_to_struct }
import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen { content_descriptor_to_parameter }
import freeflowuniverse.herolib.baobab.specification {ActorSpecification, ActorMethod, BaseObject}
import net.http
// pub enum BaseObjectMethodType {
// new
// get
// set
// delete
// list
// other
// }
// pub struct BaseObjectMethod {
// pub:
// typ BaseObjectMethodType
// object string // the name of the base object
// }
// pub fn ts_client_get_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async get${name_pascal}(id: string): Promise<${name_pascal}> {\n return this.restClient.get<${name_pascal}>(`/${root}/${name_snake}/\${id}`);\n }"
// }
// pub fn ts_client_set_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async set${name_pascal}(id: string, ${name_snake}: Partial<${name_pascal}>): Promise<${name_pascal}> {\n return this.restClient.put<${name_pascal}>(`/${root}/${name_snake}/\${id}`, ${name_snake});\n }"
// }
// pub fn ts_client_delete_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async delete${name_pascal}(id: string): Promise<void> {\n return this.restClient.delete<void>(`/${root}/${name_snake}/\${id}`);\n }"
// }
// pub fn ts_client_list_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async list${name_pascal}(): Promise<${name_pascal}[]> {\n return this.restClient.get<${name_pascal}[]>(`/${root}/${name_snake}`);\n }"
// }
fn get_endpoint_root(root string) string {
return if root == '' {
''
} else {
"/${root.trim('/')}"
}
}
// // generates a Base Object's `create` method
// pub fn ts_client_new_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async create${name_snake}(object: Omit<${name_pascal}, 'id'>): Promise<${name_pascal}> {
// return this.restClient.post<${name_pascal}>('${root}/${name_snake}', board);
// }"
// }
// pub fn ts_client_get_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async get${name_pascal}(id: string): Promise<${name_pascal}> {\n return this.restClient.get<${name_pascal}>(`/${root}/${name_snake}/\${id}`);\n }"
// }
// pub fn ts_client_set_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async set${name_pascal}(id: string, ${name_snake}: Partial<${name_pascal}>): Promise<${name_pascal}> {\n return this.restClient.put<${name_pascal}>(`/${root}/${name_snake}/\${id}`, ${name_snake});\n }"
// }
// pub fn ts_client_delete_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async delete${name_pascal}(id: string): Promise<void> {\n return this.restClient.delete<void>(`/${root}/${name_snake}/\${id}`);\n }"
// }
// pub fn ts_client_list_fn(object string, params TSClientFunctionParams) string {
// name_snake := texttools.snake_case(object)
// name_pascal := texttools.name_fix_pascal(object)
// root := get_endpoint_root(params.endpoint)
// return "async list${name_pascal}(): Promise<${name_pascal}[]> {\n return this.restClient.get<${name_pascal}[]>(`/${root}/${name_snake}`);\n }"
// }
// // generates a function prototype given an `ActorMethod`
// pub fn ts_client_fn_prototype(method ActorMethod) string {
// name := texttools.name_fix_pascal(method.name)
// params := method.parameters
// .map(content_descriptor_to_parameter(it) or {panic(err)})
// .map(it.typescript())
// .join(', ')
// return_type := content_descriptor_to_parameter(method.result) or {panic(err)}.typ.typescript()
// return 'async ${name}(${params}): Promise<${return_type}> {}'
// }

View File

@@ -1,202 +0,0 @@
module generator
import x.json2 as json
import arrays
import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openrpc
import freeflowuniverse.herolib.schemas.jsonschema
const specification = specification.ActorSpecification{
name: 'Pet Store'
description: 'A sample API for a pet store'
structure: code.Struct{}
interfaces: [.openapi]
methods: [
specification.ActorMethod{
name: 'listPets'
summary: 'List all pets'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example limit'
description: 'Example Maximum number of pets to return'
value: 10
})
]
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: json.raw_decode('[
{"id": 1, "name": "Fluffy", "tag": "dog"},
{"id": 2, "name": "Whiskers", "tag": "cat"}
]')!
})
}
parameters: [
openrpc.ContentDescriptor{
name: 'limit'
summary: 'Maximum number of pets to return'
description: 'Maximum number of pets to return'
required: false
schema: jsonschema.SchemaRef(jsonschema.Schema{
...jsonschema.schema_u32,
example: 10
})
}
]
result: openrpc.ContentDescriptor{
name: 'pets'
description: 'A paged array of pets'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
id: 'pet'
title: 'Pet'
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}))
})
}
errors: [
openrpc.ErrorSpec{
code: 400
message: 'Invalid request'
}
]
},
specification.ActorMethod{
name: 'createPet'
summary: 'Create a new pet'
example: openrpc.ExamplePairing{
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: '[]'
})
}
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
}
errors: [
openrpc.ErrorSpec{
code: 400
message: 'Invalid input'
}
]
},
specification.ActorMethod{
name: 'getPet'
summary: 'Get a pet by ID'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example petId'
description: 'Example ID of the pet to retrieve'
value: 1
})
]
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: json.raw_decode('{"id": 1, "name": "Fluffy", "tag": "dog"}')!
})
}
parameters: [
openrpc.ContentDescriptor{
name: 'petId'
summary: 'ID of the pet to retrieve'
description: 'ID of the pet to retrieve'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
...jsonschema.schema_u32,
format:'uint32'
example: 1
})
}
]
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
errors: [
openrpc.ErrorSpec{
code: 404
message: 'Pet not found'
}
]
},
specification.ActorMethod{
name: 'deletePet'
summary: 'Delete a pet by ID'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example petId'
description: 'Example ID of the pet to delete'
value: 1
})
]
}
parameters: [
openrpc.ContentDescriptor{
name: 'petId'
summary: 'ID of the pet to delete'
description: 'ID of the pet to delete'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
...jsonschema.schema_u32,
example: 1
})
}
]
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
}
errors: [
openrpc.ErrorSpec{
code: 404
message: 'Pet not found'
}
]
}
]
objects: [
specification.BaseObject{
schema: jsonschema.Schema{
title: 'Pet'
typ: 'object'
properties: {
'id': jsonschema.schema_u32,
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}
}
]
}
fn test_typescript_client_folder() {
client := typescript_client_folder(specification)
}

View File

@@ -1,192 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code { Result, Object, Param, Folder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode }
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.openrpc {Example, ContentDescriptor}
import freeflowuniverse.herolib.schemas.jsonschema.codegen {schemaref_to_type}
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
fn generate_handle_example_file(spec ActorSpecification) !VFile {
mut items := []CodeItem{}
items << CustomCode{generate_handle_example_function(spec)}
for method in spec.methods {
items << generate_example_method_handle(spec.name, method)!
}
return VFile {
name: 'act'
imports: [
Import{mod:'freeflowuniverse.herolib.baobab.stage' types:['Action']}
Import{mod:'x.json2 as json'}
]
items: items
}
}
fn generate_handle_file(spec ActorSpecification) !VFile {
mut items := []CodeItem{}
items << CustomCode{generate_handle_function(spec)}
for method in spec.methods {
items << generate_method_handle(spec.name, method)!
}
return VFile {
name: 'act'
imports: [
Import{mod:'freeflowuniverse.herolib.baobab.stage' types:['Action']}
Import{mod:'x.json2 as json'}
]
items: items
}
}
pub fn generate_handle_function(spec ActorSpecification) string {
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
mut operation_handlers := []string{}
mut routes := []string{}
// Iterate over OpenAPI paths and operations
for method in spec.methods {
operation_id := method.name
params := method.parameters.map(it.name).join(', ')
// Generate route case
route := generate_route_case(operation_id, 'handle_${operation_id}')
routes << route
}
// Combine the generated handlers and main router into a single file
return [
'// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY',
'',
'pub fn (mut actor ${actor_name_pascal}Actor) act(action Action) !Action {',
' return match action.name {',
routes.join('\n'),
' else {',
' return error("Unknown operation: \${action.name}")',
' }',
' }',
'}',
].join('\n')
}
pub fn generate_handle_example_function(spec ActorSpecification) string {
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
mut operation_handlers := []string{}
mut routes := []string{}
// Iterate over OpenAPI paths and operations
for method in spec.methods {
operation_id := method.name
params := method.parameters.map(it.name).join(', ')
// Generate route case
route := generate_route_case(operation_id, 'handle_${operation_id}_example')
routes << route
}
// Combine the generated handlers and main router into a single file
return [
'// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY',
'',
'pub fn (mut actor ${actor_name_pascal}Actor) act(action Action) !Action {',
' return match action.name {',
routes.join('\n'),
' else {',
' return error("Unknown operation: \${action.name}")',
' }',
' }',
'}',
].join('\n')
}
pub fn generate_method_handle(actor_name string, method ActorMethod) !Function {
actor_name_pascal := texttools.snake_case_to_pascal(actor_name)
name_fixed := texttools.snake_case(method.name)
mut body := ''
if method.parameters.len == 1 {
param := method.parameters[0]
param_name := texttools.snake_case(param.name)
decode_stmt := generate_decode_stmt('action.params', param)!
body += '${param_name} := ${decode_stmt}\n'
}
if method.parameters.len > 1 {
body += 'params_arr := json.raw_decode(action.params)!.arr()\n'
for i, param in method.parameters {
param_name := texttools.snake_case(param.name)
decode_stmt := generate_decode_stmt('params_arr[${i}]', param)!
body += '${param_name} := ${decode_stmt}'
}
}
call_stmt := generate_call_stmt(method)!
body += '${call_stmt}\n'
body += '${generate_return_stmt(method)!}\n'
return Function {
name: 'handle_${name_fixed}'
description: '// Handler for ${name_fixed}\n'
receiver: Param{name: 'actor', mutable: true, typ: Object{'${actor_name_pascal}Actor'}}
params: [Param{name: 'action', typ: Object{'Action'}}]
result: Param{typ: Result{Object{'Action'}}}
body: body
}
}
fn method_is_void(method ActorMethod) !bool {
return schemaref_to_type(method.result.schema).vgen().trim_space() == ''
}
pub fn generate_example_method_handle(actor_name string, method ActorMethod) !Function {
actor_name_pascal := texttools.snake_case_to_pascal(actor_name)
name_fixed := texttools.snake_case(method.name)
body := if !method_is_void(method)! {
if method.example.result is Example {
'return Action{...action, result: json.encode(\'${method.example.result.value}\')}'
} else {
"return action"
}
} else { "return action" }
return Function {
name: 'handle_${name_fixed}_example'
description: '// Handler for ${name_fixed}\n'
receiver: Param{name: 'actor', mutable: true, typ: Object{'${actor_name_pascal}Actor'}}
params: [Param{name: 'action', typ: Object{'Action'}}]
result: Param{typ: Result{Object{'Action'}}}
body: body
}
}
fn generate_call_stmt(method ActorMethod) !string {
mut call_stmt := if schemaref_to_type(method.result.schema).vgen().trim_space() != '' {
'${texttools.snake_case(method.result.name)} := '
} else {''}
name_fixed := texttools.snake_case(method.name)
param_names := method.parameters.map(texttools.snake_case(it.name))
call_stmt += 'actor.${name_fixed}(${param_names.join(", ")})!'
return call_stmt
}
fn generate_return_stmt(method ActorMethod) !string {
if schemaref_to_type(method.result.schema).vgen().trim_space() != '' {
return 'return Action{...action, result: json.encode(${texttools.snake_case(method.result.name)})}'
}
return "return action"
}
// generates decode statement for variable with given name
fn generate_decode_stmt(name string, param ContentDescriptor) !string {
param_type := schemaref_to_type(param.schema)
if param_type is Object {
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})!'
}
// else if param.schema.typ == 'array' {
// return 'json2.decode[${schemaref_to_type(param.schema)!.vgen()}](${name})'
// }
param_symbol := param_type.vgen()
return if param_symbol == 'string' {
'${name}.str()'
} else {'${name}.${param_type.vgen()}()'}
}
// Helper function to generate a case block for the main router
fn generate_route_case(case string, handler_name string) string {
name_fixed := texttools.snake_case(handler_name)
return "'${texttools.snake_case(case)}' {actor.${name_fixed}(action)}"
}

View File

@@ -1,239 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code { Folder, IFolder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode }
import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification, ActorInterface}
import json
@[params]
pub struct Params {
pub:
interfaces []ActorInterface // the interfaces to be supported
}
pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
mut files := []IFile{}
mut folders := []IFolder{}
files = [
generate_readme_file(spec)!,
generate_actor_file(spec)!,
generate_actor_test_file(spec)!,
generate_specs_file(spec.name, params.interfaces)!,
generate_handle_file(spec)!,
generate_methods_file(spec)!
generate_client_file(spec)!
generate_model_file(spec)!
]
mut docs_files := []IFile{}
// generate code files for supported interfaces
for iface in params.interfaces {
match iface {
.openrpc {
// convert actor spec to openrpc spec
openrpc_spec := spec.to_openrpc()
// generate openrpc code files
// files << generate_openrpc_client_file(openrpc_spec)!
// files << generate_openrpc_client_test_file(openrpc_spec)!
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
files << iface_file
files << iface_test_file
// add openrpc.json to docs
// TODO
docs_files << generate_openrpc_file(openrpc_spec)!
}
.openapi {
// convert actor spec to openrpc spec
openapi_spec_raw := spec.to_openapi()
docs_files << generate_openapi_file(openapi_spec_raw)!
openapi_spec := openapi.process(openapi_spec_raw)!
// generate openrpc code files
iface_file, iface_test_file := generate_openapi_interface_files(params.interfaces)
files << iface_file
files << iface_test_file
// add openapi.json to docs
folders << generate_openapi_ts_client(openapi_spec)!
}
.http {
// interfaces that have http controllers
controllers := params.interfaces.filter(it == .openrpc || it == .openapi)
// generate openrpc code files
iface_file, iface_test_file := generate_http_interface_files(controllers)
files << iface_file
files << iface_test_file
}
.command {
files << generate_command_file(spec)!
}
else {
return error('unsupported interface ${iface}')
}
}
}
// folder with docs
folders << Folder {
name: 'docs'
files: docs_files
}
folders << generate_scripts_folder(spec.name, false)
folders << generate_examples_folder(spec, params)!
// create module with code files and docs folder
name_fixed := texttools.snake_case(spec.name)
return code.new_module(
name: '${name_fixed}_actor'
description: spec.description
files: files
folders: folders
in_src: true
)
}
fn generate_readme_file(spec ActorSpecification) !File {
return File{
name: 'README'
extension: 'md'
content: '# ${spec.name}\n${spec.description}'
}
}
fn generate_actor_file(spec ActorSpecification) !VFile {
dollar := '$'
actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
actor_code := $tmpl('./templates/actor.v.template')
return VFile {
name: 'actor'
items: [CustomCode{actor_code}]
}
}
fn generate_actor_example_file(spec ActorSpecification) !VFile {
dollar := '$'
actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
actor_code := $tmpl('./templates/actor_example.v.template')
return VFile {
name: 'actor_example'
items: [CustomCode{actor_code}]
}
}
fn generate_actor_test_file(spec ActorSpecification) !VFile {
dollar := '$'
actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
actor_test_code := $tmpl('./templates/actor_test.v.template')
return VFile {
name: 'actor_test'
items: [CustomCode{actor_test_code}]
}
}
fn generate_specs_file(name string, interfaces []ActorInterface) !VFile {
support_openrpc := ActorInterface.openrpc in interfaces
support_openapi := ActorInterface.openapi in interfaces
dollar := '$'
actor_name_snake := texttools.snake_case(name)
actor_name_pascal := texttools.snake_case_to_pascal(name)
actor_code := $tmpl('./templates/specifications.v.template')
return VFile {
name: 'specifications'
items: [CustomCode{actor_code}]
}
}
pub fn generate_examples_folder(spec ActorSpecification, params Params) !Folder {
return Folder {
name: 'examples'
modules: [generate_example_actor_module(spec, params)!]
}
}
pub fn generate_example_actor_module(spec ActorSpecification, params Params) !Module {
mut files := []IFile{}
mut folders := []IFolder{}
files = [
generate_readme_file(spec)!,
generate_actor_example_file(spec)!,
generate_specs_file(spec.name, params.interfaces)!,
generate_handle_example_file(spec)!,
generate_example_client_file(spec)!
generate_model_file(spec)!
]
mut docs_files := []IFile{}
// generate code files for supported interfaces
for iface in params.interfaces {
match iface {
.openrpc {
// convert actor spec to openrpc spec
openrpc_spec := spec.to_openrpc()
// generate openrpc code files
// files << generate_openrpc_client_file(openrpc_spec)!
// files << generate_openrpc_client_test_file(openrpc_spec)!
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
files << iface_file
files << iface_test_file
// add openrpc.json to docs
// TODO
docs_files << generate_openrpc_file(openrpc_spec)!
}
.openapi {
// convert actor spec to openrpc spec
openapi_spec := spec.to_openapi()
// generate openrpc code files
iface_file, iface_test_file := generate_openapi_interface_files(params.interfaces)
files << iface_file
files << iface_test_file
// add openapi.json to docs
docs_files << generate_openapi_file(openapi_spec)!
}
.http {
// interfaces that have http controllers
controllers := params.interfaces.filter(it == .openrpc || it == .openapi)
// generate openrpc code files
iface_file, iface_test_file := generate_http_interface_files(controllers)
files << iface_file
files << iface_test_file
}
.command {
files << generate_command_file(spec)!
}
else {
return error('unsupported interface ${iface}')
}
}
}
// folder with docs
folders << Folder {
name: 'docs'
files: docs_files
}
folders << generate_scripts_folder('example_${spec.name}', true)
// create module with code files and docs folder
name_fixed := texttools.snake_case(spec.name)
return code.new_module(
name: 'example_${name_fixed}_actor'
files: files
folders: folders
)
}

View File

@@ -1,273 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openrpc
import freeflowuniverse.herolib.schemas.jsonschema
import os
import x.json2 as json {Any}
const actor_spec = specification.ActorSpecification{
name: 'Pet Store'
description: 'A sample API for a pet store'
structure: code.Struct{}
interfaces: [.openapi]
methods: [
specification.ActorMethod{
name: 'listPets'
summary: 'List all pets'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example limit'
description: 'Example Maximum number of pets to return'
value: 10
})
]
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: json.raw_decode('[
{"id": 1, "name": "Fluffy", "tag": "dog"},
{"id": 2, "name": "Whiskers", "tag": "cat"}
]')!
})
}
parameters: [
openrpc.ContentDescriptor{
name: 'limit'
summary: 'Maximum number of pets to return'
description: 'Maximum number of pets to return'
required: false
schema: jsonschema.SchemaRef(jsonschema.Schema{
...jsonschema.schema_u32,
example: 10
})
}
]
result: openrpc.ContentDescriptor{
name: 'pets'
description: 'A paged array of pets'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
id: 'pet'
title: 'Pet'
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}))
})
}
errors: [
openrpc.ErrorSpec{
code: 400
message: 'Invalid request'
}
]
},
specification.ActorMethod{
name: 'newPet'
summary: 'Create a new pet'
parameters: [
openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
id: 'pet'
title: 'Pet'
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
})
}
]
example: openrpc.ExamplePairing{
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: '[]'
})
}
result: openrpc.ContentDescriptor{
name: 'petId'
summary: 'ID of the created pet'
description: 'ID of the created pet'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
...jsonschema.schema_u32,
example: 1
})
}
errors: [
openrpc.ErrorSpec{
code: 400
message: 'Invalid input'
}
]
},
specification.ActorMethod{
name: 'getPet'
summary: 'Get a pet by ID'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example petId'
description: 'Example ID of the pet to retrieve'
value: 1
})
]
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: json.raw_decode('{"id": 1, "name": "Fluffy", "tag": "dog"}')!
})
}
parameters: [
openrpc.ContentDescriptor{
name: 'petId'
summary: 'ID of the pet to retrieve'
description: 'ID of the pet to retrieve'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
...jsonschema.schema_u32,
format:'uint32'
example: 1
})
}
]
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
errors: [
openrpc.ErrorSpec{
code: 404
message: 'Pet not found'
}
]
},
specification.ActorMethod{
name: 'deletePet'
summary: 'Delete a pet by ID'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example petId'
description: 'Example ID of the pet to delete'
value: 1
})
]
}
parameters: [
openrpc.ContentDescriptor{
name: 'petId'
summary: 'ID of the pet to delete'
description: 'ID of the pet to delete'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
...jsonschema.schema_u32,
example: 1
})
}
]
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
}
errors: [
openrpc.ErrorSpec{
code: 404
message: 'Pet not found'
}
]
}
]
objects: [
specification.BaseObject{
schema: jsonschema.Schema{
title: 'Pet'
typ: 'object'
properties: {
'id': jsonschema.schema_u32,
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}
}
]
}
const destination = '${os.dir(@FILE)}/testdata'
fn test_generate_plain_actor_module() {
// plain actor module without interfaces
actor_module := generate_actor_module(actor_spec)!
actor_module.write(destination,
format: true
overwrite: true
test: true
)!
}
fn test_generate_actor_module_with_openrpc_interface() {
// plain actor module without interfaces
actor_module := generate_actor_module(actor_spec, interfaces: [.openrpc])!
actor_module.write(destination,
format: true
overwrite: true
test: true
)!
}
fn test_generate_actor_module_with_openapi_interface() {
// plain actor module without interfaces
actor_module := generate_actor_module(actor_spec,
interfaces: [.openapi]
)!
actor_module.write(destination,
format: true
overwrite: true
test: true
)!
}
fn test_generate_actor_module_with_all_interfaces() {
// plain actor module without interfaces
actor_module := generate_actor_module(actor_spec,
interfaces: [.openapi, .openrpc, .http]
)!
actor_module.write(destination,
format: true
overwrite: true
test: true
)!
}

View File

@@ -1,135 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code { Param, Folder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode, Result }
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schemaref_to_type}
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter}
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
pub fn generate_client_file(spec ActorSpecification) !VFile {
actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
mut items := []CodeItem{}
items << CustomCode {'
pub struct Client {
stage.Client
}
fn new_client() !Client {
mut redis := redisclient.new(\'localhost:6379\')!
mut rpc_q := redis.rpc_get(\'actor_\${name}\')
return Client{
rpc: rpc_q
}
}'}
for method in spec.methods {
items << generate_client_method(method)!
}
return VFile {
imports: [
Import{
mod: 'freeflowuniverse.herolib.baobab.stage'
},
Import{
mod: 'freeflowuniverse.herolib.core.redisclient'
},
Import{
mod: 'x.json2 as json'
types: ['Any']
}
]
name: 'client'
items: items
}
}
pub fn generate_example_client_file(spec ActorSpecification) !VFile {
actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
mut items := []CodeItem{}
items << CustomCode {'
pub struct Client {
stage.Client
}
fn new_client() !Client {
mut redis := redisclient.new(\'localhost:6379\')!
mut rpc_q := redis.rpc_get(\'actor_example_\${name}\')
return Client{
rpc: rpc_q
}
}'}
for method in spec.methods {
items << generate_client_method(method)!
}
return VFile {
imports: [
Import{
mod: 'freeflowuniverse.herolib.baobab.stage'
},
Import{
mod: 'freeflowuniverse.herolib.core.redisclient'
},
Import{
mod: 'x.json2 as json'
types: ['Any']
}
]
name: 'client'
items: items
}
}
pub fn generate_client_method(method ActorMethod) !Function {
name_fixed := texttools.snake_case(method.name)
call_params := if method.parameters.len > 0 {
method.parameters.map(texttools.snake_case(it.name)).map('Any(${it})').join(', ')
} else {''}
params_stmt := if method.parameters.len == 0 {
''
} else if method.parameters.len == 1 {
'params := json.encode(${texttools.snake_case(method.parameters[0].name)})'
} else {
'mut params_arr := []Any{}
params_arr = [${call_params}]
params := json.encode(params_arr.str())
'
}
mut client_call_stmt := "action := client.call_to_action(
name: '${name_fixed}'"
if params_stmt != '' {
client_call_stmt += 'params: params'
}
client_call_stmt += ')!'
result_type := schemaref_to_type(method.result.schema).vgen().trim_space()
result_stmt := if result_type == '' {
''
} else {
"return json.decode[${result_type}](action.result)!"
}
result_param := content_descriptor_to_parameter(method.result)!
return Function {
receiver: code.new_param(v: 'mut client Client')!
result: Param{...result_param, typ: Result{result_param.typ}}
name: name_fixed
body: '${params_stmt}\n${client_call_stmt}\n${result_stmt}'
summary: method.summary
description: method.description
params: method.parameters.map(content_descriptor_to_parameter(it)!)
}
}

View File

@@ -1,48 +0,0 @@
module generator
import freeflowuniverse.herolib.baobab.specification {ActorInterface}
import freeflowuniverse.herolib.core.code { Folder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode }
fn generate_openrpc_interface_files(interfaces []ActorInterface) (VFile, VFile) {
http := ActorInterface.http in interfaces
iface_file := VFile {
name: 'interface_openrpc'
items: [CustomCode{$tmpl('./templates/interface_openrpc.v.template')}]
}
iface_test_file := VFile {
name: 'interface_openrpc_test'
items: [CustomCode{$tmpl('./templates/interface_openrpc_test.v.template')}]
}
return iface_file, iface_test_file
}
fn generate_openapi_interface_files(interfaces []ActorInterface) (VFile, VFile) {
http := ActorInterface.http in interfaces
iface_file := VFile {
name: 'interface_openapi'
items: [CustomCode{$tmpl('./templates/interface_openapi.v.template')}]
}
iface_test_file := VFile {
name: 'interface_openapi_test'
items: [CustomCode{$tmpl('./templates/interface_openapi_test.v.template')}]
}
return iface_file, iface_test_file
}
fn generate_http_interface_files(controllers []ActorInterface) (VFile, VFile) {
dollar := '$'
openapi := ActorInterface.openapi in controllers
openrpc := ActorInterface.openrpc in controllers
iface_file := VFile {
name: 'interface_http'
items: [CustomCode{$tmpl('./templates/interface_http.v.template')}]
}
iface_test_file := VFile {
name: 'interface_http_test'
items: [CustomCode{$tmpl('./templates/interface_http_test.v.template')}]
}
return iface_file, iface_test_file
}

View File

@@ -1,75 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result }
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter}
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
const crud_prefixes = ['new', 'get', 'set', 'delete', 'list']
pub fn generate_methods_file(spec ActorSpecification) !VFile {
actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
mut items := []CodeItem{}
for method in spec.methods {
method_fn := generate_method_function(spec.name, method)!
// check if method is a Base Object CRUD Method and
// if so generate the method's body
body := match spec.method_type(method) {
.base_object_new { base_object_new_body(method)! }
.base_object_get { base_object_get_body(method)! }
.base_object_set { base_object_set_body(method)! }
.base_object_delete { base_object_delete_body(method)! }
.base_object_list { base_object_list_body(method)! }
else {"panic('implement')"}
}
items << Function{...method_fn, body: body}
}
return VFile {
name: 'methods'
items: items
}
}
// returns bodyless method prototype
pub fn generate_method_function(actor_name string, method ActorMethod) !Function {
actor_name_pascal := texttools.snake_case_to_pascal(actor_name)
result_param := content_descriptor_to_parameter(method.result)!
return Function{
name: texttools.snake_case(method.name)
receiver: code.new_param(v: 'mut actor ${actor_name_pascal}Actor')!
result: Param {...result_param, typ: Result{result_param.typ}}
summary: method.summary
description: method.description
params: method.parameters.map(content_descriptor_to_parameter(it)!)
}
}
fn base_object_new_body(method ActorMethod) !string {
parameter := content_descriptor_to_parameter(method.parameters[0])!
return 'return actor.osis.new[${parameter.typ.vgen()}](${texttools.snake_case(parameter.name)})!'
}
fn base_object_get_body(method ActorMethod) !string {
parameter := content_descriptor_to_parameter(method.parameters[0])!
result := content_descriptor_to_parameter(method.result)!
return 'return actor.osis.get[${result.typ.vgen()}](${texttools.snake_case(parameter.name)})!'
}
fn base_object_set_body(method ActorMethod) !string {
parameter := content_descriptor_to_parameter(method.parameters[0])!
return 'return actor.osis.set[${parameter.typ.vgen()}](${parameter.name})!'
}
fn base_object_delete_body(method ActorMethod) !string {
parameter := content_descriptor_to_parameter(method.parameters[0])!
return 'actor.osis.delete(${texttools.snake_case(parameter.name)})!'
}
fn base_object_list_body(method ActorMethod) !string {
result := content_descriptor_to_parameter(method.result)!
base_object_type := (result.typ as Array).typ
return 'return actor.osis.list[${base_object_type.symbol()}]()!'
}

View File

@@ -1,19 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code { Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode }
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.jsonschema.codegen {schema_to_struct}
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
pub fn generate_model_file(spec ActorSpecification) !VFile {
actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
return VFile {
name: 'model'
items: spec.objects.map(CodeItem(
Struct {...schema_to_struct(it.schema)
is_pub: true
}))
}
}

View File

@@ -1,158 +0,0 @@
module generator
import json
import freeflowuniverse.herolib.core.code { VFile, File, Folder, Function, Module, Struct }
import freeflowuniverse.herolib.schemas.openapi { Components, OpenAPI, Operation }
import freeflowuniverse.herolib.schemas.openapi.codegen
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schema_to_type}
import net.http
pub fn generate_openapi_file(specification OpenAPI) !File {
openapi_json := specification.encode_json()
return File{
name: 'openapi'
extension: 'json'
content: openapi_json
}
}
pub fn generate_openapi_ts_client(specification OpenAPI) !Folder {
return codegen.ts_client_folder(specification,
body_generator: body_generator
custom_client_code: ' private restClient: HeroRestClient;
constructor(heroKeysClient: any, debug: boolean = true) {
this.restClient = new HeroRestClient(heroKeysClient, debug);
}
'
)!
}
fn body_generator(op openapi.Operation, path_ string, method http.Method) string {
path := path_.replace('{','\${')
return match method {
.post {
if schema := op.payload_schema() {
symbol := schema_to_type(schema).typescript()
"return this.restClient.post<${symbol}>('${path}', data);"
} else {''}
}
.get {
if schema := op.response_schema() {
// if op.params.len
symbol := schema_to_type(schema).typescript()
"return this.restClient.get<${symbol}>('${path}', data);"
} else {''}
} else {''}
}
// return if operation_is_base_object_method(op) {
// bo_method := operation_to_base_object_method(op)
// match method_type(op) {
// .new { ts_client_new_body(op, path) }
// .get { ts_client_get_body(op, path) }
// .set { ts_client_set_body(op, path) }
// .delete { ts_client_delete_body(op, path) }
// .list { ts_client_list_body(op, path) }
// else {''}
// }
// } else {''}
}
// pub fn operation_is_base_object_method(op openapi.Operation, base_objs []string) BaseObjectMethod {
// // name := texttools.pascal_case(op.operation_id)
// // if op.operation_id.starts_with('new') {
// // if op.&& operation.params.len == 1
// return true
// }
// pub fn operation_to_base_object_method(op openapi.Operation) BaseObjectMethod {
// if op.operation_id.starts_with('update')
// }
// pub fn openapi_ts_client_body(op openapi.Operation, path string, method http.Method) string {
// match method {
// post {
// if schema := op.payload_schema() {
// symbol := schema_to_type(schema).typescript()
// return "return this.restClient.post<${symbol}>('${path}', data);"
// }
// }
// }
// return if operation_is_base_object_method(op) {
// bo_method := operation_to_base_object_method(op)
// match bo_method. {
// .new { ts_client_new_body(op, path) }
// .get { ts_client_get_body(op, path) }
// .set { ts_client_set_body(op, path) }
// .delete { ts_client_delete_body(op, path) }
// .list { ts_client_list_body(op, path) }
// else {''}
// }
// } else {''}
// }
fn get_endpoint(path string) string {
return if path == '' {
''
} else {
"/${path.trim('/')}"
}
}
// // generates a Base Object's `create` method
// fn ts_client_new_body(op Operation, path string) string {
// // the parameter of a base object new method is always the base object
// bo_param := openapi_codegen.parameter_to_param(op.parameters[0])
// return "return this.restClient.post<${bo_param.typ.typescript()}>('${get_endpoint(path)}', ${bo_param.name});"
// }
// // generates a Base Object's `create` method
// fn ts_client_get_body(op Operation, path string) string {
// // the parameter of a base object get method is always the id
// id_param := openapi_codegen.parameter_to_param(op.parameters[0])
// return "return this.restClient.get<${id_param.typ.typescript()}>('${get_endpoint(path)}', ${id_param.name});"
// }
// // generates a Base Object's `create` method
// fn ts_client_set_body(op Operation, path string) string {
// // the parameter of a base object set method is always the base object
// bo_param := openapi_codegen.parameter_to_param(op.parameters[0])
// return "return this.restClient.put<${bo_param.typ.typescript()}>('${get_endpoint(path)}', ${bo_param.name});"
// }
// // generates a Base Object's `delete` method
// fn ts_client_delete_body(op Operation, path string) string {
// // the parameter of a base object delete method is always the id
// id_param := openapi_codegen.parameter_to_param(op.parameters[0])
// return "return this.restClient.get<${id_param.typ.typescript()}>('${get_endpoint(path)}', ${id_param.name});"
// }
// // generates a Base Object's `list` method
// fn ts_client_list_body(op Operation, path string) string {
// // the result parameter of a base object list method is always the array of bo
// result_param := openapi_codegen.parameter_to_param(op.parameters[0])
// return "return this.restClient.get<${result_param.typ.typescript()}>('${get_endpoint(path)}');"
// }
// pub enum BaseObjectMethodType {
// new
// get
// set
// delete
// list
// other
// }
// pub struct BaseObjectMethod {
// pub:
// typ BaseObjectMethodType
// object string // the name of the base object
// }

View File

@@ -1,88 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code { Folder, File }
import freeflowuniverse.herolib.core.texttools
// generates the folder with runnable scripts of the actor
pub fn generate_scripts_folder(name string, example bool) Folder {
actor_name := '${texttools.snake_case(name)}_actor'
return Folder {
name: 'scripts'
files: [
generate_run_script(actor_name),
generate_docs_script(actor_name),
generate_run_actor_script(actor_name),
// generate_run_example_actor_script(actor_name),
generate_run_http_server_script(actor_name, example),
// generate_compile_script(actor_name),
// generate_generate_script(actor_name)
]
}
}
// Function to generate a script for running an actor
fn generate_run_script(actor_name string) File {
actor_title := texttools.title_case(actor_name)
dollar := '$'
return File{
name: 'run'
extension:'sh'
content: $tmpl('./templates/run.sh.template')
}
}
// Function to generate a script for running an actor
fn generate_docs_script(actor_name string) File {
dollar := '$'
return File{
name: 'docs'
extension:'vsh'
content: $tmpl('./templates/docs.vsh.template')
}
}
// Function to generate a script for running an actor
fn generate_run_actor_script(actor_name string) File {
return File{
name: 'run_actor'
extension:'vsh'
content: $tmpl('./templates/run_actor.vsh.template')
}
}
// Function to generate a script for running an example actor
fn generate_run_example_actor_script(actor_name string) File {
return File{
name: 'run_example_actor'
extension:'vsh'
content: $tmpl('./templates/run_example_actor.vsh.template')
}
}
// Function to generate a script for running an HTTP server
fn generate_run_http_server_script(actor_name string, example bool) File {
port := if example {8081} else {8080}
return File{
name: 'run_http_server'
extension:'vsh'
content: $tmpl('./templates/run_http_server.vsh.template')
}
}
// // Function to generate a script for compiling the project
// fn generate_compile_script(actor_name string) File {
// return File{
// name: 'compile'
// extension:'sh'
// content: $tmpl('./templates/run_http_server.vsh.template')
// }
// }
// // Function to generate a script for general generation tasks
// fn generate_generate_script(actor_name string) File {
// return File{
// name: 'generate'
// extension: 'vsh'
// content: $tmpl('./templates/run_http_server.vsh.template')
// }
// }

View File

@@ -1,37 +0,0 @@
import os
import freeflowuniverse.herolib.baobab.stage
import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.schemas.openapi
const name = '@{actor_name_snake}'
@@[heap]
struct @{actor_name_pascal}Actor {
stage.Actor
}
pub fn new() !&@{actor_name_pascal}Actor {
return &@{actor_name_pascal}Actor {
Actor: stage.new_actor('@{actor_name_snake}')!
}
}
pub fn (mut a @{actor_name_pascal}Actor) handle(method string, data string) !string {
action := a.act(
name: method
params: data
)!
return action.result
}
// Actor listens to the Redis queue for method invocations
pub fn (mut a @{actor_name_pascal}Actor) run() ! {
mut redis := redisclient.new('localhost:6379') or { panic(err) }
mut rpc := redis.rpc_get('actor_@{dollar}{a.name}')
println('Actor started and listening for tasks...')
for {
rpc.process(a.handle)!
time.sleep(time.millisecond * 100) // Prevent CPU spinning
}
}

View File

@@ -1,37 +0,0 @@
import os
import freeflowuniverse.herolib.baobab.stage
import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.schemas.openapi
const name = '@{actor_name_snake}'
@@[heap]
struct @{actor_name_pascal}Actor {
stage.Actor
}
pub fn new() !&@{actor_name_pascal}Actor {
return &@{actor_name_pascal}Actor {
Actor: stage.new_actor('example_@{actor_name_snake}')!
}
}
pub fn (mut a @{actor_name_pascal}Actor) handle(method string, data string) !string {
action := a.act(
name: method
params: data
)!
return action.result
}
// Actor listens to the Redis queue for method invocations
pub fn (mut a @{actor_name_pascal}Actor) run() ! {
mut redis := redisclient.new('localhost:6379') or { panic(err) }
mut rpc := redis.rpc_get('actor_@{dollar}{a.name}')
println('Actor started and listening for tasks...')
for {
rpc.process(a.handle)!
time.sleep(time.millisecond * 100) // Prevent CPU spinning
}
}

View File

@@ -1,10 +0,0 @@
const test_port = 8101
pub fn test_new() ! {
new() or { return error('Failed to create actor:\n@{dollar}{err}') }
}
pub fn test_actor_run() ! {
mut actor := new()!
spawn actor.run()
}

View File

@@ -1,74 +0,0 @@
module pet_store_actor
import freeflowuniverse.herolib.baobab.stage
import freeflowuniverse.herolib.core.redisclient
import x.json2 as json
import time
fn mock_response() ! {
mut redis := redisclient.new('localhost:6379')!
mut rpc_q := redis.rpc_get('actor_pet_store')
for {
rpc_q.process(fn(method string, data string)!string{
return json.encode(method)
})!
time.sleep(time.millisecond * 100) // Prevent CPU spinning
}
}
fn test_list_pets() ! {
mut client := new_client()!
limit := 10
spawn mock_response()
pets := client.list_pets(limit)!
// assert pets.len <= limit
println('test_list_pets passed')
}
fn test_create_pet() ! {
mut client := new_client()!
client.create_pet()!
println('test_create_pet passed')
}
fn test_get_pet() ! {
mut client := new_client()!
pet_id := 1 // Replace with an actual pet ID in your system
pet := client.get_pet(pet_id)!
// assert pet.id == pet_id
println('test_get_pet passed')
}
fn test_delete_pet() ! {
mut client := new_client()!
pet_id := 1 // Replace with an actual pet ID in your system
client.delete_pet(pet_id)!
println('test_delete_pet passed')
}
fn test_list_orders() ! {
mut client := new_client()!
client.list_orders()!
println('test_list_orders passed')
}
fn test_get_order() ! {
mut client := new_client()!
order_id := 1 // Replace with an actual order ID in your system
order := client.get_order(order_id)!
// assert order.id == order_id
println('test_get_order passed')
}
fn test_delete_order() ! {
mut client := new_client()!
order_id := 1 // Replace with an actual order ID in your system
client.delete_order(order_id)!
println('test_delete_order passed')
}
fn test_create_user() ! {
mut client := new_client()!
client.create_user()!
println('test_create_user passed')
}

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
import os
abs_dir_of_script := dir(@@FILE)
// Format code
println('Formatting code...')
if os.system('v fmt -w @{dollar}{abs_dir_of_script}/examples') != 0 {
eprintln('Warning: Failed to format examples')
}
if os.system('v fmt -w @{dollar}{abs_dir_of_script}/src') != 0 {
eprintln('Warning: Failed to format actor')
}
// Clean existing docs
println('Cleaning existing documentation...')
os.rmdir_all('_docs') or {}
os.rmdir_all('docs') or {}
os.rmdir_all('vdocs') or {}
herolib_path := os.join_path(abs_dir_of_script, 'lib')
os.chdir(herolib_path) or {
panic('Failed to change directory to herolib: @{dollar}{err}')
}
os.rmdir_all('_docs') or {}
os.rmdir_all('docs') or {}
os.rmdir_all('vdocs') or {}
// Generate HTML documentation
println('Generating HTML documentation...')
if os.system('v doc -m -f html . -readme -comments -no-timestamp -o ../docs') != 0 {
panic('Failed to generate HTML documentation')
}
os.chdir(abs_dir_of_script) or {
panic('Failed to change directory to abs_dir_of_script: @{dollar}{err}')
}
// Generate Markdown documentation
println('Generating Markdown documentation...')
os.rmdir_all('vdocs') or {}
if os.system('v doc -m -no-color -f md -o vdocs/herolib/') != 0 {
panic('Failed to generate Hero markdown documentation')
}
println('Documentation generation completed successfully!')

View File

@@ -1,46 +0,0 @@
import freeflowuniverse.herolib.schemas.openapi { OpenAPI }
import freeflowuniverse.herolib.baobab.stage {Client, ClientConfig}
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
import freeflowuniverse.herolib.baobab.stage.interfaces { HTTPServer, Context }
import veb
@@[params]
pub struct ServerParams {
pub:
base_url string
port int = 8080
}
pub fn new_http_server(params ServerParams) !&HTTPServer {
mut s := interfaces.new_http_server()!
@if openrpc
mut openrpc_controller := new_openrpc_http_controller(ServerParams{
...params,
base_url: '@{dollar}{params.base_url}/openrpc'
})!
s.register_controller[openrpc.HTTPController, Context]('/openrpc', mut openrpc_controller)!
@end
@if openapi
mut openapi_controller := new_openapi_http_controller(ServerParams{
...params,
base_url: '@{dollar}{params.base_url}/openapi/v1'
})!
mut openapi_example_controller := new_openapi_http_controller(ServerParams{
...params,
base_url: '@{dollar}{params.base_url}/openapi/example'
})!
mut openapi_playground_controller := openapi.new_playground_controller(
base_url: '@{dollar}{params.base_url}/playground/openapi'
specification_path: openapi_spec_path
)!
s.register_controller[openapi.HTTPController, Context]('/openapi/v1', mut openapi_controller)!
s.register_controller[openapi.HTTPController, Context]('/openapi/example', mut openapi_example_controller)!
s.register_controller[openapi.PlaygroundController, Context]('/playground/openapi', mut openapi_playground_controller)!
@end
return s
}
pub fn run_http_server(params ServerParams) ! {
mut server := new_http_server(params)!
veb.run[HTTPServer, Context](mut server, params.port)
}

View File

@@ -1,7 +0,0 @@
fn test_new_http_server() ! {
new_http_server()!
}
fn test_run_http_server() ! {
spawn run_http_server()
}

View File

@@ -1,21 +0,0 @@
import freeflowuniverse.herolib.baobab.stage.interfaces
import freeflowuniverse.herolib.schemas.openapi
pub fn new_openapi_interface() !&interfaces.OpenAPIInterface {
// create OpenAPI Handler with actor's client
client := new_client()!
return interfaces.new_openapi_interface(client.Client)
}
@if http
// creates HTTP controller with the actor's OpenAPI Handler
// and OpenAPI Specification
pub fn new_openapi_http_controller(params ServerParams) !&openapi.HTTPController {
return openapi.new_http_controller(
base_url: params.base_url
specification: openapi_specification
specification_path: openapi_spec_path
handler: new_openapi_interface()!
)
}
@end

View File

@@ -1,9 +0,0 @@
fn test_new_openapi_interface() ! {
new_openapi_interface()!
}
@if http
fn test_new_openapi_http_controller() ! {
new_openapi_http_controller()!
}
@end

View File

@@ -1,19 +0,0 @@
import freeflowuniverse.herolib.baobab.stage.interfaces
import freeflowuniverse.herolib.schemas.openrpc
pub fn new_openrpc_interface() !&interfaces.OpenRPCInterface {
// create OpenRPC Handler with actor's client
client := new_client()!
return interfaces.new_openrpc_interface(client.Client)
}
@if http
// creates HTTP controller with the actor's OpenRPC Handler
// and OpenRPC Specification
pub fn new_openrpc_http_controller(params ServerParams) !&openrpc.HTTPController {
return openrpc.new_http_controller(
specification: openrpc_specification
handler: new_openrpc_interface()!
)
}
@end

View File

@@ -1,9 +0,0 @@
fn test_new_openrpc_interface() ! {
new_openrpc_interface()!
}
@if http
fn test_new_openrpc_http_controller() ! {
new_openrpc_http_controller()!
}
@end

View File

@@ -1,38 +0,0 @@
#!/bin/bash
DIR="@{dollar}(cd "@{dollar}(dirname "@{dollar}{BASH_SOURCE[0]}")" && pwd)"
echo "@{dollar}DIR"
chmod +x @{dollar}{DIR}/run_actor.vsh
@{dollar}{DIR}/run_actor.vsh > /dev/null 2>&1 &
ACTOR_PID=@{dollar}!
chmod +x @{dollar}{DIR}/run_http_server.vsh
@{dollar}{DIR}/run_http_server.vsh > /dev/null 2>&1 &
HTTP_SERVER_PID=@{dollar}!
# Print desired output
echo "${actor_title} Actor Redis Interface running on redis://localhost:6379"
echo "* /queues/${actor_name} -> Action Interface"
echo ""
echo "${actor_title} Actor HTTP Server running on http://localhost:8080"
echo "* /playground/openapi -> OpenAPI Playground"
echo "* /openapi -> OpenAPI Interface"
echo "* /docs -> Documentation"
echo ""
# Function to clean up when script is killed
cleanup() {
echo "Stopping background processes..."
kill "@{dollar}ACTOR_PID" "@{dollar}HTTP_SERVER_PID" 2>/dev/null
wait
echo "All processes stopped."
exit 0
}
# Trap SIGINT (Ctrl+C), SIGTERM, and SIGQUIT to call cleanup
trap cleanup SIGINT SIGTERM SIGQUIT
# Wait for processes to finish
wait

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import @{actor_name}
mut actor := @{actor_name}.new()!
actor.run()!

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import @{actor_name}
mut actor := @{actor_name}.new_example()!
actor.run()!

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import @{actor_name}
@{actor_name}.run_http_server(
base_url: 'http://localhost:@{port}'
port: @{port}
)!

View File

@@ -1,14 +0,0 @@
import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.schemas.openrpc
import os
@if support_openrpc
const openrpc_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/openrpc.json'
const openrpc_spec_json = os.read_file(openrpc_spec_path) or { panic(err) }
const openrpc_specification = openrpc.decode(openrpc_spec_json)!
@end
@if support_openapi
const openapi_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/openapi.json'
const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) }
const openapi_specification = openapi.json_decode(openapi_spec_json)!
@end

View File

@@ -1,406 +0,0 @@
module generator
import freeflowuniverse.herolib.baobab.specification {BaseObject}
import freeflowuniverse.herolib.core.code { type_from_symbol, VFile, CodeItem, Function, Import, Param, Param, Struct, StructField, Type }
import freeflowuniverse.herolib.core.texttools
const id_param = Param{
name: 'id'
typ: type_from_symbol('u32')
}
// pub fn generate_object_code(actor Struct, object BaseObject) VFile {
// obj_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// mut items := []CodeItem{}
// items = [generate_new_method(actor, object), generate_get_method(actor, object),
// generate_set_method(actor, object), generate_delete_method(actor, object),
// generate_list_result_struct(actor, object), generate_list_method(actor, object)]
// items << generate_object_methods(actor, object)
// mut file := code.new_file(
// mod: texttools.name_fix(actor.name)
// name: obj_name
// imports: [
// Import{
// mod: object.structure.mod
// types: [object_type]
// },
// Import{
// mod: 'freeflowuniverse.herolib.baobab.backend'
// types: ['FilterParams']
// },
// ]
// items: items
// )
// if object.structure.fields.any(it.attrs.any(it.name == 'index')) {
// // can't filter without indices
// filter_params := generate_filter_params(actor, object)
// file.items << filter_params.map(CodeItem(it))
// file.items << generate_filter_method(actor, object)
// }
// return file
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_get_method(actor Struct, object BaseObject) Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// get_method := Function{
// name: 'get_${object_name}'
// description: 'gets the ${object_name} with the given object id'
// receiver: Param{
// mutable: true
// name: 'actor'
// typ: type_from_symbol(actor.name)
// }
// }
// params: [generator.id_param]
// result: Param{
// typ: type_from_symbol(object.structure.name)
// is_result: true
// }
// body: 'return actor.backend.get[${object_type}](id)!'
// }
// return get_method
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_set_method(actor Struct, object BaseObject) Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// param_getters := generate_param_getters(
// structure: object.structure
// prefix: ''
// only_mutable: true
// )
// body := 'actor.backend.set[${object_type}](${object_name})!'
// get_method := Function{
// name: 'set_${object_name}'
// description: 'updates the ${object.structure.name} with the given object id'
// receiver: Param{
// mutable: true
// name: 'actor'
// typ: type_from_symbol(actor.name)
// }
// }
// params: [
// Param{
// name: object_name
// typ: Type{
// symbol: object_type
// }
// },
// ]
// result: Param{
// is_result: true
// }
// body: body
// }
// return get_method
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_delete_method(actor Struct, object BaseObject) Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// body := 'actor.backend.delete[${object_type}](id)!'
// get_method := Function{
// name: 'delete_${object_name}'
// description: 'deletes the ${object.structure.name} with the given object id'
// receiver: Param{
// mutable: true
// name: 'actor'
// typ: Type{
// symbol: actor.name
// }
// }
// params: [generator.id_param]
// result: Param{
// is_result: true
// }
// body: body
// }
// return get_method
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_new_method(actor Struct, object BaseObject) Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// param_getters := generate_param_getters(
// structure: object.structure
// prefix: ''
// only_mutable: false
// )
// body := 'return actor.backend.new[${object_type}](${object_name})!'
// new_method := Function{
// name: 'new_${object_name}'
// description: 'news the ${object.structure.name} with the given object id'
// receiver: Param{
// name: 'actor'
// typ: Type{
// symbol: actor.name
// }
// mutable: true
// }
// params: [
// Param{
// name: object_name
// typ: Type{
// symbol: object_type
// }
// },
// ]
// result: Param{
// is_result: true
// typ: Type{
// symbol: 'u32'
// }
// }
// body: body
// }
// return new_method
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_list_result_struct(actor Struct, object BaseObject) Struct {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// return Struct{
// name: '${object_type}List'
// is_pub: true
// fields: [
// StructField{
// name: 'items'
// typ: Type{
// symbol: '[]${object_type}'
// }
// },
// ]
// }
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_list_method(actor Struct, object BaseObject) Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// list_struct := Struct{
// name: '${object_type}List'
// fields: [
// StructField{
// name: 'items'
// typ: Type{
// symbol: '[]${object_type}'
// }
// },
// ]
// }
// param_getters := generate_param_getters(
// structure: object.structure
// prefix: ''
// only_mutable: false
// )
// body := 'return ${object_type}List{items:actor.backend.list[${object_type}]()!}'
// result_struct := generate_list_result_struct(actor, object)
// mut result := Param{}
// result.typ.symbol = result_struct.name
// result.is_result = true
// new_method := Function{
// name: 'list_${object_name}'
// description: 'lists all of the ${object_name} objects'
// receiver: Param{
// name: 'actor'
// typ: Type{
// symbol: actor.name
// }
// mutable: true
// }
// params: []
// result: result
// body: body
// }
// return new_method
// }
// fn generate_filter_params(actor Struct, object BaseObject) []Struct {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// return [
// Struct{
// name: 'Filter${object_type}Params'
// fields: [
// StructField{
// name: 'filter'
// typ: Type{
// symbol: '${object_type}Filter'
// }
// },
// StructField{
// name: 'params'
// typ: Type{
// symbol: 'FilterParams'
// }
// },
// ]
// },
// Struct{
// name: '${object_type}Filter'
// fields: object.structure.fields.filter(it.attrs.any(it.name == 'index'))
// },
// ]
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_filter_method(actor Struct, object BaseObject) Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// param_getters := generate_param_getters(
// structure: object.structure
// prefix: ''
// only_mutable: false
// )
// params_type := 'Filter${object_type}Params'
// body := 'return actor.backend.filter[${object_type}, ${object_type}Filter](filter.filter, filter.params)!'
// return Function{
// name: 'filter_${object_name}'
// description: 'lists all of the ${object_name} objects'
// receiver: Param{
// name: 'actor'
// typ: Type{
// symbol: actor.name
// }
// mutable: true
// }
// params: [
// Param{
// name: 'filter'
// typ: Type{
// symbol: params_type
// }
// },
// ]
// result: Param{
// typ: Type{
// symbol: '[]${object_type}'
// }
// is_result: true
// }
// body: body
// }
// }
// // // generate_object_methods generates CRUD actor methods for a provided structure
// // fn generate_object_methods(actor Struct, object BaseObject) []Function {
// // object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// // object_type := object.structure.name
// // mut funcs := []Function{}
// // for method in object.methods {
// // mut params := [Param{
// // name: 'id'
// // typ: Type{
// // symbol: 'u32'
// // }
// // }]
// // params << method.params
// // funcs << Function{
// // name: method.name
// // description: method.description
// // receiver: Param{
// // name: 'actor'
// // typ: Type{
// // symbol: actor.name
// // }
// // mutable: true
// // }
// // params: params
// // result: method.result
// // body: 'obj := actor.backend.get[${method.receiver.typ.symbol}](id)!
// // obj.${method.name}(${method.params.map(it.name).join(',')})
// // actor.backend.set[${method.receiver.typ.symbol}](obj)!
// // '
// // }
// // }
// // return funcs
// // }
// @[params]
// struct GenerateParamGetters {
// structure Struct
// prefix string
// only_mutable bool // if true generates param.get methods for only mutable struct fields. Used for updating.
// }
// fn generate_param_getters(params GenerateParamGetters) []string {
// mut param_getters := []string{}
// fields := if params.only_mutable {
// params.structure.fields.filter(it.is_mut && it.is_pub)
// } else {
// params.structure.fields.filter(it.is_pub)
// }
// for field in fields {
// if field.typ.symbol.starts_with_capital() {
// subgetters := generate_param_getters(GenerateParamGetters{
// ...params
// structure: field.structure
// prefix: '${field.name}_'
// })
// // name of the tested object, used for param declaration
// // ex: fruits []Fruit becomes fruit_name
// nested_name := field.structure.name.to_lower()
// if field.typ.is_map {
// param_getters.insert(0, '${nested_name}_key := params.get(\'${nested_name}_key\')!')
// param_getters << '${field.name}: {${nested_name}_key: ${field.structure.name}}{'
// } else if field.typ.is_array {
// param_getters << '${field.name}: [${field.structure.name}{'
// } else {
// param_getters << '${field.name}: ${field.structure.name}{'
// }
// param_getters << subgetters
// param_getters << if field.typ.is_array { '}]' } else { '}' }
// continue
// }
// mut get_method := '${field.name}: params.get'
// if field.typ.symbol != 'string' {
// // TODO: check if params method actually exists
// 'get_${field.typ.symbol}'
// }
// if field.default != '' {
// get_method += '_default'
// }
// get_method = get_method + "('${params.prefix}${field.name}')!"
// param_getters << get_method
// }
// return param_getters
// }
// @[params]
// struct GetChildField {
// parent Struct @[required]
// child Struct @[required]
// }
// fn get_child_field(params GetChildField) StructField {
// fields := params.parent.fields.filter(it.typ.symbol == 'map[string]&${params.child.name}')
// if fields.len != 1 {
// panic('this should never happen')
// }
// return fields[0]
// }

View File

@@ -1,168 +0,0 @@
module generator
import freeflowuniverse.herolib.core.code { VFile, CustomCode, Function, Import, Struct }
import freeflowuniverse.herolib.baobab.specification {BaseObject}
import rand
import freeflowuniverse.herolib.core.texttools
// // generate_object_methods generates CRUD actor methods for a provided structure
// pub fn generate_object_test_code(actor Struct, object BaseObject) !VFile {
// consts := CustomCode{"const db_dir = '\${os.home_dir()}/hero/db'
// const actor_name = '${actor.name}_test_actor'"}
// clean_code := 'mut actor := get(name: actor_name)!\nactor.backend.reset()!'
// testsuite_begin := Function{
// name: 'testsuite_begin'
// body: clean_code
// }
// testsuite_end := Function{
// name: 'testsuite_end'
// body: clean_code
// }
// actor_name := texttools.name_fix(actor.name)
// object_name := texttools.name_fix_pascal_to_snake(object.schema.name)
// object_type := object.structure.name
// // TODO: support modules outside of crystal
// mut file := VFile{
// name: '${object_name}_test'
// mod: texttools.name_fix(actor_name)
// imports: [
// Import{
// mod: 'os'
// },
// Import{
// mod: '${object.structure.mod}'
// types: [object_type]
// },
// ]
// items: [
// consts,
// testsuite_begin,
// testsuite_end,
// generate_new_method_test(actor, object)!,
// generate_get_method_test(actor, object)!,
// ]
// }
// if object.structure.fields.any(it.attrs.any(it.name == 'index')) {
// // can't filter without indices
// file.items << generate_filter_test(actor, object)!
// }
// return file
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_new_method_test(actor Struct, object BaseObject) !Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// required_fields := object.structure.fields.filter(it.attrs.any(it.name == 'required'))
// mut fields := []string{}
// for field in required_fields {
// mut field_decl := '${field.name}: ${get_mock_value(field.typ.symbol())!}'
// fields << field_decl
// }
// body := 'mut actor := get(name: actor_name)!
// mut ${object_name}_id := actor.new_${object_name}(${object_type}{${fields.join(',')}})!
// assert ${object_name}_id == 1
// ${object_name}_id = actor.new_${object_name}(${object_type}{${fields.join(',')}})!
// assert ${object_name}_id == 2'
// return Function{
// name: 'test_new_${object_name}'
// description: 'news the ${object_type} with the given object id'
// result: code.Param{
// is_result: true
// }
// body: body
// }
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_get_method_test(actor Struct, object BaseObject) !Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// required_fields := object.structure.fields.filter(it.attrs.any(it.name == 'required'))
// mut fields := []string{}
// for field in required_fields {
// mut field_decl := '${field.name}: ${get_mock_value(field.typ.symbol())!}'
// fields << field_decl
// }
// body := 'mut actor := get(name: actor_name)!
// mut ${object_name} := ${object_type}{${fields.join(',')}}
// ${object_name}.id = actor.new_${object_name}(${object_name})!
// assert ${object_name} == actor.get_${object_name}(${object_name}.id)!'
// return Function{
// name: 'test_get_${object_name}'
// description: 'news the ${object_type} with the given object id'
// result: code.Param{
// is_result: true
// }
// body: body
// }
// }
// // generate_object_methods generates CRUD actor methods for a provided structure
// fn generate_filter_test(actor Struct, object BaseObject) !Function {
// object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
// object_type := object.structure.name
// index_fields := object.structure.fields.filter(it.attrs.any(it.name == 'index'))
// if index_fields.len == 0 {
// return error('Cannot generate filter method test for object without any index fields')
// }
// mut index_tests := []string{}
// for i, field in index_fields {
// val := get_mock_value(field.typ.symbol())!
// index_field := '${field.name}: ${val}' // index field assignment line
// mut fields := [index_field]
// fields << get_required_fields(object.structure)!
// index_tests << '${object_name}_id${i} := actor.new_${object_name}(${object_type}{${fields.join(',')}})!
// ${object_name}_list${i} := actor.filter_${object_name}(
// filter: ${object_type}Filter{${index_field}}
// )!
// assert ${object_name}_list${i}.len == 1
// assert ${object_name}_list${i}[0].${field.name} == ${val}
// '
// }
// body := 'mut actor := get(name: actor_name)!
// \n${index_tests.join('\n\n')}'
// return Function{
// name: 'test_filter_${object_name}'
// description: 'news the ${object_type} with the given object id'
// result: code.Param{
// is_result: true
// }
// body: body
// }
// }
// fn get_required_fields(s Struct) ![]string {
// required_fields := s.fields.filter(it.attrs.any(it.name == 'required'))
// mut fields := []string{}
// for field in required_fields {
// fields << '${field.name}: ${get_mock_value(field.typ.symbol())!}'
// }
// return fields
// }
// fn get_mock_value(typ string) !string {
// if typ == 'string' {
// return "'mock_string_${rand.string(3)}'"
// } else if typ == 'int' || typ == 'u32' {
// return '42'
// } else {
// return error('mock values for types other than strings and numbers are not yet supported')
// }
// }

View File

@@ -1,8 +0,0 @@
module osis
pub fn new(config OSISConfig) !OSIS {
return OSIS{
indexer: new_indexer()!
storer: new_storer()!
}
}

View File

@@ -1,116 +0,0 @@
module osis
import json
import db.sqlite
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.pathlib
pub struct Indexer {
db sqlite.DB
}
@[params]
pub struct IndexerConfig {
db_path string
reset bool
}
pub fn new_indexer(config IndexerConfig) !Indexer {
return Indexer{}
}
// deletes an indexer table belonging to a base object
pub fn reset(path string) ! {
mut db_file := pathlib.get_file(path: path)!
db_file.delete()!
}
pub fn (mut i Indexer) new_generic[T](id u32, object T) !u32 {
return i.new(get_table[T](), id, get_indices[T](object))!
}
// new creates a new root object entry in the root_objects table,
// and the table belonging to the type of root object with columns for index fields
pub fn (mut i Indexer) new(table string, id u32, indices map[string]string) !u32 {
insert_query := 'INSERT into ${table} (${indices.keys().join(',')}) values (${indices.values().join(',')})'
i.db.exec(insert_query) or {
return error('Error inserting object ${id} into table ${table}\n${err}')
}
return 0
}
// save the session to redis & mem
pub fn (mut backend Indexer) set(obj RootObject) ! {
panic('implement')
}
// save the session to redis & mem
pub fn (mut backend Indexer) delete(id string, obj RootObject) ! {
panic('implement')
}
pub fn (mut backend Indexer) get(id string, obj RootObject) !RootObject {
panic('implement')
}
pub fn (mut backend Indexer) get_json(id string, obj RootObject) !string {
panic('implement')
}
pub fn (mut backend Indexer) list(obj RootObject) ![]u32 {
panic('implement')
}
// from and to for int f64 time etc.
@[params]
pub struct FilterParams {
// indices map[string]string // map of index values that are being filtered by, in order of priority.
limit int // limit to the number of values to be returned, in order of priority
fuzzy bool // if fuzzy matching is enabled in matching indices
matches_all bool // if results should match all indices or any
}
// filter lists root objects of type T that match provided index parameters and params.
pub fn (mut backend Indexer) filter(filter RootObject, params FilterParams) ![]string {
panic('implement')
}
// create_root_struct_table creates a table for a root_struct with columns for each index field
fn (mut backend Indexer) create_root_object_table(object RootObject) ! {
panic('implement')
}
// deletes an indexer table belonging to a root object
fn (mut backend Indexer) delete_table(object RootObject)! {
panic('implement')
}
fn (mut backend Indexer) get_table_indices(table_name string) ![]string {
panic('implement')
}
fn (mut backend Indexer) table_exists(table_name string) !bool {
panic('implement')
}
// get_table_name returns the name of the table belonging to a root struct
fn get_table_name(object RootObject) string {
panic('implement')
}
// get_table_name returns the name of the table belonging to a root struct
fn get_table[T]() string {
return typeof[T]()
}
// returns the lists of the indices of a root objects db table, and corresponding values
pub fn get_indices[T](object T) map[string]string {
mut indices := map[string]string
$for field in T.fields {
if field.attrs.contains('index') {
value := object.$(field.name)
indices[field.name] = '${value}'
}
}
return indices
}

View File

@@ -1,16 +0,0 @@
module osis
pub struct OSIS {
pub mut:
indexer Indexer // storing indeces
storer Storer
}
@[params]
pub struct OSISConfig {
pub:
directory string
name string
secret string
reset bool
}

View File

@@ -1,15 +0,0 @@
module osis
import freeflowuniverse.herolib.data.ourdb {OurDB}
import os
pub struct Storer {
pub mut:
db OurDB
}
pub fn new_storer() !Storer {
return Storer {
db: ourdb.new()!
}
}

View File

@@ -1,23 +0,0 @@
module osis
import json
// new creates a new root object entry in the root_objects table,
// and the table belonging to the type of root object with columns for index fields
pub fn (mut storer Storer) new_generic[T](obj T) !u32 {
data := json.encode(obj).bytes()
return storer.db.set(data: data)
}
pub fn (mut storer Storer) generic_get[T](id u32) !T {
return json.decode(T, storer.db.get(id)!.bytestr())
}
pub fn (mut storer Storer) generic_set[T](obj T) ! {
data := json.encode(obj).bytes()
return storer.db.set(data: data)
}
pub fn (mut storer Storer) delete(id u32) ! {
storer.db.delete(id)!
}

View File

@@ -1,150 +0,0 @@
module specification
import freeflowuniverse.herolib.core.code { Struct, Function }
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
import freeflowuniverse.herolib.schemas.openapi { Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec }
import freeflowuniverse.herolib.schemas.openrpc { ExamplePairing, Example, ExampleRef, ContentDescriptor, ErrorSpec }
// Helper function: Convert OpenAPI parameter to ContentDescriptor
fn openapi_param_to_content_descriptor(param Parameter) ContentDescriptor {
return ContentDescriptor{
name: param.name,
summary: param.description,
description: param.description,
required: param.required,
schema: param.schema
}
}
// Helper function: Convert OpenAPI parameter to ContentDescriptor
fn openapi_param_to_example(param Parameter) ?Example {
if param.schema is Schema {
if param.schema.example.str() != '' {
return Example{
name: 'Example ${param.name}',
description: 'Example ${param.description}',
value: param.schema.example
}
}
}
return none
}
// Helper function: Convert OpenAPI operation to ActorMethod
fn openapi_operation_to_actor_method(info openapi.OperationInfo) ActorMethod {
mut parameters := []ContentDescriptor{}
mut example_parameters:= []Example{}
for param in info.operation.parameters {
parameters << openapi_param_to_content_descriptor(param)
example_parameters << openapi_param_to_example(param) or {
continue
}
}
response_200 := info.operation.responses['200'].content['application/json']
mut result := ContentDescriptor{
name: "result",
description: "The response of the operation.",
required: true,
schema: response_200.schema
}
example_result := if response_200.example.str() != '' {
Example{
name: 'Example response',
value: response_200.example
}
} else {Example{}}
pairing := if example_result != Example{} || example_parameters.len > 0 {
ExamplePairing{
params: example_parameters.map(ExampleRef(it))
result: ExampleRef(example_result)
}
} else {ExamplePairing{}}
mut errors := []ErrorSpec{}
for status, response in info.operation.responses {
if status.int() >= 400 {
error_schema := if response.content.len > 0 {
response.content.values()[0].schema
} else {Schema{}}
errors << ErrorSpec{
code: status.int(),
message: response.description,
data: error_schema, // Extend if error schema is defined
}
}
}
return ActorMethod{
name: info.operation.operation_id,
description: info.operation.description,
summary: info.operation.summary,
parameters: parameters,
example: pairing
result: result,
errors: errors,
}
}
// Helper function: Convert OpenAPI schema to Struct
fn openapi_schema_to_struct(name string, schema SchemaRef) Struct {
// Assuming schema properties can be mapped to Struct fields
return Struct{
name: name,
}
}
// Converts OpenAPI to ActorSpecification
pub fn from_openapi(spec_raw OpenAPI) !ActorSpecification {
spec := openapi.process(spec_raw)!
mut objects := []BaseObject{}
// get all operations for path as list of tuple [](path_string, http.Method, openapi.Operation)
// Extract methods from OpenAPI paths
// for path, item in spec.paths {
// if item.get.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.get, item.get.operation_id, path)
// }
// if item.post.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.post, item.post.operation_id, path)
// }
// if item.put.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.put, item.put.operation_id, path)
// }
// if item.delete.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.delete, item.delete.operation_id, path)
// }
// if item.patch.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.patch, item.patch.operation_id, path)
// }
// if item.head.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.head, item.head.operation_id, path)
// }
// if item.options.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.options, item.options.operation_id, path)
// }
// if item.trace.operation_id != '' {
// methods << openapi_operation_to_actor_method(item.trace, item.trace.operation_id, path)
// }
// }
// Extract objects from OpenAPI components.schemas
for name, schema in spec.components.schemas {
objects << BaseObject{schema: schema as Schema}
}
return ActorSpecification{
openapi: spec_raw
name: spec.info.title,
description: spec.info.description,
structure: Struct{}, // Assuming no top-level structure for this use case
interfaces: [.openapi], // Default to OpenAPI for input
methods: spec.get_operations().map(openapi_operation_to_actor_method(it)),
objects: objects,
}
}

View File

@@ -1,400 +0,0 @@
module specification
import x.json2 as json {Any}
import freeflowuniverse.herolib.core.code { Struct, Function }
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec }
import freeflowuniverse.herolib.schemas.openapi { OpenAPI, Info, ServerSpec, Components, Operation, PathItem, PathRef }
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference, SchemaRef}
const openapi_spec = openapi.OpenAPI{
openapi: '3.0.3'
info: openapi.Info{
title: 'Pet Store API'
description: 'A sample API for a pet store'
version: '1.0.0'
}
servers: [
openapi.ServerSpec{
url: 'https://api.petstore.example.com/v1'
description: 'Production server'
},
openapi.ServerSpec{
url: 'https://staging.petstore.example.com/v1'
description: 'Staging server'
}
]
paths: {
'/pets': openapi.PathItem{
get: openapi.Operation{
summary: 'List all pets'
operation_id: 'listPets'
parameters: [
openapi.Parameter{
name: 'limit'
in_: 'query'
description: 'Maximum number of pets to return'
required: false
schema: Schema{
typ: 'integer'
format: 'int32'
example: 10
}
}
]
responses: {
'200': openapi.ResponseSpec{
description: 'A paginated list of pets'
content: {
'application/json': openapi.MediaType{
schema: Reference{
ref: '#/components/schemas/Pets'
}
example: json.raw_decode('[
{ "id": 1, "name": "Fluffy", "tag": "dog" },
{ "id": 2, "name": "Whiskers", "tag": "cat" }
]')!
}
}
}
'400': openapi.ResponseSpec{
description: 'Invalid request'
}
}
}
post: openapi.Operation{
summary: 'Create a new pet'
operation_id: 'createPet'
request_body: openapi.RequestBody{
required: true
content: {
'application/json': openapi.MediaType{
schema: Reference{
ref: '#/components/schemas/NewPet'
}
example: json.raw_decode('{ "name": "Bella", "tag": "dog" }')!
}
}
}
responses: {
'201': openapi.ResponseSpec{
description: 'Pet created'
content: {
'application/json': openapi.MediaType{
schema: Reference{
ref: '#/components/schemas/Pet'
}
example: json.raw_decode('{ "id": 3, "name": "Bella", "tag": "dog" }')!
}
}
}
'400': openapi.ResponseSpec{
description: 'Invalid input'
}
}
}
}
'/pets/{petId}': openapi.PathItem{
get: openapi.Operation{
summary: 'Get a pet by ID'
operation_id: 'getPet'
parameters: [
openapi.Parameter{
name: 'petId'
in_: 'path'
description: 'ID of the pet to retrieve'
required: true
schema: Schema{
typ: 'integer'
format: 'int64'
example: 1
}
}
]
responses: {
'200': openapi.ResponseSpec{
description: 'A pet'
content: {
'application/json': openapi.MediaType{
schema: Reference{
ref: '#/components/schemas/Pet'
}
example: json.raw_decode('{ "id": 1, "name": "Fluffy", "tag": "dog" }')!
}
}
}
'404': openapi.ResponseSpec{
description: 'Pet not found'
}
}
}
delete: openapi.Operation{
summary: 'Delete a pet by ID'
operation_id: 'deletePet'
parameters: [
openapi.Parameter{
name: 'petId'
in_: 'path'
description: 'ID of the pet to delete'
required: true
schema: Schema{
typ: 'integer'
format: 'int64'
example: 1
}
}
]
responses: {
'204': openapi.ResponseSpec{
description: 'Pet deleted'
}
'404': openapi.ResponseSpec{
description: 'Pet not found'
}
}
}
}
}
components: openapi.Components{
schemas: {
'Pet': SchemaRef(Schema{
typ: 'object'
required: ['id', 'name']
properties: {
'id': SchemaRef(Schema{
typ: 'integer'
format: 'int64'
})
'name': SchemaRef(Schema{
typ: 'string'
})
'tag': SchemaRef(Schema{
typ: 'string'
})
}
})
'NewPet': SchemaRef(Schema{
typ: 'object'
required: ['name']
properties: {
'name': SchemaRef(Schema{
typ: 'string'
})
'tag': SchemaRef(Schema{
typ: 'string'
})
}
})
'Pets': SchemaRef(Schema{
typ: 'array'
items: SchemaRef(Reference{
ref: '#/components/schemas/Pet'
})
})
}
}
}
const actor_spec = specification.ActorSpecification{
name: 'Pet Store API'
description: 'A sample API for a pet store'
structure: code.Struct{}
interfaces: [.openapi]
methods: [
specification.ActorMethod{
name: 'listPets'
summary: 'List all pets'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example limit'
description: 'Example Maximum number of pets to return'
value: 10
})
]
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: json.raw_decode('[
{"id": 1, "name": "Fluffy", "tag": "dog"},
{"id": 2, "name": "Whiskers", "tag": "cat"}
]')!
})
}
parameters: [
openrpc.ContentDescriptor{
name: 'limit'
summary: 'Maximum number of pets to return'
description: 'Maximum number of pets to return'
required: false
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
format: 'int32'
example: 10
})
}
]
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pets'
})
}
errors: [
openrpc.ErrorSpec{
code: 400
message: 'Invalid request'
}
]
},
specification.ActorMethod{
name: 'createPet'
summary: 'Create a new pet'
example: openrpc.ExamplePairing{
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: '[]'
})
}
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
}
errors: [
openrpc.ErrorSpec{
code: 400
message: 'Invalid input'
}
]
},
specification.ActorMethod{
name: 'getPet'
summary: 'Get a pet by ID'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example petId'
description: 'Example ID of the pet to retrieve'
value: 1
})
]
result: openrpc.ExampleRef(openrpc.Example{
name: 'Example response'
value: json.raw_decode('{"id": 1, "name": "Fluffy", "tag": "dog"}')!
})
}
parameters: [
openrpc.ContentDescriptor{
name: 'petId'
summary: 'ID of the pet to retrieve'
description: 'ID of the pet to retrieve'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
format: 'int64'
example: 1
})
}
]
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
}
errors: [
openrpc.ErrorSpec{
code: 404
message: 'Pet not found'
}
]
},
specification.ActorMethod{
name: 'deletePet'
summary: 'Delete a pet by ID'
example: openrpc.ExamplePairing{
params: [
openrpc.ExampleRef(openrpc.Example{
name: 'Example petId'
description: 'Example ID of the pet to delete'
value: 1
})
]
}
parameters: [
openrpc.ContentDescriptor{
name: 'petId'
summary: 'ID of the pet to delete'
description: 'ID of the pet to delete'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
format: 'int64'
example: 1
})
}
]
result: openrpc.ContentDescriptor{
name: 'result'
description: 'The response of the operation.'
required: true
}
errors: [
openrpc.ErrorSpec{
code: 404
message: 'Pet not found'
}
]
}
]
objects: [
specification.BaseObject{
schema: jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
format: 'int64'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}
},
specification.BaseObject{
schema: jsonschema.Schema{
typ: 'object'
properties: {
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['name']
}
},
specification.BaseObject{
schema: jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
}))
}
}
]
}
pub fn test_from_openapi() ! {
// panic(from_openapi(openapi_spec)!)
assert from_openapi(openapi_spec)! == actor_spec
}

View File

@@ -1,105 +0,0 @@
module specification
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC, Method, ContentDescriptor, ErrorSpec }
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
import freeflowuniverse.herolib.core.texttools
// Helper function: Convert OpenRPC Method to ActorMethod
fn openrpc_method_to_actor_method(method Method) ActorMethod {
mut parameters := []ContentDescriptor{}
mut errors := []ErrorSpec{}
// Process parameters
for param in method.params {
if param is ContentDescriptor {
parameters << param
} else {
panic('Method param should be inflated')
}
}
// Process errors
for err in method.errors {
if err is ErrorSpec {
errors << err
} else {
panic('Method error should be inflated')
}
}
if method.result is Reference {
panic('Method result should be inflated')
}
return ActorMethod{
name: method.name
description: method.description
summary: method.summary
parameters: parameters
result: method.result as ContentDescriptor
errors: errors
}
}
// // Helper function: Extract Structs from OpenRPC Components
// fn extract_structs_from_openrpc(openrpc OpenRPC) []Struct {
// mut structs := []Struct{}
// for schema_name, schema in openrpc.components.schemas {
// if schema is Schema {
// mut fields := []Struct.Field{}
// for field_name, field_schema in schema.properties {
// if field_schema is Schema {
// fields << Struct.Field{
// name: field_name
// typ: field_schema.to_code() or { panic(err) }
// description: field_schema.description
// required: field_name in schema.required
// }
// }
// }
// structs << Struct{
// name: schema_name
// description: schema.description
// fields: fields
// }
// }
// }
// return structs
// }
// Converts OpenRPC to ActorSpecification
pub fn from_openrpc(spec OpenRPC) !ActorSpecification {
mut methods := []ActorMethod{}
mut objects := []BaseObject{}
// Process methods
for method in spec.methods {
methods << openrpc_method_to_actor_method(spec.inflate_method(method))
}
// Process objects (schemas)
// structs := extract_structs_from_openrpc(spec)
for key, schema in spec.components.schemas {
if schema is Schema {
if schema.typ == 'object' {
objects << BaseObject{
schema: Schema {...schema,
title: texttools.name_fix_pascal(key)
id: texttools.snake_case(key)
}
}
}
}
}
return ActorSpecification{
name: spec.info.title
description: spec.info.description
interfaces: [.openrpc]
methods: methods
objects: objects
}
}

View File

@@ -1,393 +0,0 @@
module specification
import freeflowuniverse.herolib.core.code { Struct, Function }
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec }
import freeflowuniverse.herolib.schemas.openapi { OpenAPI, Info, ServerSpec, Components, Operation, PathItem, PathRef }
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference, SchemaRef}
const openrpc_spec = openrpc.OpenRPC{
openrpc: '1.0.0-rc1'
info: openrpc.Info{
title: 'Petstore'
license: openrpc.License{
name: 'MIT'
}
version: '1.0.0'
}
servers: [openrpc.Server{
name: 'localhost'
url: openrpc.RuntimeExpression('http://localhost:8080')
}]
methods: [
openrpc.Method{
name: 'list_pets'
summary: 'List all pets'
params: [openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'limit'
description: 'How many items to return at one time (max 100)'
required: false
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
minimum: 1
})
})]
result: openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'pets'
description: 'A paged array of pets'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
}))
})
})
examples: [openrpc.ExamplePairing{
name: 'listPetExample'
description: 'List pet example'
}]
},
openrpc.Method{
name: 'create_pet'
summary: 'Create a pet'
params: [
openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'newPetName'
description: 'Name of pet to create'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}),
openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'newPetTag'
description: 'Pet tag to create'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
})
]
result: openrpc.ContentDescriptorRef(jsonschema.Reference{
ref: '#/components/contentDescriptors/PetId'
})
examples: [openrpc.ExamplePairing{
name: 'createPetExample'
description: 'Create pet example'
}]
},
openrpc.Method{
name: 'get_pet'
summary: 'Info for a specific pet'
params: [openrpc.ContentDescriptorRef(jsonschema.Reference{
ref: '#/components/contentDescriptors/PetId'
})]
result: openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'pet'
description: 'Expected response to a valid request'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
})
examples: [openrpc.ExamplePairing{
name: 'getPetExample'
description: 'Get pet example'
}]
},
openrpc.Method{
name: 'update_pet'
summary: 'Update a pet'
params: [
openrpc.ContentDescriptorRef(jsonschema.Reference{
ref: '#/components/contentDescriptors/PetId'
}),
openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'updatedPetName'
description: 'New name for the pet'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}),
openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'updatedPetTag'
description: 'New tag for the pet'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
})
]
result: openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'pet'
description: 'Updated pet object'
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/Pet'
})
})
examples: [openrpc.ExamplePairing{
name: 'updatePetExample'
description: 'Update pet example'
}]
},
openrpc.Method{
name: 'delete_pet'
summary: 'Delete a pet'
params: [openrpc.ContentDescriptorRef(jsonschema.Reference{
ref: '#/components/contentDescriptors/PetId'
})]
result: openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'success'
description: 'Boolean indicating success'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'boolean'
})
})
examples: [openrpc.ExamplePairing{
name: 'deletePetExample'
description: 'Delete pet example'
}]
}
]
components: openrpc.Components{
content_descriptors: {
'PetId': openrpc.ContentDescriptorRef(openrpc.ContentDescriptor{
name: 'petId'
description: 'The ID of the pet'
required: true
schema: jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
})
})
}
schemas: {
'PetId': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
minimum: 0
}),
'Pet': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
})
}
}
}
const actor_spec = specification.ActorSpecification{
name: 'Petstore'
structure: code.Struct{
is_pub: false
}
interfaces: [.openrpc]
methods: [specification.ActorMethod{
name: 'list_pets'
summary: 'List all pets'
parameters: [openrpc.ContentDescriptor{
name: 'limit'
description: 'How many items to return at one time (max 100)'
required: false
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
minimum: 1
})
}]
result: openrpc.ContentDescriptor{
name: 'pets'
description: 'A paged array of pets'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}))
})
}
}, specification.ActorMethod{
name: 'create_pet'
summary: 'Create a pet'
parameters: [openrpc.ContentDescriptor{
name: 'newPetName'
description: 'Name of pet to create'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}, openrpc.ContentDescriptor{
name: 'newPetTag'
description: 'Pet tag to create'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}]
}, specification.ActorMethod{
name: 'get_pet'
summary: 'Info for a specific pet'
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'Expected response to a valid request'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
})
}
}, specification.ActorMethod{
name: 'update_pet'
summary: 'Update a pet'
parameters: [openrpc.ContentDescriptor{
name: 'updatedPetName'
description: 'New name for the pet'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}, openrpc.ContentDescriptor{
name: 'updatedPetTag'
description: 'New tag for the pet'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'Updated pet object'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
})
}
}, specification.ActorMethod{
name: 'delete_pet'
summary: 'Delete a pet'
result: openrpc.ContentDescriptor{
name: 'success'
description: 'Boolean indicating success'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'boolean'
})
}
}]
objects: [specification.BaseObject{
schema: jsonschema.Schema{
id: 'pet'
title: 'Pet'
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}
}]
}
pub fn test_from_openrpc() ! {
actor_spec_ := from_openrpc(openrpc_spec)!
assert actor_spec_.methods.len == actor_spec.methods.len
assert_methods_match(actor_spec_.methods[0], actor_spec.methods[0])
// assert from_openrpc(openrpc_spec)! == actor_spec
}
fn assert_methods_match(a ActorMethod, b ActorMethod) {
// Compare method names
assert a.name == b.name, 'Method names do not match: ${a.name} != ${b.name}'
// Compare summaries
assert a.summary == b.summary, 'Method summaries do not match for method ${a.name}.'
// Compare descriptions
assert a.description == b.description, 'Method descriptions do not match for method ${a.name}.'
// Compare parameters count
assert a.parameters.len == b.parameters.len, 'Parameter counts do not match for method ${a.name}.'
// Compare each parameter
for i, param_a in a.parameters {
assert_params_match(param_a, b.parameters[i], a.name)
}
// Compare result
assert_params_match(a.result, b.result, a.name)
}
fn assert_params_match(a openrpc.ContentDescriptor, b openrpc.ContentDescriptor, method_name string) {
// Compare parameter names
assert a.name == b.name, 'Parameter names do not match in method ${method_name}: ${a.name} != ${b.name}'
// Compare summaries
assert a.summary == b.summary, 'Parameter summaries do not match in method ${method_name}: ${a.name}'
// Compare descriptions
assert a.description == b.description, 'Parameter descriptions do not match in method ${method_name}: ${a.name}'
// Compare required flags
assert a.required == b.required, 'Required flags do not match in method ${method_name}: ${a.name}'
// Compare schemas
// assert_schemas_match(a.schema, b.schema, method_name, a.name)
}
// fn assert_schemas_match(a jsonschema.SchemaRef, b jsonschema.SchemaRef, method_name string, param_name string) {
// if a is Schema &&
// // Compare schema types
// assert a.typ == b.typ, 'Schema types do not match for parameter ${param_name} in method ${method_name}: ${a.typ} != ${b.typ}'
// // Compare schema titles
// assert a.title == b.title, 'Schema titles do not match for parameter ${param_name} in method ${method_name}.'
// // Compare schema descriptions
// assert a.description == b.description, 'Schema descriptions do not match for parameter ${param_name} in method ${method_name}.'
// // Compare other schema fields as needed (e.g., properties, additional properties, items, etc.)
// // Add more checks here if needed for deeper schema comparisons
// }

View File

@@ -1,200 +0,0 @@
module specification
import freeflowuniverse.herolib.core.code { Struct, Function }
import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.schemas.openrpc {ExamplePairing, ContentDescriptor, ErrorSpec}
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference}
pub struct ActorSpecification {
pub mut:
openapi ?openapi.OpenAPI
openrpc ?openrpc.OpenRPC
name string @[omitempty]
description string @[omitempty]
structure Struct @[omitempty]
interfaces []ActorInterface @[omitempty]
methods []ActorMethod @[omitempty]
objects []BaseObject @[omitempty]
}
pub enum ActorInterface {
openrpc
openapi
webui
command
http
}
pub struct ActorMethod {
pub:
name string @[omitempty]
description string @[omitempty]
summary string
example ExamplePairing
parameters []ContentDescriptor
result ContentDescriptor
errors []ErrorSpec
}
pub struct BaseObject {
pub mut:
schema Schema
new_method ?ActorMethod
get_method ?ActorMethod
set_method ?ActorMethod
delete_method ?ActorMethod
list_method ?ActorMethod
filter_method ?ActorMethod
other_methods []ActorMethod
}
pub enum MethodCategory {
base_object_new
base_object_get
base_object_set
base_object_delete
base_object_list
other
}
// returns whether method belongs to a given base object
// TODO: link to more info about base object methods
fn (m ActorMethod) belongs_to_object(obj BaseObject) bool {
base_obj_is_param := m.parameters
.filter(it.schema is Schema)
.map(it.schema as Schema)
.any(it.id == obj.schema.id)
base_obj_is_result := if m.result.schema is Schema {
m.result.schema.id == obj.schema.id
} else {
ref := m.result.schema as Reference
ref.ref.all_after_last('/') == obj.name()
}
return base_obj_is_param || base_obj_is_result
}
pub fn (s ActorSpecification) validate() ActorSpecification {
mut validated_objects := []BaseObject{}
for obj_ in s.objects {
mut obj := obj_
if obj.schema.id == '' {
obj.schema.id = obj.schema.title
}
methods := s.methods.filter(it.belongs_to_object(obj))
if m := methods.filter(it.is_new_method())[0] {
obj.new_method = m
}
if m := methods.filter(it.is_set_method())[0] {
obj.set_method = m
}
if m := methods.filter(it.is_get_method())[0] {
obj.get_method = m
}
if m := methods.filter(it.is_delete_method())[0] {
obj.delete_method = m
}
if m := methods.filter(it.is_list_method())[0] {
obj.list_method = m
}
validated_objects << BaseObject {
...obj
other_methods: methods.filter(!it.is_crudlf_method())
}
}
return ActorSpecification {
...s,
objects: validated_objects
}
}
// method category returns what category a method falls under
pub fn (s ActorSpecification) method_type(method ActorMethod) MethodCategory {
return if s.is_base_object_new_method(method) {
.base_object_new
} else if s.is_base_object_get_method(method) {
.base_object_get
} else if s.is_base_object_set_method(method) {
.base_object_set
} else if s.is_base_object_delete_method(method) {
.base_object_delete
} else if s.is_base_object_list_method(method) {
.base_object_list
} else {
.other
}
}
// a base object method is a method that is a
// CRUD+list+filter method of a base object
fn (s ActorSpecification) is_base_object_method(method ActorMethod) bool {
base_obj_is_param := method.parameters
.filter(it.schema is Schema)
.map(it.schema as Schema)
.any(it.id in s.objects.map(it.schema.id))
base_obj_is_result := if method.result.schema is Schema {
method.result.schema.id in s.objects.map(it.name())
} else {
ref := method.result.schema as Reference
ref.ref.all_after_last('/') in s.objects.map(it.name())
}
return base_obj_is_param || base_obj_is_result
}
fn (m ActorMethod) is_new_method() bool {
return m.name.starts_with('new')
}
fn (m ActorMethod) is_get_method() bool {
return m.name.starts_with('get')
}
fn (m ActorMethod) is_set_method() bool {
return m.name.starts_with('set')
}
fn (m ActorMethod) is_delete_method() bool {
return m.name.starts_with('delete')
}
fn (m ActorMethod) is_list_method() bool {
return m.name.starts_with('list')
}
fn (m ActorMethod) is_filter_method() bool {
return m.name.starts_with('filter')
}
fn (m ActorMethod) is_crudlf_method() bool {
return m.is_new_method() ||
m.is_get_method() ||
m.is_set_method() ||
m.is_delete_method() ||
m.is_list_method() ||
m.is_filter_method()
}
pub fn (o BaseObject) name() string {
return if o.schema.id.trim_space() != '' {
o.schema.id.trim_space()
} else {o.schema.title.trim_space()}
}
fn (s ActorSpecification) is_base_object_new_method(method ActorMethod) bool {
return s.is_base_object_method(method) && method.name.starts_with('new')
}
fn (s ActorSpecification) is_base_object_get_method(method ActorMethod) bool {
return s.is_base_object_method(method) && method.name.starts_with('get')
}
fn (s ActorSpecification) is_base_object_set_method(method ActorMethod) bool {
return s.is_base_object_method(method) && method.name.starts_with('set')
}
fn (s ActorSpecification) is_base_object_delete_method(method ActorMethod) bool {
return s.is_base_object_method(method) && method.name.starts_with('delete')
}
fn (s ActorSpecification) is_base_object_list_method(method ActorMethod) bool {
return s.is_base_object_method(method) && method.name.starts_with('list')
}

View File

@@ -1,91 +0,0 @@
module specification
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
import freeflowuniverse.herolib.schemas.openapi { MediaType, ResponseSpec, Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec }
import net.http
// Converts ActorSpecification to OpenAPI
pub fn (s ActorSpecification) to_openapi() OpenAPI {
if openapi_spec := s.openapi {
return openapi_spec
}
mut paths := map[string]PathItem{}
// Map ActorMethods to paths
for method in s.methods {
op := method.to_openapi_operation()
paths['${method.http_path()}'] = match method.http_method() {
.get { PathItem {get: op} }
else { panic('unsupported http method') }
}
// Assign operation to corresponding HTTP method
// TODO: what about other verbs
}
mut schemas := map[string]SchemaRef{}
for object in s.objects {
schemas[object.schema.id] = object.to_schema()
}
return OpenAPI{
openapi: '3.0.0',
info: Info{
title: s.name,
summary: s.description,
description: s.description,
version: '1.0.0',
},
servers: [
ServerSpec{
url: 'http://localhost:8080',
description: 'Default server',
},
],
paths: paths,
components: Components{
schemas: schemas
},
}
}
fn (bo BaseObject) to_schema() Schema {
return Schema{}
}
fn (m ActorMethod) http_path() string {
return m.name
}
fn (m ActorMethod) http_method() http.Method {
return .get
}
fn (method ActorMethod) to_openapi_operation() Operation {
mut op := Operation{
summary: method.summary,
description: method.description,
operation_id: method.name,
}
// Convert parameters to OpenAPI format
for param in method.parameters {
op.parameters << Parameter{
name: param.name,
in_: 'query', // Default to query parameters; adjust based on function context
description: param.description,
required: param.required,
schema: param.schema,
}
}
// if method.is_void()
op.responses['200'] = ResponseSpec {
description: method.description
content: {
'application/json': MediaType {
schema: method.result.schema
}
}
}
return op
}

View File

@@ -1,159 +0,0 @@
module specification
import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
import freeflowuniverse.herolib.schemas.openapi { Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec }
import freeflowuniverse.herolib.schemas.openrpc
const actor_spec = specification.ActorSpecification{
name: 'Petstore'
structure: code.Struct{
is_pub: false
}
interfaces: [.openrpc]
methods: [specification.ActorMethod{
name: 'list_pets'
summary: 'List all pets'
parameters: [openrpc.ContentDescriptor{
name: 'limit'
description: 'How many items to return at one time (max 100)'
required: false
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'integer'
minimum: 1
})
}]
result: openrpc.ContentDescriptor{
name: 'pets'
description: 'A paged array of pets'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'array'
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}))
})
}
}, specification.ActorMethod{
name: 'create_pet'
summary: 'Create a pet'
parameters: [openrpc.ContentDescriptor{
name: 'newPetName'
description: 'Name of pet to create'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}, openrpc.ContentDescriptor{
name: 'newPetTag'
description: 'Pet tag to create'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}]
}, specification.ActorMethod{
name: 'get_pet'
summary: 'Info for a specific pet'
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'Expected response to a valid request'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
})
}
}, specification.ActorMethod{
name: 'update_pet'
summary: 'Update a pet'
parameters: [openrpc.ContentDescriptor{
name: 'updatedPetName'
description: 'New name for the pet'
required: true
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}, openrpc.ContentDescriptor{
name: 'updatedPetTag'
description: 'New tag for the pet'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}]
result: openrpc.ContentDescriptor{
name: 'pet'
description: 'Updated pet object'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
})
}
}, specification.ActorMethod{
name: 'delete_pet'
summary: 'Delete a pet'
result: openrpc.ContentDescriptor{
name: 'success'
description: 'Boolean indicating success'
schema: jsonschema.SchemaRef(jsonschema.Schema{
typ: 'boolean'
})
}
}]
objects: [specification.BaseObject{
schema: jsonschema.Schema{
id: 'pet'
title: 'Pet'
typ: 'object'
properties: {
'id': jsonschema.SchemaRef(jsonschema.Reference{
ref: '#/components/schemas/PetId'
}),
'name': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
}),
'tag': jsonschema.SchemaRef(jsonschema.Schema{
typ: 'string'
})
}
required: ['id', 'name']
}
}]
}
// Converts ActorSpecification to OpenAPI
pub fn test_specification_to_openapi() {
panic(actor_spec.to_openapi())
}

View File

@@ -1,72 +0,0 @@
module specification
import freeflowuniverse.herolib.schemas.openrpc {OpenRPC, Components}
import freeflowuniverse.herolib.schemas.jsonschema {SchemaRef}
import freeflowuniverse.herolib.schemas.jsonschema.codegen { struct_to_schema }
// pub fn from_openrpc(spec openrpc.OpenRPC) !ActorSpecification {
// // Extract Actor metadata from OpenRPC info
// // actor_name := openrpc_doc.info.title
// // actor_description := openrpc_doc.info.description
// // // Generate methods
// // mut methods := []ActorMethod{}
// // for method in openrpc_doc.methods {
// // method_code := method.to_code()! // Using provided to_code function
// // methods << ActorMethod{
// // name: method.name
// // func: method_code
// // }
// // }
// // // Generate BaseObject structs from schemas
// // mut objects := []BaseObject{}
// // for key, schema_ref in openrpc_doc.components.schemas {
// // struct_obj := schema_ref.to_code()! // Assuming schema_ref.to_code() converts schema to Struct
// // // objects << BaseObject{
// // // structure: code.Struct{
// // // name: struct_obj.name
// // // }
// // // }
// // }
// // Build the Actor struct
// return ActorSpecification{
// // name: actor_name
// // description: actor_description
// // methods: methods
// // objects: objects
// }
// }
pub fn (specification ActorSpecification) to_openrpc() OpenRPC {
mut schemas := map[string]SchemaRef{}
for obj in specification.objects {
schemas[obj.schema.id] = obj.schema
// for child in obj.children {
// schemas[child.name] = struct_to_schema(child)
// }
}
return OpenRPC{
info: openrpc.Info{
title: specification.name.title()
version: '1.0.0'
}
methods: specification.methods.map(method_to_openrpc_method(it))
components: Components{
schemas: schemas
}
}
}
pub fn method_to_openrpc_method(method ActorMethod) openrpc.Method {
return openrpc.Method {
name: method.name
summary: method.summary
description: method.description
params: method.parameters.map(openrpc.ContentDescriptorRef(it))
result: openrpc.ContentDescriptorRef(method.result)
errors: method.errors.map(openrpc.ErrorRef(it))
}
}

View File

@@ -1,140 +0,0 @@
# Stage Module
The **Stage** module is a core component of the **Baobab** (Base Object and Actor Backend) library. It provides the infrastructure for handling RPC-based communication and managing the lifecycle of **Actors** and **Actions**. This module facilitates processing incoming requests, converting them to actions, and ensuring their correct execution.
## Architecture Overview
The **Stage** module operates based on the following architecture:
1. **RPC Request Handling**:
- An **Interface Handler** receives an RPC request. Supported interfaces include:
- **OpenRPC**
- **JSON-RPC**
- **OpenAPI**
2. **Action Creation**:
- The **Interface Handler** converts the incoming request into an **Action**, which represents the task to be executed.
3. **Action Execution**:
- The **Interface Handler** passes the **Action** to the **Director** for coordinated execution.
- (Note: Currently, the **Director** is not fully implemented. Actions are passed directly to the **Actor** for execution.)
4. **Actor Processing**:
- The **Actor** uses its `act` method to execute the **Action**.
- The result of the **Action** is stored in its `result` field, and the **Action** is returned.
5. **RPC Response Generation**:
- The **Interface Handler** converts the resulting **Action** back into the appropriate RPC response format and returns it.
---
## Key Components
### **Interface Handlers**
- **Responsibilities**:
- Receive and parse incoming RPC requests.
- Convert requests into **Actions**.
- Convert resulting **Actions** into appropriate RPC responses.
- Files:
- `interfaces/jsonrpc_interface.v`
- `interfaces/openapi_interface.v`
### **Director**
- **Responsibilities**:
- (Planned) Coordinate the execution of **Actions**.
- Handle retries, timeouts, and error recovery.
- File:
- `director.v`
### **Actors**
- **Responsibilities**:
- Execute **Actions** using their `act` method.
- Populate the `result` field of **Actions** with the execution result.
- File:
- `actor.v`
### **Actions**
- **Responsibilities**:
- Represent tasks to be executed by **Actors**.
- Carry results back after execution.
- File:
- `action.v`
### **Executor**
- **Responsibilities**:
- Manage the assignment of **Actions** to **Actors**.
- File:
- `executor.v`
---
## Directory Structure
```
stage/
interfaces/
jsonrpc_interface.v # Converts JSON-RPC requests to Actions
openapi_interface.v # Converts OpenAPI requests to Actions
actor.v # Defines the Actor and its behavior
action.v # Defines the Action structure and utilities
executor.v # Executes Actions on Actors
director.v # (Planned) Coordinates actors, actions, and retries
```
---
## Workflow Example
### 1. Receiving an RPC Request
An RPC request is received by an interface handler:
```json
{
"jsonrpc": "2.0",
"method": "doSomething",
"params": { "key": "value" },
"id": 1
}
```
### 2. Converting the Request to an Action
The interface handler converts the request into an **Action**:
```v
action := jsonrpc_interface.jsonrpc_to_action(request)
```
### 3. Executing the Action
The action is passed directly to an **Actor** for execution:
```v
actor := MyActor{id: "actor-1"}
resulting_action := actor.act(action)
```
### 4. Returning the RPC Response
The interface handler converts the resulting **Action** back into a JSON-RPC response:
```json
{
"jsonrpc": "2.0",
"result": { "status": "success", "data": "..." },
"id": 1
}
```
---
## Future Improvements
- **Director Implementation**:
- Add retries and timeout handling for actions.
- Provide better coordination for complex workflows.
- **Enhanced Interfaces**:
- Add support for more RPC protocols.
---
This module is a crucial building block of the **Baobab** library, designed to streamline RPC-based communication and task execution with flexibility and scalability.

View File

@@ -1,15 +0,0 @@
module stage
// import freeflowuniverse.herolib.core.smartid
pub struct Action {
pub mut:
id string
name string
priority int = 10 // 0 is highest, do 10 as default
params string // json encoded params
result string // can be used to remember outputs
// run bool = true // certain actions can be defined but meant to be executed directly
comments string
done bool // if done then no longer need to process
}

View File

@@ -1,47 +0,0 @@
module stage
import freeflowuniverse.herolib.core.redisclient
// Processor struct for managing procedure calls
pub struct Client {
pub mut:
rpc redisclient.RedisRpc // Redis RPC mechanism
}
// Parameters for processing a procedure call
@[params]
pub struct Params {
pub:
timeout int // Timeout in seconds
}
pub struct ClientConfig {
pub:
redis_url string // url to redis server running
redis_queue string // name of redis queue
}
pub fn new_client(config ClientConfig) !Client {
mut redis := redisclient.new(config.redis_url)!
mut rpc_q := redis.rpc_get(config.redis_queue)
return Client{
rpc: rpc_q
}
}
// Process the procedure call
pub fn (mut p Client) call_to_action(action Action, params Params) !Action {
// Use RedisRpc's `call` to send the call and wait for the response
response_data := p.rpc.call(redisclient.RPCArgs{
cmd: action.name
data: action.params
timeout: u64(params.timeout * 1000) // Convert seconds to milliseconds
wait: true
})!
return Action {
...action
result: response_data
}
}

View File

@@ -1,45 +0,0 @@
module stage
import freeflowuniverse.herolib.baobab.osis {OSIS}
@[heap]
pub interface IActor {
name string
mut:
act(Action) !Action
}
pub struct Actor {
pub:
name string
redis_url string = 'localhost:6379'
mut:
osis OSIS
}
pub fn new_actor(name string) !Actor {
return Actor{
osis: osis.new()!
name: name
}
}
pub fn (mut a IActor) handle(method string, data string) !string {
action := a.act(
name: method
params: data
)!
return action.result
}
// // Actor listens to the Redis queue for method invocations
// pub fn (mut a IActor) run() ! {
// mut redis := redisclient.new('localhost:6379') or { panic(err) }
// mut rpc := redis.rpc_get(a.name)
// println('Actor started and listening for tasks...')
// for {
// rpc.process(a.handle)!
// time.sleep(time.millisecond * 100) // Prevent CPU spinning
// }
// }

View File

@@ -1,16 +0,0 @@
module interfaces
import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.baobab.stage {Action}
pub fn action_from_jsonrpc_request(request jsonrpc.Request) Action {
return Action{
id: request.id
name: request.method
params: request.params
}
}
pub fn action_to_jsonrpc_response(action Action) jsonrpc.Response {
return jsonrpc.new_response(action.id, action.result)
}

View File

@@ -1,53 +0,0 @@
module interfaces
import rand
import x.json2 as json {Any}
import freeflowuniverse.herolib.baobab.stage {Action, Client}
import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.schemas.openapi
pub struct OpenAPIInterface {
pub mut:
client Client
}
pub fn new_openapi_interface(client Client) &OpenAPIInterface {
return &OpenAPIInterface{client}
}
pub fn (mut i OpenAPIInterface) handle(request openapi.Request) !openapi.Response {
// Convert incoming OpenAPI request to a procedure call
action := action_from_openapi_request(request)
println('debugzo3 ${action}')
response := i.client.call_to_action(action) or {
println('debugzo3.5 ${err.msg()}')
return err
}
println('debugzo4 ${response}')
return action_to_openapi_response(response)
}
pub fn action_from_openapi_request(request openapi.Request) Action {
mut params := []Any{}
if request.arguments.len > 0 {
params << request.arguments.values()
}
if request.body != '' {
params << request.body
}
if request.parameters.len > 0 {
params << json.encode(request.parameters)
}
return Action {
id: rand.uuid_v4()
name: request.operation.operation_id
params: json.encode(params.str())
}
}
pub fn action_to_openapi_response(action Action) openapi.Response {
return openapi.Response {
body: action.result
}
}

View File

@@ -1,29 +0,0 @@
module interfaces
import freeflowuniverse.herolib.baobab.stage {Client}
import freeflowuniverse.herolib.schemas.jsonrpc
// handler for test echoes JSONRPC Request as JSONRPC Response
fn handler(request jsonrpc.Request) !jsonrpc.Response {
return jsonrpc.Response {
jsonrpc: request.jsonrpc
id: request.id
result: request.params
}
}
pub struct OpenRPCInterface {
pub mut:
client Client
}
pub fn new_openrpc_interface(client Client) &OpenRPCInterface {
return &OpenRPCInterface{client}
}
pub fn (mut i OpenRPCInterface) handle(request jsonrpc.Request) !jsonrpc.Response {
// Convert incoming OpenAPI request to a procedure call
action := action_from_jsonrpc_request(request)
response := i.client.call_to_action(action)!
return action_to_jsonrpc_response(response)
}

View File

@@ -1,91 +0,0 @@
module interfaces
// import os
// import time
// import veb
// import x.json2 {Any}
// import net.http
import freeflowuniverse.herolib.baobab.stage {Action}
import freeflowuniverse.herolib.schemas.openapi {Request}
pub fn openapi_request_to_action(request Request) Action {
// // Convert incoming OpenAPI request to a procedure call
// mut params := []Any{}
// if request.arguments.len > 0 {
// params << request.arguments.values().map(it.str()).clone()
// }
// if request.body != '' {
// params << request.body
// }
// if request.parameters != '' {
// params << request.body
// }
// if request.parameters.len != 0 {
// mut param_map := map[string]Any{} // Store parameters with correct types
// for param_name, param_value in request.parameters {
// operation_param := request.operation.parameters.filter(it.name == param_name)
// if operation_param.len > 0 {
// param_schema := operation_param[0].schema as Schema
// param_type := param_schema.typ
// param_format := param_schema.format
// // Convert parameter value to corresponding type
// match param_type {
// 'integer' {
// match param_format {
// 'int32' {
// param_map[param_name] = param_value.int() // Convert to int
// }
// 'int64' {
// param_map[param_name] = param_value.i64() // Convert to i64
// }
// else {
// param_map[param_name] = param_value.int() // Default to int
// }
// }
// }
// 'string' {
// param_map[param_name] = param_value // Already a string
// }
// 'boolean' {
// param_map[param_name] = param_value.bool() // Convert to bool
// }
// 'number' {
// match param_format {
// 'float' {
// param_map[param_name] = param_value.f32() // Convert to float
// }
// 'double' {
// param_map[param_name] = param_value.f64() // Convert to double
// }
// else {
// param_map[param_name] = param_value.f64() // Default to double
// }
// }
// }
// else {
// param_map[param_name] = param_value // Leave as string for unknown types
// }
// }
// } else {
// // If the parameter is not defined in the OpenAPI operation, skip or log it
// println('Unknown parameter: $param_name')
// }
// }
// // Encode the parameter map to JSON if needed
// params << json.encode(param_map.str())
// }
// call := Action{
// name: request.operation.operation_id
// params_json: json2.encode(params.str()) // Keep as a string since ProcedureCall expects a string
// }
// return call
return Action{}
}

View File

@@ -1,43 +0,0 @@
module interfaces
import freeflowuniverse.herolib.schemas.openapi { OpenAPI }
import freeflowuniverse.herolib.baobab.stage {Client, ClientConfig}
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
import veb
pub struct HTTPServer {
veb.Controller
}
pub struct Context {
veb.Context
}
pub struct HTTPServerConfig {
ClientConfig
pub:
openapi_specification OpenAPI
openrpc_specification OpenRPC
}
pub fn new_http_server() !&HTTPServer {
mut s := &HTTPServer{}
// client := actor.new_client(cfg.ClientConfig)!
// openapi_proxy := new_openapi_proxy(
// client: new_client(cfg.ClientConfig)!
// specification: cfg.openapi_spec
// )
// mut openrpc_controller := openrpc.new_http_controller(
// specification: cfg.openrpc_specification
// handler: new_openrpc_interface(client)
// )
// s.register_controller[openrpc.HTTPController, Context]('/openrpc', mut openrpc_controller)!
return s
}
pub fn (mut server HTTPServer) run() {
veb.run[HTTPServer, Context](mut server, 8082)
}

View File

@@ -0,0 +1,153 @@
module livekit
// import time
// import rand
// import crypto.hmac
// import crypto.sha256
// import encoding.base64
// import json
// // Define AccessTokenOptions struct
// pub struct AccessTokenOptions {
// pub mut:
// ttl int | string // TTL in seconds or a time span (e.g., '2d', '5h')
// name string // Display name for the participant
// identity string // Identity of the user
// metadata string // Custom metadata to be passed to participants
// }
// // Struct representing grants
// pub struct ClaimGrants {
// pub mut:
// video VideoGrant
// iss string
// exp i64
// nbf int
// sub string
// name string
// }
// // VideoGrant struct placeholder
// pub struct VideoGrant {
// pub mut:
// room string
// room_join bool @[json: 'roomJoin']
// can_publish bool @[json: 'canPublish']
// can_publish_data bool @[json: 'canPublishData']
// can_subscribe bool @[json: 'canSubscribe']
// }
// // SIPGrant struct placeholder
// struct SIPGrant {}
// // AccessToken class
// pub struct AccessToken {
// mut:
// api_key string
// api_secret string
// grants ClaimGrants
// identity string
// ttl int | string
// }
// // Constructor for AccessToken
// pub fn new_access_token(api_key string, api_secret string, options AccessTokenOptions) !AccessToken {
// if api_key == '' || api_secret == '' {
// return error('API key and API secret must be set')
// }
// ttl := if options.ttl is int { options.ttl } else { 21600 } // Default TTL of 6 hours (21600 seconds)
// return AccessToken{
// api_key: api_key
// api_secret: api_secret
// identity: options.identity
// ttl: ttl
// grants: ClaimGrants{
// exp: time.now().unix()+ttl
// iss: api_key
// sub: options.name
// name: options.name
// }
// }
// }
// // Method to add a video grant to the token
// pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) {
// token.grants.video = grant
// }
// // Method to generate a JWT token
// pub fn (token AccessToken) to_jwt() !string {
// // Create JWT payload
// payload := json.encode(token.grants)
// println('payload: ${payload}')
// // Create JWT header
// header := '{"alg":"HS256","typ":"JWT"}'
// // Encode header and payload in base64
// header_encoded := base64.url_encode_str(header)
// payload_encoded := base64.url_encode_str(payload)
// // Create the unsigned token
// unsigned_token := '${header_encoded}.${payload_encoded}'
// // Create the HMAC-SHA256 signature
// signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, sha256.block_size)
// // Encode the signature in base64
// signature_encoded := base64.url_encode(signature)
// // Create the final JWT
// jwt := '${unsigned_token}.${signature_encoded}'
// return jwt
// }
// // TokenVerifier class
// pub struct TokenVerifier {
// api_key string
// api_secret string
// }
// // Constructor for TokenVerifier
// pub fn new_token_verifier(api_key string, api_secret string) !TokenVerifier {
// if api_key == '' || api_secret == '' {
// return error('API key and API secret must be set')
// }
// return TokenVerifier{
// api_key: api_key
// api_secret: api_secret
// }
// }
// // Method to verify the JWT token
// pub fn (verifier TokenVerifier) verify(token string) !ClaimGrants {
// // Split the token into parts
// parts := token.split('.')
// if parts.len != 3 {
// return error('Invalid token')
// }
// // Decode header, payload, and signature
// payload_encoded := parts[1]
// signature_encoded := parts[2]
// // Recompute the HMAC-SHA256 signature
// unsigned_token := '${parts[0]}.${parts[1]}'
// expected_signature := hmac.new(verifier.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, sha256.block_size)
// expected_signature_encoded := base64.url_encode(expected_signature)
// // Verify the signature
// if signature_encoded != expected_signature_encoded {
// return error('Invalid token signature')
// }
// // Decode the payload
// payload_json := base64.url_decode_str(payload_encoded)
// // Parse and return the claims as ClaimGrants
// return json.decode(ClaimGrants, payload_json)
// }

View File

@@ -0,0 +1,199 @@
module livekit
import net.http
import json
// // pub struct Client {
// // pub:
// // host string
// // token string
// // }
// // pub struct Room {
// // pub mut:
// // sid string
// // name string
// // empty_timeout string
// // max_participants string
// // creation_time string
// // turn_password string
// // metadata string
// // num_participants u32
// // active_recording bool
// // }
// pub struct ParticipantInfo {
// pub mut:
// sid string
// identity string
// name string
// state string
// tracks []TrackInfo
// metadata string
// joined_at i64
// permission ParticipantPermission
// is_publisher bool
// }
// pub struct TrackInfo {
// pub mut:
// sid string
// typ string @[json: 'type']
// source string
// name string
// mime_type string
// muted bool
// width u32
// height u32
// simulcast bool
// disable_dtx bool
// layers []VideoLayer
// }
// pub struct ParticipantPermission {
// pub mut:
// can_subscribe bool
// can_publish bool
// can_publish_data bool
// }
// pub struct VideoLayer {
// pub mut:
// quality string
// width u32
// height u32
// }
// // Helper method to make POST requests to LiveKit API
// fn (client Client) make_post_request(url string, body string) !http.Response {
// mut headers := http.new_header()
// headers.add_custom('Authorization', 'Bearer ${client.token}')!
// headers.add_custom('Content-Type', 'application/json')!
// req := http.Request{
// method: http.Method.post
// url: url
// data: body
// header: headers
// }
// return req.do()!
// }
// pub struct CreateRoomArgs {
// pub:
// name string
// empty_timeout u32
// max_participants u32
// metadata string
// }
// // RoomService API methods
// pub fn (client Client) create_room(args CreateRoomArgs) !Room {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/CreateRoom'
// response := client.make_post_request(url, body)!
// return json.decode(Room, response.body)!
// }
// // pub fn (client Client) list_rooms(names []string) ![]Room {
// // body := json.encode({
// // 'names': names
// // })
// // url := '${client.host}/twirp/livekit.RoomService/ListRooms'
// // response := client.make_post_request(url, body)!
// // return json.decode([]Room, response.body)!
// // }
// pub fn (client Client) delete_room(room_name string) ! {
// body := json.encode({
// 'room': room_name
// })
// url := '${client.host}/twirp/livekit.RoomService/DeleteRoom'
// _ := client.make_post_request(url, body)!
// }
// pub fn (client Client) list_participants(room_name string) ![]ParticipantInfo {
// body := json.encode({
// 'room': room_name
// })
// url := '${client.host}/twirp/livekit.RoomService/ListParticipants'
// response := client.make_post_request(url, body)!
// return json.decode([]ParticipantInfo, response.body)!
// }
// pub fn (client Client) get_participant(room_name string, identity string) !ParticipantInfo {
// body := json.encode({
// 'room': room_name
// 'identity': identity
// })
// url := '${client.host}/twirp/livekit.RoomService/GetParticipant'
// response := client.make_post_request(url, body)!
// return json.decode(ParticipantInfo, response.body)!
// }
// pub fn (client Client) remove_participant(room_name string, identity string) ! {
// body := json.encode({
// 'room': room_name
// 'identity': identity
// })
// url := '${client.host}/twirp/livekit.RoomService/RemoveParticipant'
// _ := client.make_post_request(url, body)!
// }
// pub struct MutePublishedTrackArgs {
// pub:
// room_name string
// identity string
// track_sid string
// muted bool
// }
// pub fn (client Client) mute_published_track(args MutePublishedTrackArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/MutePublishedTrack'
// _ := client.make_post_request(url, body)!
// }
// pub struct UpdateParticipantArgs {
// pub:
// room_name string @[json: 'room']
// identity string
// metadata string
// permission ParticipantPermission
// }
// pub fn (client Client) update_participant(args UpdateParticipantArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/UpdateParticipant'
// _ := client.make_post_request(url, body)!
// }
// pub struct UpdateRoomMetadataArgs {
// pub:
// room_name string @[json: 'room']
// metadata string
// }
// pub fn (client Client) update_room_metadata(args UpdateRoomMetadataArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/UpdateRoomMetadata'
// _ := client.make_post_request(url, body)!
// }
// pub struct SendDataArgs {
// pub:
// room_name string @[json: 'room']
// data []u8
// kind string
// destination_identities []string
// }
// pub fn (client Client) send_data(args SendDataArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/SendData'
// _ := client.make_post_request(url, body)!
// }

View File

@@ -1,39 +1,65 @@
module mycelium module mycelium
import json import json
import encoding.base64
import freeflowuniverse.herolib.core.httpconnection import freeflowuniverse.herolib.core.httpconnection
// Represents a destination for a message, can be either IP or public key
pub struct MessageDestination { pub struct MessageDestination {
pub: pub:
pk string ip string @[omitempty] // IP in the subnet of the receiver node
pk string @[omitempty] // hex encoded public key of the receiver node
} }
// Body of a message to be sent
pub struct PushMessageBody { pub struct PushMessageBody {
pub: pub:
dst MessageDestination dst MessageDestination
payload string topic ?string // optional message topic
payload string // base64 encoded message
} }
// Response containing message ID after pushing
pub struct PushMessageResponseId {
pub:
id string // hex encoded message ID
}
// A message received by the system
pub struct InboundMessage { pub struct InboundMessage {
pub: pub:
id string id string
src_ip string @[json: 'srcIP'] src_ip string @[json: 'srcIp'] // Sender overlay IP address
src_pk string @[json: 'srcPk'] src_pk string @[json: 'srcPk'] // Sender public key, hex encoded
dst_ip string @[json: 'dstIp'] dst_ip string @[json: 'dstIp'] // Receiver overlay IP address
dst_pk string @[json: 'dstPk'] dst_pk string @[json: 'dstPk'] // Receiver public key, hex encoded
payload string topic string // Optional message topic
payload string // Message payload, base64 encoded
} }
// Information about an outbound message
pub struct MessageStatusResponse { pub struct MessageStatusResponse {
pub: pub:
id string dst string // IP address of receiving node
dst string state string // pending, received, read, aborted or sending object
state string created i64 // Unix timestamp of creation
created string deadline i64 // Unix timestamp of expiry
deadline string msg_len int @[json: 'msgLen'] // Length in bytes
msg_len string @[json: 'msgLen']
} }
// General information about a node
pub struct Info {
pub:
node_subnet string @[json: 'nodeSubnet'] // subnet owned by node
}
// Response containing public key for a node IP
pub struct PublicKeyResponse {
pub:
node_pub_key string @[json: 'NodePubKey'] // hex encoded public key
}
// Get connection to mycelium server
pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection { pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection {
mut c := self.conn or { mut c := self.conn or {
mut c2 := httpconnection.new( mut c2 := httpconnection.new(
@@ -47,30 +73,63 @@ pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection {
return c return c
} }
pub fn (mut self Mycelium) send_msg(pk string, payload string, wait bool) !InboundMessage { @[params]
pub struct SendMessageArgs {
pub mut:
public_key string @[required]
payload string @[required]
topic ?string
wait bool
}
// Send a message to a node identified by public key
pub fn (mut self Mycelium) send_msg(args SendMessageArgs) !InboundMessage {
mut conn := self.connection()! mut conn := self.connection()!
mut params := { mut body := PushMessageBody{
'dst': json.encode(MessageDestination{ pk: pk }) dst: MessageDestination{
'payload': payload pk: args.public_key
ip: ''
}
payload: base64.encode_str(args.payload)
topic: if v := args.topic {
base64.encode_str(v)
} else {
none
}
} }
mut prefix := '' mut prefix := '/api/v1/messages'
if wait { if args.wait {
prefix = '?reply_timeout=120' prefix += '?reply_timeout=120'
} }
return conn.post_json_generic[InboundMessage]( return conn.post_json_generic[InboundMessage](
method: .post method: .post
prefix: prefix prefix: prefix
params: params data: json.encode(body)
dataformat: .json dataformat: .json
)! )!
} }
pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage { @[params]
pub struct ReceiveMessageArgs {
pub mut:
topic ?string
wait bool
peek bool
}
// Receive a message from the queue
pub fn (mut self Mycelium) receive_msg(args ReceiveMessageArgs) !InboundMessage {
mut conn := self.connection()! mut conn := self.connection()!
mut prefix := '' mut prefix := '/api/v1/messages?peek=${args.peek}&'
if wait {
prefix = '?timeout=60' if args.wait {
prefix += 'timeout=120&'
} }
if v := args.topic {
prefix += 'topic=${base64.encode_str(v)}'
}
return conn.get_json_generic[InboundMessage]( return conn.get_json_generic[InboundMessage](
method: .get method: .get
prefix: prefix prefix: prefix
@@ -78,17 +137,9 @@ pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage {
)! )!
} }
pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage { // Optional version of receive_msg that returns none on 204
mut conn := self.connection()! pub fn (mut self Mycelium) receive_msg_opt(args ReceiveMessageArgs) ?InboundMessage {
mut prefix := '' res := self.receive_msg(args) or {
if wait {
prefix = '?timeout=60'
}
res := conn.get_json_generic[InboundMessage](
method: .get
prefix: prefix
dataformat: .json
) or {
if err.msg().contains('204') { if err.msg().contains('204') {
return none return none
} }
@@ -97,25 +148,62 @@ pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage {
return res return res
} }
// Get status of a message by ID
pub fn (mut self Mycelium) get_msg_status(id string) !MessageStatusResponse { pub fn (mut self Mycelium) get_msg_status(id string) !MessageStatusResponse {
mut conn := self.connection()! mut conn := self.connection()!
return conn.get_json_generic[MessageStatusResponse]( return conn.get_json_generic[MessageStatusResponse](
method: .get method: .get
prefix: 'status/${id}' prefix: '/api/v1/messages/status/${id}'
dataformat: .json dataformat: .json
)! )!
} }
pub fn (mut self Mycelium) reply_msg(id string, pk string, payload string) ! { @[params]
pub struct ReplyMessageArgs {
pub mut:
id string @[required]
public_key string @[required]
payload string @[required]
topic ?string
}
// Reply to a message
pub fn (mut self Mycelium) reply_msg(args ReplyMessageArgs) ! {
mut conn := self.connection()! mut conn := self.connection()!
mut params := { mut body := PushMessageBody{
'dst': json.encode(MessageDestination{ pk: pk }) dst: MessageDestination{
'payload': payload pk: args.public_key
ip: ''
}
payload: base64.encode_str(args.payload)
topic: if v := args.topic { base64.encode_str(v) } else { none }
} }
conn.post_json_generic[json.Any]( _ := conn.post_json_str(
method: .post method: .post
prefix: 'reply/${id}' prefix: '/api/v1/messages/reply/${args.id}'
params: params data: json.encode(body)
dataformat: .json
)!
}
// curl -v -H 'Content-Type: application/json' -d '{"dst": {"pk": "be4bf135d60b7e43a46be1ad68f955cdc1209a3c55dc30d00c4463b1dace4377"}, "payload": "xuV+"}' http://localhost:8989/api/v1/messages\
// Get node info
pub fn (mut self Mycelium) get_info() !Info {
mut conn := self.connection()!
return conn.get_json_generic[Info](
method: .get
prefix: '/api/v1/admin'
dataformat: .json
)!
}
// Get public key for a node IP
pub fn (mut self Mycelium) get_pubkey_from_ip(ip string) !PublicKeyResponse {
mut conn := self.connection()!
return conn.get_json_generic[PublicKeyResponse](
method: .get
prefix: '/api/v1/pubkey/${ip}'
dataformat: .json dataformat: .json
)! )!
} }

View File

@@ -0,0 +1,71 @@
module mycelium
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.installers.lang.rust
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.osal.screen
import freeflowuniverse.herolib.ui
import freeflowuniverse.herolib.sysadmin.startupmanager
import os
import time
import json
pub fn check() bool {
// if core.is_osx()! {
// mut scr := screen.new(reset: false) or {return False}
// name := 'mycelium'
// if !scr.exists(name) {
// return false
// }
// }
// if !(osal.process_exists_byname('mycelium') or {return False}) {
// return false
// }
// TODO: might be dangerous if that one goes out
ping_result := osal.ping(address: '40a:152c:b85b:9646:5b71:d03a:eb27:2462', retry: 2) or {
return false
}
if ping_result == .ok {
console.print_debug('could reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
return true
}
console.print_stderr('could not reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
return false
}
pub struct MyceliumInspectResult {
pub:
public_key string @[json: publicKey]
address string
}
@[params]
pub struct MyceliumInspectArgs {
pub:
key_file_path string = '/root/hero/cfg/priv_key.bin'
}
pub fn inspect(args MyceliumInspectArgs) !MyceliumInspectResult {
command := 'mycelium inspect --key-file ${args.key_file_path} --json'
result := os.execute(command)
if result.exit_code != 0 {
return error('Command failed: ${result.output}')
}
inspect_result := json.decode(MyceliumInspectResult, result.output) or {
return error('Failed to parse JSON: ${err}')
}
return inspect_result
}
// if returns empty then probably mycelium is not installed
pub fn ipaddr() string {
r := inspect() or { MyceliumInspectResult{} }
return r.address
}

View File

@@ -2,8 +2,6 @@ module mycelium
import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.encoderhero
__global ( __global (
mycelium_global map[string]&Mycelium mycelium_global map[string]&Mycelium
@@ -12,35 +10,71 @@ __global (
/////////FACTORY /////////FACTORY
// set the model in mem and the config on the filesystem @[params]
pub struct ArgsGet {
pub mut:
name string
}
fn args_get(args_ ArgsGet) ArgsGet {
mut args := args_
if args.name == '' {
args.name = 'default'
}
return args
}
pub fn get(args_ ArgsGet) !&Mycelium {
mut context := base.context()!
mut args := args_get(args_)
mut obj := Mycelium{}
if args.name !in mycelium_global {
if !exists(args)! {
set(obj)!
} else {
heroscript := context.hero_config_get('mycelium', args.name)!
mut obj_ := heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return mycelium_global[args.name] or {
println(mycelium_global)
// bug if we get here because should be in globals
panic('could not get config for mycelium with name, is bug:${args.name}')
}
}
// register the config for the future
pub fn set(o Mycelium) ! { pub fn set(o Mycelium) ! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set('mycelium', o.name, heroscript)!
}
// does the config exists?
pub fn exists(args_ ArgsGet) !bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists('mycelium', args.name)
}
pub fn delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('mycelium', args.name)!
if args.name in mycelium_global {
// del mycelium_global[args.name]
}
}
// only sets in mem, does not set as config
fn set_in_mem(o Mycelium) ! {
mut o2 := obj_init(o)! mut o2 := obj_init(o)!
mycelium_global[o.name] = &o2 mycelium_global[o.name] = &o2
mycelium_default = o.name mycelium_default = o.name
} }
// check we find the config on the filesystem
pub fn exists(args_ ArgsGet) bool {
mut model := args_get(args_)
mut context := base.context() or { panic('bug') }
return context.hero_config_exists('mycelium', model.name)
}
// load the config error if it doesn't exist
pub fn load(args_ ArgsGet) ! {
mut model := args_get(args_)
mut context := base.context()!
mut heroscript := context.hero_config_get('mycelium', model.name)!
play(heroscript: heroscript)!
}
// save the config to the filesystem in the context
pub fn save(o Mycelium) ! {
mut context := base.context()!
heroscript := encoderhero.encode[Mycelium](o)!
context.hero_config_set('mycelium', model.name, heroscript)!
}
@[params] @[params]
pub struct PlayArgs { pub struct PlayArgs {
pub mut: pub mut:
@@ -50,21 +84,28 @@ pub mut:
} }
pub fn play(args_ PlayArgs) ! { pub fn play(args_ PlayArgs) ! {
mut model := args_ mut args := args_
if model.heroscript == '' { mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
model.heroscript = heroscript_default()!
}
mut plbook := model.plbook or { playbook.new(text: model.heroscript)! }
mut configure_actions := plbook.find(filter: 'mycelium.configure')! mut install_actions := plbook.find(filter: 'mycelium.configure')!
if configure_actions.len > 0 { if install_actions.len > 0 {
for config_action in configure_actions { for install_action in install_actions {
mut p := config_action.params heroscript := install_action.heroscript()
mycfg := cfg_play(p)! mut obj2 := heroscript_loads(heroscript)!
console.print_debug('install action mycelium.configure\n${mycfg}') set(obj2)!
set(mycfg)!
save(mycfg)!
} }
} }
} }
// switch instance to be used for mycelium
pub fn switch(name string) {
mycelium_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,39 +1,33 @@
module mycelium module mycelium
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.core.httpconnection import freeflowuniverse.herolib.core.httpconnection
import os import freeflowuniverse.herolib.data.encoderhero
pub const version = '0.0.0' pub const version = '0.0.0'
const singleton = true const singleton = true
const default = true const default = true
pub fn heroscript_default() !string {
heroscript := "
!!mycelium.configure
name:'mycelium'
"
return heroscript
}
@[heap] @[heap]
pub struct Mycelium { pub struct Mycelium {
pub mut: pub mut:
name string = 'default' name string = 'default'
server_url string server_url string = 'http://localhost:8989'
conn ?&httpconnection.HTTPConnection conn ?&httpconnection.HTTPConnection @[skip; str: skip]
} }
fn cfg_play(p paramsparser.Params) ! { // your checking & initialization code if needed
mut mycfg := Mycelium{ fn obj_init(mycfg_ Mycelium) !Mycelium {
name: p.get_default('name', 'default')! mut mycfg := mycfg_
server_url: p.get_default('server_url', 'http://localhost:8989/api/v1/messages')! return mycfg
}
set(mycfg)!
} }
fn obj_init(obj_ Mycelium) !Mycelium { /////////////NORMALLY NO NEED TO TOUCH
// never call get here, only thing we can do here is work on object itself
mut obj := obj_ pub fn heroscript_dumps(obj Mycelium) !string {
return encoderhero.encode[Mycelium](obj)!
}
pub fn heroscript_loads(heroscript string) !Mycelium {
mut obj := encoderhero.decode[Mycelium](heroscript)!
return obj return obj
} }

View File

@@ -0,0 +1,602 @@
openapi: 3.0.2
info:
version: '1.0.0'
title: Mycelium management
contact:
url: 'https://github.com/threefoldtech/mycelium'
license:
name: Apache 2.0
url: 'https://github.com/threefoldtech/mycelium/blob/master/LICENSE'
description: |
This is the specification of the **mycelium** management API. It is used to perform admin tasks on the system, and
to perform administrative duties.
externalDocs:
description: For full documentation, check out the mycelium github repo.
url: 'https://github.com/threefoldtech/mycelium'
tags:
- name: Admin
description: Administrative operations
- name: Peer
description: Operations related to peer management
- name: Route
description: Operations related to network routes
- name: Message
description: Operations on the embedded message subsystem
servers:
- url: 'http://localhost:8989'
paths:
'/api/v1/admin':
get:
tags:
- Admin
summary: Get general info about the node
description: |
Get general info about the node, which is not related to other more specific functionality
operationId: getInfo
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Info'
'/api/v1/admin/peers':
get:
tags:
- Admin
- Peer
summary: List known peers
description: |
List all peers known in the system, and info about their connection.
This includes the endpoint, how we know about the peer, the connection state, and if the connection is alive the amount
of bytes we've sent to and received from the peer.
operationId: getPeers
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PeerStats'
post:
tags:
- Admin
- Peer
summary: Add a new peer
description: |
Add a new peer identified by the provided endpoint.
The peer is added to the list of known peers. It will eventually be connected
to by the standard connection loop of the peer manager. This means that a peer
which can't be connected to will stay in the system, as it might be reachable
later on.
operationId: addPeer
responses:
'204':
description: Peer added
'400':
description: Malformed endpoint
content:
text/plain:
schema:
type: string
description: Details about why the endpoint is not valid
'409':
description: Peer already exists
content:
text/plain:
schema:
type: string
description: message saying we already know this peer
'/api/v1/admin/peers/{endpoint}':
delete:
tags:
- Admin
- Peer
summary: Remove an existing peer
description: |
Remove an existing peer identified by the provided endpoint.
The peer is removed from the list of known peers. If a connection to it
is currently active, it will be closed.
operationId: deletePeer
responses:
'204':
description: Peer removed
'400':
description: Malformed endpoint
content:
text/plain:
schema:
type: string
description: Details about why the endpoint is not valid
'404':
description: Peer doesn't exist
content:
text/plain:
schema:
type: string
description: message saying we don't know this peer
'/api/v1/admin/routes/selected':
get:
tags:
- Admin
- Route
summary: List all selected routes
description: |
List all selected routes in the system, and their next hop identifier, metric and sequence number.
It is possible for a route to be selected and have an infinite metric. This route will however not forward packets.
operationId: getSelectedRoutes
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Route'
'/api/v1/admin/routes/fallback':
get:
tags:
- Admin
- Route
summary: List all active fallback routes
description: |
List all fallback routes in the system, and their next hop identifier, metric and sequence number.
These routes are available to be selected in case the selected route for a destination suddenly fails, or gets retracted.
operationId: getSelectedRoutes
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Route'
'/api/v1/messages':
get:
tags:
- Message
summary: Get a message from the inbound message queue
description: |
Get a message from the inbound message queue. By default, the message is removed from the queue and won't be shown again.
If the peek query parameter is set to true, the message will be peeked, and the next call to this endpoint will show the same message.
This method returns immediately by default: a message is returned if one is ready, and if there isn't nothing is returned. If the timeout
query parameter is set, this call won't return for the given amount of seconds, unless a message is received
operationId: popMessage
parameters:
- in: query
name: peek
required: false
schema:
type: boolean
description: Whether to peek the message or not. If this is true, the message won't be removed from the inbound queue when it is read
example: true
- in: query
name: timeout
required: false
schema:
type: integer
format: int64
minimum: 0
description: |
Amount of seconds to wait for a message to arrive if one is not available. Setting this to 0 is valid and will return
a message if present, or return immediately if there isn't
example: 60
- in: query
name: topic
required: false
schema:
type: string
format: byte
minLength: 0
maxLength: 340
description: |
Optional filter for loading messages. If set, the system checks if the message has the given string at the start. This way
a topic can be encoded.
example: example.topic
responses:
'200':
description: Message retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/InboundMessage'
'204':
description: No message ready
post:
tags:
- Message
summary: Submit a new message to the system.
description: |
Push a new message to the systems outbound message queue. The system will continuously attempt to send the message until
it is either fully transmitted, or the send deadline is expired.
operationId: pushMessage
parameters:
- in: query
name: reply_timeout
required: false
schema:
type: integer
format: int64
minimum: 0
description: |
Amount of seconds to wait for a reply to this message to come in. If not set, the system won't wait for a reply and return
the ID of the message, which can be used later. If set, the system will wait for at most the given amount of seconds for a reply
to come in. If a reply arrives, it is returned to the client. If not, the message ID is returned for later use.
example: 120
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageBody'
responses:
'200':
description: We received a reply within the specified timeout
content:
application/json:
schema:
$ref: '#/components/schemas/InboundMessage'
'201':
description: Message pushed successfully, and not waiting for a reply
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageResponseId'
'408':
description: The system timed out waiting for a reply to the message
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageResponseId'
'/api/v1/messsages/reply/{id}':
post:
tags:
- Message
summary: Reply to a message with the given ID
description: |
Submits a reply message to the system, where ID is an id of a previously received message. If the sender is waiting
for a reply, it will bypass the queue of open messages.
operationId: pushMessageReply
parameters:
- in: path
name: id
required: true
schema:
type: string
format: hex
minLength: 16
maxLength: 16
example: abcdef0123456789
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageBody'
responses:
'204':
description: successfully submitted the reply
'/api/v1/messages/status/{id}':
get:
tags:
- Message
summary: Get the status of an outbound message
description: |
Get information about the current state of an outbound message. This can be used to check the transmission
state, size and destination of the message.
operationId: getMessageInfo
parameters:
- in: path
name: id
required: true
schema:
type: string
format: hex
minLength: 16
maxLength: 16
example: abcdef0123456789
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/MessageStatusResponse'
'404':
description: Message not found
'/api/v1/pubkey/{mycelium_ip}':
get:
summary: Get the pubkey from node ip
description: |
Get the node's public key from it's IP address.
operationId: getPublicKeyFromIp
parameters:
- in: path
name: mycelium_ip
required: true
schema:
type: string
format: ipv6
example: 5fd:7636:b80:9ad0::1
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/PublicKeyResponse'
'404':
description: Public key not found
components:
schemas:
Info:
description: General information about a node
type: object
properties:
nodeSubnet:
description: The subnet owned by the node and advertised to peers
type: string
example: 54f:b680:ba6e:7ced::/64
Endpoint:
description: Identification to connect to a peer
type: object
properties:
proto:
description: Protocol used
type: string
enum:
- 'tcp'
- 'quic'
example: tcp
socketAddr:
description: The socket address used
type: string
example: 192.0.2.6:9651
PeerStats:
description: Info about a peer
type: object
properties:
endpoint:
$ref: '#/components/schemas/Endpoint'
type:
description: How we know about this peer
type: string
enum:
- 'static'
- 'inbound'
- 'linkLocalDiscovery'
example: static
connectionState:
description: The current state of the connection to the peer
type: string
enum:
- 'alive'
- 'connecting'
- 'dead'
example: alive
txBytes:
description: The amount of bytes transmitted to this peer
type: integer
format: int64
minimum: 0
example: 464531564
rxBytes:
description: The amount of bytes received from this peer
type: integer
format: int64
minimum: 0
example: 64645089
Route:
description: Information about a route
type: object
properties:
subnet:
description: The overlay subnet for which this is the route
type: string
example: 469:1348:ab0c:a1d8::/64
nextHop:
description: A way to identify the next hop of the route, where forwarded packets will be sent
type: string
example: TCP 203.0.113.2:60128 <-> 198.51.100.27:9651
metric:
description: The metric of the route, an estimation of how long the packet will take to arrive at its final destination
oneOf:
- description: A finite metric value
type: integer
format: int32
minimum: 0
maximum: 65534
example: 13
- description: An infinite (unreachable) metric. This is always `infinite`
type: string
example: infinite
seqno:
description: the sequence number advertised with this route by the source
type: integer
format: int32
minimum: 0
maximum: 65535
example: 1
InboundMessage:
description: A message received by the system
type: object
properties:
id:
description: Id of the message, hex encoded
type: string
format: hex
minLength: 16
maxLength: 16
example: 0123456789abcdef
srcIp:
description: Sender overlay IP address
type: string
format: ipv6
example: 449:abcd:0123:defa::1
srcPk:
description: Sender public key, hex encoded
type: string
format: hex
minLength: 64
maxLength: 64
example: fedbca9876543210fedbca9876543210fedbca9876543210fedbca9876543210
dstIp:
description: Receiver overlay IP address
type: string
format: ipv6
example: 34f:b680:ba6e:7ced:355f:346f:d97b:eecb
dstPk:
description: Receiver public key, hex encoded. This is the public key of the system
type: string
format: hex
minLength: 64
maxLength: 64
example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf
topic:
description: An optional message topic
type: string
format: byte
minLength: 0
maxLength: 340
example: hpV+
payload:
description: The message payload, encoded in standard alphabet base64
type: string
format: byte
example: xuV+
PushMessageBody:
description: A message to send to a given receiver
type: object
properties:
dst:
$ref: '#/components/schemas/MessageDestination'
topic:
description: An optional message topic
type: string
format: byte
minLength: 0
maxLength: 340
example: hpV+
payload:
description: The message to send, base64 encoded
type: string
format: byte
example: xuV+
MessageDestination:
oneOf:
- description: An IP in the subnet of the receiver node
type: object
properties:
ip:
description: The target IP of the message
format: ipv6
example: 449:abcd:0123:defa::1
- description: The hex encoded public key of the receiver node
type: object
properties:
pk:
description: The hex encoded public key of the target node
type: string
minLength: 64
maxLength: 64
example: bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32
PushMessageResponseId:
description: The ID generated for a message after pushing it to the system
type: object
properties:
id:
description: Id of the message, hex encoded
type: string
format: hex
minLength: 16
maxLength: 16
example: 0123456789abcdef
MessageStatusResponse:
description: Information about an outbound message
type: object
properties:
dst:
description: IP address of the receiving node
type: string
format: ipv6
example: 449:abcd:0123:defa::1
state:
$ref: '#/components/schemas/TransmissionState'
created:
description: Unix timestamp of when this message was created
type: integer
format: int64
example: 1649512789
deadline:
description: Unix timestamp of when this message will expire. If the message is not received before this, the system will give up
type: integer
format: int64
example: 1649513089
msgLen:
description: Length of the message in bytes
type: integer
minimum: 0
example: 27
TransmissionState:
description: The state of an outbound message in it's lifetime
oneOf:
- type: string
enum: ['pending', 'received', 'read', 'aborted']
example: 'received'
- type: object
properties:
sending:
type: object
properties:
pending:
type: integer
minimum: 0
example: 5
sent:
type: integer
minimum: 0
example: 17
acked:
type: integer
minimum: 0
example: 3
example: 'received'
PublicKeyResponse:
description: Public key requested based on a node's IP
type: object
properties:
NodePubKey:
type: string
format: hex
minLength: 64
maxLength: 64
example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf

View File

@@ -1,6 +1,13 @@
# Mycelium Client # Mycelium Client
A V client library for interacting with the Mycelium messaging system. This client provides functionality for sending, receiving, and managing messages through a Mycelium server. A V client library for interacting with the Mycelium messaging system. This client provides functionality for configuring and inspecting a Mycelium node.
## Components
The Mycelium integration consists of two main components:
1. **Mycelium Client** (this package) - For interacting with a running Mycelium node
2. **Mycelium Installer** (in `installers/net/mycelium/`) - For installing and managing Mycelium nodes
## Configuration ## Configuration
@@ -11,131 +18,101 @@ The client can be configured either through V code or using heroscript.
```v ```v
import freeflowuniverse.herolib.clients.mycelium import freeflowuniverse.herolib.clients.mycelium
// Get default client instance
mut client := mycelium.get()! mut client := mycelium.get()!
// By default connects to http://localhost:8989/api/v1/messages // Get named client instance
// To use a different server: mut client := mycelium.get(name: "custom")!
mut client := mycelium.get(name: "custom", server_url: "http://myserver:8989/api/v1/messages")!
``` ```
### Heroscript Configuration ## Core Functions
```hero ### Inspect Node
!!mycelium.configure
name:'custom' # optional, defaults to 'default'
server_url:'http://myserver:8989/api/v1/messages' # optional, defaults to localhost:8989
```
Note: Configuration is not needed if using a locally running Mycelium server with default settings. Get information about the local Mycelium node:
## Example Script ```v
import freeflowuniverse.herolib.clients.mycelium
Save as `mycelium_example.vsh`:
// Get node info including public key and address
result := mycelium.inspect()!
println('Public Key: ${result.public_key}')
println('Address: ${result.address}')
// Get just the IP address
addr := mycelium.ipaddr()
println('IP Address: ${addr}')
```
### Check Node Status
Check if the Mycelium node is running and reachable:
```v
import freeflowuniverse.herolib.clients.mycelium
is_running := mycelium.check()
if is_running {
println('Mycelium node is running and reachable')
} else {
println('Mycelium node is not running or unreachable')
}
```
### Sending and Receiving Messages
The client provides several functions for sending and receiving messages between nodes:
```v ```v
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.mycelium import freeflowuniverse.herolib.clients.mycelium
// Initialize client
mut client := mycelium.get()! mut client := mycelium.get()!
// Send a message and wait for reply // Send a message to a node by public key
// Parameters: public_key, payload, topic, wait_for_reply
msg := client.send_msg( msg := client.send_msg(
pk: "recipient_public_key" 'abc123...', // destination public key
payload: "Hello!" 'Hello World', // message payload
wait: true // wait for reply (timeout 120s) 'greetings', // optional topic
true // wait for reply
)!
println('Sent message ID: ${msg.id}')
// Receive messages
// Parameters: wait_for_message, peek_only, topic_filter
received := client.receive_msg(true, false, 'greetings')!
println('Received message from: ${received.src_pk}')
println('Message payload: ${received.payload}')
// Reply to a message
client.reply_msg(
received.id, // original message ID
received.src_pk, // sender's public key
'Got your message!', // reply payload
'greetings' // topic
)! )!
println('Message sent with ID: ${msg.id}')
// Check message status // Check message status
status := client.get_msg_status(msg.id)! status := client.get_msg_status(msg.id)!
println('Message status: ${status.state}') println('Message status: ${status.state}')
println('Created at: ${status.created}')
// Receive messages with timeout println('Expires at: ${status.deadline}')
if incoming := client.receive_msg_opt(wait: true) {
println('Received message: ${incoming.payload}')
println('From: ${incoming.src_pk}')
// Reply to the message
client.reply_msg(
id: incoming.id
pk: incoming.src_pk
payload: "Got your message!"
)!
}
``` ```
## API Reference The messaging API supports:
- Sending messages to nodes identified by public key
- Optional message topics for filtering
- Waiting for replies when sending messages
- Peeking at messages without removing them from the queue
- Tracking message delivery status
- Base64 encoded message payloads for binary data
### Sending Messages ## Installation and Management
```v For installing and managing Mycelium nodes, use the Mycelium Installer package located in `installers/net/mycelium/`. The installer provides functionality for:
// Send a message to a specific public key
// wait=true means wait for reply (timeout 120s)
msg := client.send_msg(pk: "recipient_public_key", payload: "Hello!", wait: true)!
// Get status of a sent message - Installing Mycelium nodes
status := client.get_msg_status(id: "message_id")! - Starting/stopping nodes
``` - Managing node configuration
- Setting up TUN interfaces
### Receiving Messages - Configuring peer connections
```v
// Receive a message (non-blocking)
msg := client.receive_msg(wait: false)!
// Receive a message with timeout (blocking for 60s)
msg := client.receive_msg(wait: true)!
// Receive a message (returns none if no message available)
if msg := client.receive_msg_opt(wait: false) {
println('Received: ${msg.payload}')
}
```
### Replying to Messages
```v
// Reply to a specific message
client.reply_msg(
id: "original_message_id",
pk: "sender_public_key",
payload: "Reply message"
)!
```
## Message Types
### InboundMessage
```v
struct InboundMessage {
id string
src_ip string
src_pk string
dst_ip string
dst_pk string
payload string
}
```
### MessageStatusResponse
```v
struct MessageStatusResponse {
id string
dst string
state string
created string
deadline string
msg_len string
}
```
## Heroscript Complete Example
```hero
!!mycelium.configure
name:'mycelium'
server_url:'http://localhost:8989/api/v1/messages'
# More heroscript commands can be added here as the API expands

View File

@@ -0,0 +1,148 @@
# Code Model
A set of models that represent code structures, such as structs, functions, imports, and constants. The motivation behind this module is to provide a more generic and lighter alternative to v.ast code models, that can be used for code parsing and code generation across multiple languages.
## Features
- **Struct Modeling**: Complete struct representation including:
- Fields with types, visibility, and mutability
- Embedded structs
- Generic type support
- Attributes
- Documentation comments
- **Function Modeling**: Comprehensive function support with:
- Parameters and return types
- Receiver methods
- Optional and result types
- Function body content
- Visibility modifiers
- **Type System**: Rich type representation including:
- Basic types
- Reference types
- Arrays and maps
- Optional and result types
- Mutable and shared types
- **Code Organization**:
- Import statements with module and type specifications
- Constants (both single and grouped)
- Custom code blocks for specialized content
- Documentation through single and multi-line comments
## Using Codemodel
The codemodel module provides a set of types and utilities for working with code structures. Here are some examples of how to use the module:
### Working with Functions
```v
// Parse a function definition
fn_def := 'pub fn (mut app App) process() !string'
function := codemodel.parse_function(fn_def)!
println(function.name) // prints: process
println(function.receiver.name) // prints: app
println(function.result.typ.symbol) // prints: string
// Create a function model
my_fn := Function{
name: 'add'
is_pub: true
params: [
Param{
name: 'x'
typ: Type{symbol: 'int'}
},
Param{
name: 'y'
typ: Type{symbol: 'int'}
}
]
result: Result{
typ: Type{symbol: 'int'}
}
body: 'return x + y'
}
```
### Working with Imports
```v
// Parse an import statement
import_def := 'import os { exists }'
imp := codemodel.parse_import(import_def)
println(imp.mod) // prints: os
println(imp.types) // prints: ['exists']
// Create an import model
my_import := Import{
mod: 'json'
types: ['encode', 'decode']
}
```
### Working with Constants
```v
// Parse constant definitions
const_def := 'const max_size = 1000'
constant := codemodel.parse_const(const_def)!
println(constant.name) // prints: max_size
println(constant.value) // prints: 1000
// Parse grouped constants
const_block := 'const (
pi = 3.14
e = 2.718
)'
constants := codemodel.parse_consts(const_block)!
```
### Working with Types
The module provides rich type modeling capabilities:
```v
// Basic type
basic := Type{
symbol: 'string'
}
// Array type
array := Type{
symbol: 'string'
is_array: true
}
// Optional type
optional := Type{
symbol: 'int'
is_optional: true
}
// Result type
result := Type{
symbol: 'string'
is_result: true
}
```
## Code Generation
The codemodel types can be used as intermediate structures for code generation. For example, generating documentation:
```v
mut doc := ''
// Document a struct
for field in my_struct.fields {
doc += '- ${field.name}: ${field.typ.symbol}'
if field.description != '' {
doc += ' // ${field.description}'
}
doc += '\n'
}
```
The codemodel module provides a foundation for building tools that need to work with code structures, whether for analysis, transformation, or generation purposes.

View File

@@ -1,11 +1,10 @@
module code module codemodel
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.core.pathlib
import os import os
pub struct CodeFile {
pub struct VFile {
pub mut: pub mut:
name string name string
mod string mod string
@@ -15,15 +14,15 @@ pub mut:
content string content string
} }
pub fn new_file(config VFile) VFile { pub fn new_file(config CodeFile) CodeFile {
return VFile{ return CodeFile{
...config ...config
mod: texttools.name_fix(config.mod) mod: texttools.name_fix(config.mod)
items: config.items items: config.items
} }
} }
pub fn (mut file VFile) add_import(import_ Import) ! { pub fn (mut file CodeFile) add_import(import_ Import) ! {
for mut i in file.imports { for mut i in file.imports {
if i.mod == import_.mod { if i.mod == import_.mod {
i.add_types(import_.types) i.add_types(import_.types)
@@ -33,7 +32,7 @@ pub fn (mut file VFile) add_import(import_ Import) ! {
file.imports << import_ file.imports << import_
} }
pub fn (code VFile) write(path string, options WriteOptions) ! { pub fn (code CodeFile) write_v(path string, options WriteOptions) ! {
filename := '${options.prefix}${texttools.name_fix(code.name)}.v' filename := '${options.prefix}${texttools.name_fix(code.name)}.v'
mut filepath := pathlib.get('${path}/${filename}') mut filepath := pathlib.get('${path}/${filename}')
@@ -59,21 +58,16 @@ pub fn (code VFile) write(path string, options WriteOptions) ! {
} }
mut file := pathlib.get_file( mut file := pathlib.get_file(
path: filepath.path path: filepath.path
create: true create: true
)! )!
file.write('module ${code.mod}\n${imports_str}\n${consts_str}\n${code_str}')!
mod_stmt := if code.mod == '' {''} else {
'module ${code.mod}'
}
file.write('${mod_stmt}\n${imports_str}\n${consts_str}${code_str}')!
if options.format { if options.format {
os.execute('v fmt -w ${file.path}') os.execute('v fmt -w ${file.path}')
} }
} }
pub fn (file VFile) get_function(name string) ?Function { pub fn (file CodeFile) get_function(name string) ?Function {
functions := file.items.filter(it is Function).map(it as Function) functions := file.items.filter(it is Function).map(it as Function)
target_lst := functions.filter(it.name == name) target_lst := functions.filter(it.name == name)
@@ -86,7 +80,7 @@ pub fn (file VFile) get_function(name string) ?Function {
return target_lst[0] return target_lst[0]
} }
pub fn (mut file VFile) set_function(function Function) ! { pub fn (mut file CodeFile) set_function(function Function) ! {
function_names := file.items.map(if it is Function { it.name } else { '' }) function_names := file.items.map(if it is Function { it.name } else { '' })
index := function_names.index(function.name) index := function_names.index(function.name)
@@ -96,10 +90,10 @@ pub fn (mut file VFile) set_function(function Function) ! {
file.items[index] = function file.items[index] = function
} }
pub fn (file VFile) functions() []Function { pub fn (file CodeFile) functions() []Function {
return file.items.filter(it is Function).map(it as Function) return file.items.filter(it is Function).map(it as Function)
} }
pub fn (file VFile) structs() []Struct { pub fn (file CodeFile) structs() []Struct {
return file.items.filter(it is Struct).map(it as Struct) return file.items.filter(it is Struct).map(it as Struct)
} }

Some files were not shown because too many files have changed in this diff Show More