Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8965f7ae89 | |||
| 9a931b65e2 | |||
| 2c149507f6 | |||
|
|
34dea39c52 | ||
|
|
f1a4547961 | ||
|
|
8ae56a8df6 | ||
| b731c4c388 | |||
| e929ce029d | |||
| 5160096a1a | |||
| f219a4041a | |||
| 674eae1c11 | |||
| f62369bd01 | |||
| 7a6660ebd8 | |||
| e20d1bdcc5 | |||
| 3e309b6379 | |||
| ae4e92e090 | |||
| 7b69719f0e | |||
| 1d631fec21 | |||
| 690b1b68c3 |
187
aiprompts/reflection.md
Normal file
187
aiprompts/reflection.md
Normal 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
2
cli/.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
hero
|
hero
|
||||||
|
compile
|
||||||
|
compile_upload
|
||||||
|
|||||||
22
cli/hero.v
22
cli/hero.v
@@ -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)
|
||||||
|
|||||||
3
examples/baobab/generator/.gitignore
vendored
3
examples/baobab/generator/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
methods.v
|
|
||||||
pet_store_actor
|
|
||||||
docs
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
|
||||||
)!
|
|
||||||
@@ -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
|
|
||||||
)!
|
|
||||||
@@ -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
|
|
||||||
)!
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
methods.v
|
|
||||||
meeting_scheduler_actor
|
|
||||||
@@ -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'])!
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
@@ -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))
|
|
||||||
@@ -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
108
examples/clients/mycelium.vsh
Executable 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}')
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
|
||||||
14
examples/develop/gittools/gittools_path_get.vsh
Executable file
14
examples/develop/gittools/gittools_path_get.vsh
Executable 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)
|
||||||
@@ -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() ! {
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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()!
|
||||||
|
|||||||
@@ -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
12
examples/installers/traefik.vsh
Executable 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()!
|
||||||
6
examples/installers/zinit_installer.vsh
Executable file
6
examples/installers/zinit_installer.vsh
Executable 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
24
examples/osal/tun.vsh
Executable 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}')
|
||||||
|
}
|
||||||
1
examples/schemas/openapi/codegen/.gitignore
vendored
1
examples/schemas/openapi/codegen/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
client_typescript
|
|
||||||
@@ -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)!
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
3
examples/virt/podman_buildah/.gitignore
vendored
3
examples/virt/podman_buildah/.gitignore
vendored
@@ -1 +1,4 @@
|
|||||||
buildah_example
|
buildah_example
|
||||||
|
buildah_run_clean
|
||||||
|
buildah_run_mdbook
|
||||||
|
buildah_run
|
||||||
|
|||||||
@@ -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()!
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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()!
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}> {}'
|
|
||||||
// }
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)}"
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)!
|
|
||||||
}
|
|
||||||
@@ -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)!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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()}]()!'
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
// }
|
|
||||||
@@ -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')
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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')
|
|
||||||
}
|
|
||||||
@@ -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!')
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fn test_new_http_server() ! {
|
|
||||||
new_http_server()!
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_run_http_server() ! {
|
|
||||||
spawn run_http_server()
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
|
||||||
|
|
||||||
import @{actor_name}
|
|
||||||
|
|
||||||
mut actor := @{actor_name}.new()!
|
|
||||||
actor.run()!
|
|
||||||
@@ -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()!
|
|
||||||
@@ -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}
|
|
||||||
)!
|
|
||||||
@@ -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
|
|
||||||
@@ -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]
|
|
||||||
// }
|
|
||||||
@@ -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')
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
module osis
|
|
||||||
|
|
||||||
pub fn new(config OSISConfig) !OSIS {
|
|
||||||
return OSIS{
|
|
||||||
indexer: new_indexer()!
|
|
||||||
storer: new_storer()!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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()!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)!
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
// }
|
|
||||||
@@ -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')
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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{}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
153
lib/clients/livekit/access_token.v
Normal file
153
lib/clients/livekit/access_token.v
Normal 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)
|
||||||
|
// }
|
||||||
199
lib/clients/livekit/server_client.v
Normal file
199
lib/clients/livekit/server_client.v
Normal 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)!
|
||||||
|
// }
|
||||||
@@ -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
|
||||||
)!
|
)!
|
||||||
}
|
}
|
||||||
|
|||||||
71
lib/clients/mycelium/mycelium_check.v
Normal file
71
lib/clients/mycelium/mycelium_check.v
Normal 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
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
602
lib/clients/mycelium/openapi.yaml
Normal file
602
lib/clients/mycelium/openapi.yaml
Normal 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
|
||||||
@@ -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
|
|
||||||
|
|||||||
148
lib/code/codemodel/README.md
Normal file
148
lib/code/codemodel/README.md
Normal 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.
|
||||||
@@ -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
Reference in New Issue
Block a user