imrprove code generation to support example

This commit is contained in:
timurgordon
2025-02-12 03:37:22 +03:00
parent 13471d4ca5
commit 6644d3b11c
28 changed files with 491 additions and 372 deletions

View File

@@ -6,22 +6,6 @@ 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)}
@@ -32,6 +16,7 @@ fn generate_handle_file(spec ActorSpecification) !VFile {
name: 'act'
imports: [
Import{mod:'freeflowuniverse.herolib.baobab.stage' types:['Action']}
Import{mod:'freeflowuniverse.herolib.core.texttools'}
Import{mod:'x.json2 as json'}
]
items: items
@@ -58,37 +43,7 @@ pub fn generate_handle_function(spec ActorSpecification) string {
'// 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 {',
' return match texttools.snake_case(action.name) {',
routes.join('\n'),
' else {',
' return error("Unknown operation: \${action.name}")',
@@ -116,7 +71,7 @@ pub fn generate_method_handle(actor_name string, method ActorMethod) !Function {
body += '${param_name} := ${decode_stmt}'
}
}
call_stmt := generate_call_stmt(method)!
call_stmt := generate_call_stmt(actor_name, method)!
body += '${call_stmt}\n'
body += '${generate_return_stmt(method)!}\n'
return Function {
@@ -153,13 +108,15 @@ pub fn generate_example_method_handle(actor_name string, method ActorMethod) !Fu
}
}
fn generate_call_stmt(method ActorMethod) !string {
fn generate_call_stmt(name string, 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)
method_name := texttools.snake_case(method.name)
snake_name := texttools.snake_case(name)
param_names := method.parameters.map(texttools.snake_case(it.name))
call_stmt += 'actor.${name_fixed}(${param_names.join(", ")})!'
call_stmt += 'actor.${snake_name}.${method_name}(${param_names.join(", ")})!'
return call_stmt
}
@@ -170,23 +127,23 @@ fn generate_return_stmt(method ActorMethod) !string {
return "return action"
}
// 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)}"
}
// 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})'
// }
else if param_type is code.Array {
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})'
}
param_symbol := param_type.vgen()
return if param_symbol == 'string' {
'${name}.str()'
} else {'${name}.${param_type.vgen()}()'}
}
// Helper function to generate a case block for the main router
fn generate_route_case(case string, handler_name string) string {
name_fixed := texttools.snake_case(handler_name)
return "'${texttools.snake_case(case)}' {actor.${name_fixed}(action)}"
}

View File

@@ -12,22 +12,14 @@ pub:
interfaces []ActorInterface // the interfaces to be supported
}
pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
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)!
]
files = [generate_readme_file(spec)!]
mut docs_files := []IFile{}
mut spec_files := []IFile{}
// generate code files for supported interfaces
for iface in params.interfaces {
@@ -35,31 +27,80 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
.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)!
spec_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)!
spec_files << generate_openapi_file(openapi_spec_raw)!
openapi_spec := openapi.process(openapi_spec_raw)!
folders << generate_openapi_ts_client(openapi_spec)!
}
else {}
}
}
specs_folder := Folder {
name: 'specs'
files: spec_files
}
// folder with docs
folders << Folder {
name: 'docs'
files: docs_files
folders: [specs_folder]
}
folders << generate_scripts_folder(spec.name, false)
folders << generate_examples_folder()!
// create module with code files and docs folder
name_fixed := texttools.snake_case(spec.name)
return code.Folder{
name: '${name_fixed}_actor'
files: files
folders: folders
modules: [generate_actor_module(spec, params)!]
}
}
pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
mut files := []IFile{}
mut folders := []IFolder{}
files = [
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_methods_interface_file(spec)!
generate_methods_example_file(spec)!
generate_client_file(spec)!
generate_model_file(spec)!
]
// 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()
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
files << iface_file
files << iface_test_file
}
.openapi {
// convert actor spec to openrpc spec
openapi_spec_raw := spec.to_openapi()
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
@@ -77,15 +118,6 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
}
}
}
// 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)
@@ -108,8 +140,9 @@ fn generate_readme_file(spec ActorSpecification) !File {
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)
version := spec.version
name_snake := texttools.snake_case(spec.name)
name_pascal := texttools.snake_case_to_pascal(spec.name)
actor_code := $tmpl('./templates/actor.v.template')
return VFile {
name: 'actor'
@@ -117,17 +150,6 @@ fn generate_actor_file(spec ActorSpecification) !VFile {
}
}
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)
@@ -152,88 +174,8 @@ fn generate_specs_file(name string, interfaces []ActorInterface) !VFile {
}
}
pub fn generate_examples_folder(spec ActorSpecification, params Params) !Folder {
pub fn generate_examples_folder() !Folder {
return Folder {
name: 'examples'
modules: [generate_example_actor_module(spec, params)!]
}
}
pub fn generate_example_actor_module(spec ActorSpecification, params Params) !Module {
mut files := []IFile{}
mut folders := []IFolder{}
files = [
generate_readme_file(spec)!,
generate_actor_example_file(spec)!,
generate_specs_file(spec.name, params.interfaces)!,
generate_handle_example_file(spec)!,
generate_example_client_file(spec)!
generate_model_file(spec)!
]
mut docs_files := []IFile{}
// generate code files for supported interfaces
for iface in params.interfaces {
match iface {
.openrpc {
// convert actor spec to openrpc spec
openrpc_spec := spec.to_openrpc()
// generate openrpc code files
// files << generate_openrpc_client_file(openrpc_spec)!
// files << generate_openrpc_client_test_file(openrpc_spec)!
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
files << iface_file
files << iface_test_file
// add openrpc.json to docs
// TODO
docs_files << generate_openrpc_file(openrpc_spec)!
}
.openapi {
// convert actor spec to openrpc spec
openapi_spec := spec.to_openapi()
// generate openrpc code files
iface_file, iface_test_file := generate_openapi_interface_files(params.interfaces)
files << iface_file
files << iface_test_file
// add openapi.json to docs
docs_files << generate_openapi_file(openapi_spec)!
}
.http {
// interfaces that have http controllers
controllers := params.interfaces.filter(it == .openrpc || it == .openapi)
// generate openrpc code files
iface_file, iface_test_file := generate_http_interface_files(controllers)
files << iface_file
files << iface_test_file
}
.command {
files << generate_command_file(spec)!
}
else {
return error('unsupported interface ${iface}')
}
}
}
// folder with docs
folders << Folder {
name: 'docs'
files: docs_files
}
folders << generate_scripts_folder('example_${spec.name}', true)
// create module with code files and docs folder
name_fixed := texttools.snake_case(spec.name)
return code.new_module(
name: 'example_${name_fixed}_actor'
files: files
folders: folders
)
}

View File

@@ -17,11 +17,9 @@ pub fn generate_client_file(spec ActorSpecification) !VFile {
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
fn new_client(config stage.ActorConfig) !Client {
return Client {
Client: stage.new_client(config)!
}
}'}
@@ -42,7 +40,7 @@ pub fn generate_client_file(spec ActorSpecification) !VFile {
types: ['Any']
}
]
name: 'client'
name: 'client_actor'
items: items
}
}

View File

@@ -19,7 +19,7 @@ fn generate_openrpc_interface_files(interfaces []ActorInterface) (VFile, VFile)
fn generate_openapi_interface_files(interfaces []ActorInterface) (VFile, VFile) {
http := ActorInterface.http in interfaces
dollar := '$'
iface_file := VFile {
name: 'interface_openapi'
items: [CustomCode{$tmpl('./templates/interface_openapi.v.template')}]

View File

@@ -8,12 +8,19 @@ import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecific
const crud_prefixes = ['new', 'get', 'set', 'delete', 'list']
pub fn generate_methods_file(spec ActorSpecification) !VFile {
actor_name_snake := texttools.snake_case(spec.name)
name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
mut items := []CodeItem{}
receiver := generate_methods_receiver(spec.name)
receiver_param := Param {
mutable: true
name: name_snake
typ: code.Result{code.Object{receiver.name}}
}
mut items := [CodeItem(receiver), CodeItem(generate_core_factory(receiver_param))]
for method in spec.methods {
method_fn := generate_method_function(spec.name, method)!
method_fn := generate_method_function(receiver_param, method)!
// check if method is a Base Object CRUD Method and
// if so generate the method's body
body := match spec.method_type(method) {
@@ -29,17 +36,34 @@ pub fn generate_methods_file(spec ActorSpecification) !VFile {
return VFile {
name: 'methods'
imports: [Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']}]
items: items
}
}
fn generate_methods_receiver(name string) code.Struct {
return code.Struct {
is_pub: true
name: '${texttools.pascal_case(name)}'
embeds: [code.Struct{name:'OSIS'}]
}
}
fn generate_core_factory(receiver code.Param) code.Function {
return code.Function {
is_pub: true
name: 'new_${receiver.name}'
body: "return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}"
result: receiver
}
}
// 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)
pub fn generate_method_function(receiver code.Param, method ActorMethod) !Function {
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')!
receiver: receiver
result: Param {...result_param, typ: Result{result_param.typ}}
summary: method.summary
description: method.description

View File

@@ -0,0 +1,65 @@
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 {Example}
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter}
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
pub fn generate_methods_example_file(spec ActorSpecification) !VFile {
name_snake := texttools.snake_case(spec.name)
name_pascal := texttools.pascal_case(spec.name)
receiver := generate_example_methods_receiver(spec.name)
receiver_param := Param {
mutable: true
name: name_snake
typ: code.Result{code.Object{receiver.name}}
}
mut items := [CodeItem(receiver), CodeItem(generate_core_example_factory(receiver_param))]
for method in spec.methods {
method_fn := generate_method_function(receiver_param, method)!
items << Function{...method_fn,
body: generate_method_example_body(method_fn, method)!
}
}
return VFile {
name: 'methods_example'
imports: [
Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']},
Import{mod: 'x.json2 as json'}
]
items: items
}
}
fn generate_core_example_factory(receiver code.Param) code.Function {
return code.Function {
is_pub: true
name: 'new_${receiver.name}_example'
body: "return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}"
result: receiver
}
}
fn generate_example_methods_receiver(name string) code.Struct {
return code.Struct {
is_pub: true
name: '${texttools.pascal_case(name)}Example'
embeds: [code.Struct{name:'OSIS'}]
}
}
fn generate_method_example_body(func Function, method ActorMethod) !string {
return if !method_is_void(method)! {
if method.example.result is Example {
"data := '${method.example.result.value}'
return ${generate_decode_stmt('data', method.result)!}"
} else {
"return ${func.result.typ.empty_value()}"
}
} else {
""
}
}

View File

@@ -0,0 +1,31 @@
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}
pub fn generate_methods_interface_file(spec ActorSpecification) !VFile {
return VFile {
name: 'methods_interface'
imports: [Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']}]
items: [code.CodeItem(generate_methods_interface_declaration(spec)!)]
}
}
// returns bodyless method prototype
pub fn generate_methods_interface_declaration(spec ActorSpecification) !code.Interface {
name_snake := texttools.snake_case(spec.name)
name_pascal := texttools.pascal_case(spec.name)
receiver := generate_methods_receiver(spec.name)
receiver_param := Param {
mutable: true
name: name_snake
typ: code.Object{receiver.name}
}
return code.Interface {
is_pub: true
name: 'I${name_pascal}'
methods: spec.methods.map(generate_method_function(receiver_param, it)!)
}
}

View File

@@ -11,9 +11,9 @@ pub fn generate_scripts_folder(name string, example bool) Folder {
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_run_actor_script(name),
generate_run_actor_example_script(name),
generate_run_http_server_script(name),
// generate_compile_script(actor_name),
// generate_generate_script(actor_name)
]
@@ -42,7 +42,9 @@ fn generate_docs_script(actor_name string) File {
}
// Function to generate a script for running an actor
fn generate_run_actor_script(actor_name string) File {
fn generate_run_actor_script(name string) File {
name_snake := texttools.snake_case(name)
name_pascal := texttools.pascal_case(name)
return File{
name: 'run_actor'
extension:'vsh'
@@ -51,38 +53,23 @@ fn generate_run_actor_script(actor_name string) File {
}
// Function to generate a script for running an example actor
fn generate_run_example_actor_script(actor_name string) File {
fn generate_run_actor_example_script(name string) File {
name_snake := texttools.snake_case(name)
name_pascal := texttools.pascal_case(name)
return File{
name: 'run_example_actor'
name: 'run_actor_example'
extension:'vsh'
content: $tmpl('./templates/run_example_actor.vsh.template')
content: $tmpl('./templates/run_actor_example.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}
fn generate_run_http_server_script(name string) File {
port := 8080
name_snake := texttools.snake_case(name)
return File{
name: 'run_http_server'
extension:'vsh'
content: $tmpl('./templates/run_http_server.vsh.template')
}
}
// // Function to generate a script for compiling the project
// fn generate_compile_script(actor_name string) File {
// return File{
// name: 'compile'
// extension:'sh'
// content: $tmpl('./templates/run_http_server.vsh.template')
// }
// }
// // Function to generate a script for general generation tasks
// fn generate_generate_script(actor_name string) File {
// return File{
// name: 'generate'
// extension: 'vsh'
// content: $tmpl('./templates/run_http_server.vsh.template')
// }
// }
}

View File

@@ -4,20 +4,26 @@ import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.schemas.openapi
import time
const name = '@{actor_name_snake}'
@@[heap]
struct @{actor_name_pascal}Actor {
stage.Actor
pub const configuration = stage.ActorConfig {
name: '@{name_snake}'
version: '@{version}'
}
pub fn new() !&@{actor_name_pascal}Actor {
return &@{actor_name_pascal}Actor {
Actor: stage.new_actor('@{actor_name_snake}')!
@@[heap]
struct @{name_pascal}Actor {
stage.Actor
pub mut:
@{name_snake} I@{name_pascal}
}
pub fn new(@{name_snake} I@{name_pascal}, config stage.ActorConfig) !&@{name_pascal}Actor {
return &@{name_pascal}Actor {
Actor: stage.new_actor(config)!
@{name_snake}: @{name_snake}
}
}
pub fn (mut a @{actor_name_pascal}Actor) handle(method string, data string) !string {
pub fn (mut a @{name_pascal}Actor) handle(method string, data string) !string {
action := a.act(
name: method
params: data
@@ -26,11 +32,8 @@ pub fn (mut a @{actor_name_pascal}Actor) handle(method string, data string) !str
}
// 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...')
pub fn (mut a @{name_pascal}Actor) run() ! {
mut rpc := a.get_redis_rpc()!
for {
rpc.process(a.handle)!
time.sleep(time.millisecond * 100) // Prevent CPU spinning

View File

@@ -5,42 +5,37 @@ import freeflowuniverse.herolib.baobab.stage.interfaces { HTTPServer, Context }
import veb
@@[params]
pub struct ServerParams {
pub struct HTTPServerParams {
pub:
base_url string
port int = 8080
}
pub fn new_http_server(params ServerParams) !&HTTPServer {
pub fn new_http_server(params HTTPServerParams) !&HTTPServer {
mut s := interfaces.new_http_server()!
@if openrpc
mut openrpc_controller := new_openrpc_http_controller(ServerParams{
mut openrpc_controller := new_openrpc_http_controller(HTTPServerParams{
...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_ctrl := new_openapi_http_controller(configuration, params)!
mut openapi_ex_ctrl := new_openapi_http_controller(configuration.example().example(), params)!
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.HTTPController, Context]('/openapi/v1', mut openapi_ctrl)!
s.register_controller[openapi.HTTPController, Context]('/openapi/example', mut openapi_ex_ctrl)!
s.register_controller[openapi.PlaygroundController, Context]('/playground/openapi', mut openapi_playground_controller)!
@end
return s
}
pub fn run_http_server(params ServerParams) ! {
pub fn run_http_server(params HTTPServerParams) ! {
mut server := new_http_server(params)!
veb.run[HTTPServer, Context](mut server, params.port)
}

View File

@@ -1,21 +1,22 @@
import freeflowuniverse.herolib.baobab.stage.interfaces
import freeflowuniverse.herolib.baobab.stage
import freeflowuniverse.herolib.schemas.openapi
pub fn new_openapi_interface() !&interfaces.OpenAPIInterface {
pub fn new_openapi_interface(config stage.ActorConfig) !&interfaces.OpenAPIInterface {
// create OpenAPI Handler with actor's client
client := new_client()!
client := new_client(config)!
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 {
pub fn new_openapi_http_controller(config stage.ActorConfig, params HTTPServerParams) !&openapi.HTTPController {
return openapi.new_http_controller(
base_url: params.base_url
base_url: '@{dollar}{params.base_url}/openapi/@{dollar}{config.version}'
specification: openapi_specification
specification_path: openapi_spec_path
handler: new_openapi_interface()!
handler: new_openapi_interface(config)!
)
}
@end

View File

@@ -7,6 +7,10 @@ chmod +x @{dollar}{DIR}/run_actor.vsh
@{dollar}{DIR}/run_actor.vsh &
ACTOR_PID=@{dollar}!
chmod +x @{dollar}{DIR}/run_actor_example.vsh
@{dollar}{DIR}/run_actor_example.vsh &
EXAMPLE_ACTOR_PID=@{dollar}!
chmod +x @{dollar}{DIR}/run_http_server.vsh
@{dollar}{DIR}/run_http_server.vsh &
HTTP_SERVER_PID=@{dollar}!

View File

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

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env -S v -w -n -enable-globals run
import @{name_snake}_actor
mut actor := @{name_snake}_actor.new(
@{name_snake}_actor.new_@{name_snake}_example()!,
@{name_snake}_actor.configuration.example()
)!
actor.run()!

View File

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

View File

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

View File

@@ -3,12 +3,12 @@ import freeflowuniverse.herolib.schemas.openrpc
import os
@if support_openrpc
const openrpc_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/openrpc.json'
const openrpc_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/specs/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(os.dir(@@FILE))}/docs/openapi.json'
const openapi_spec_path = '@{dollar}{os.dir(os.dir(@@FILE))}/docs/specs/openapi.json'
const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) }
const openapi_specification = openapi.json_decode(openapi_spec_json)!
@end

View File

@@ -7,6 +7,7 @@ import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference}
pub struct ActorSpecification {
pub mut:
version string = '1.0.0'
openapi ?openapi.OpenAPI
openrpc ?openrpc.OpenRPC
name string @[omitempty]

View File

@@ -16,14 +16,14 @@ pub:
}
pub struct ClientConfig {
ActorConfig
pub:
redis_url string // url to redis server running
redis_queue string // name of redis queue
redis_url string = 'localhost:6379' // url to redis server running
}
pub fn new_client(config ClientConfig) !Client {
pub fn new_client(config ActorConfig) !Client {
mut redis := redisclient.new(config.redis_url)!
mut rpc_q := redis.rpc_get(config.redis_queue)
mut rpc_q := redis.rpc_get(config.redis_queue_name())
return Client{
rpc: rpc_q

View File

@@ -1,6 +1,7 @@
module stage
import freeflowuniverse.herolib.baobab.osis {OSIS}
import freeflowuniverse.herolib.core.redisclient
@[heap]
pub interface IActor {
@@ -10,17 +11,48 @@ mut:
}
pub struct Actor {
pub:
name string
redis_url string = 'localhost:6379'
ActorConfig
mut:
osis OSIS
}
pub fn new_actor(name string) !Actor {
@[params]
pub struct ActorConfig {
pub:
name string
version string
redis_url string = 'localhost:6379'
}
pub fn (config ActorConfig) redis_queue_name() string {
mut str := 'actor_${config.name}'
if config.version != '' {
str += '_${config.version}'
}
return str
}
pub fn new_actor(config ActorConfig) !Actor {
return Actor{
ActorConfig: config
osis: osis.new()!
name: name
}
}
pub fn (a ActorConfig) get_redis_rpc() !redisclient.RedisRpc {
mut redis := redisclient.new(a.redis_url)!
return redis.rpc_get(a.redis_queue_name())
}
pub fn (a ActorConfig) version(v string) ActorConfig {
return ActorConfig {...a,
version: v
}
}
pub fn (a ActorConfig) example() ActorConfig {
return ActorConfig {...a,
version: 'example'
}
}

View File

@@ -0,0 +1,2 @@
module stage

View File

@@ -13,6 +13,7 @@ pub struct Folder {
pub:
name string
files []IFile
folders []IFolder
modules []Module
}
@@ -29,6 +30,9 @@ pub fn (f Folder) write(path string, options WriteOptions) ! {
for file in f.files {
file.write(dir.path, options)!
}
for folder in f.folders {
folder.write(dir.path, options)!
}
for mod in f.modules {
mod.write(dir.path, options)!
}

View File

@@ -1,5 +1,7 @@
module code
import freeflowuniverse.herolib.core.texttools
pub struct Function {
pub:
name string @[omitempty]
@@ -15,6 +17,61 @@ pub mut:
has_return bool @[omitempty]
}
// vgen_function generates a function statement for a function
pub fn (function Function) vgen(options WriteOptions) string {
mut params_ := function.params.clone()
optionals := function.params.filter(it.is_optional)
options_struct := Struct{
name: '${texttools.snake_case_to_pascal(function.name)}Options'
attrs: [Attribute{
name: 'params'
}]
fields: optionals.map(StructField{
name: it.name
description: it.description
typ: it.typ
})
}
if optionals.len > 0 {
params_ << Param{
name: 'options'
typ: type_from_symbol(options_struct.name)
}
}
params := params_.filter(!it.is_optional).map(it.vgen()).join(', ')
receiver_ := Param{
...function.receiver,
typ: if function.receiver.typ is Result {
function.receiver.typ.typ
} else {function.receiver.typ}
}
receiver := if receiver_.vgen().trim_space() != '' {
'(${receiver_.vgen()})'
} else {''}
result := function.result.typ.vgen()
mut function_str := $tmpl('templates/function/function.v.template')
// if options.format {
// result := os.execute_opt('echo "${function_str.replace('$', '\\$')}" | v fmt') or {
// panic('${function_str}\n${err}')
// }
// function_str = result.output
// }
function_str = function_str.split_into_lines().filter(!it.starts_with('import ')).join('\n')
return if options_struct.fields.len != 0 {
'${options_struct.vgen()}\n${function_str}'
} else {
function_str
}
}
pub fn new_function(code string) !Function {
// TODO: implement function from file line
return parse_function(code)!

View File

@@ -3,7 +3,7 @@ module code
// Code is a list of statements
// pub type Code = []CodeItem
pub type CodeItem = Alias | Comment | CustomCode | Function | Import | Struct | Sumtype
pub type CodeItem = Alias | Comment | CustomCode | Function | Import | Struct | Sumtype | Interface
// item for adding custom code in
pub struct CustomCode {

View File

@@ -32,7 +32,7 @@ pub fn new_module(mod Module) Module {
pub fn (mod Module) write(path string, options WriteOptions) ! {
mut module_dir := pathlib.get_dir(
path: if mod.in_src { '${path}/${mod.name}/src' } else { '${path}/${mod.name}' }
path: if mod.in_src { '${path}/src' } else { '${path}/${mod.name}' }
empty: options.overwrite
)!
console.print_debug("write ${module_dir.path}")

View File

@@ -1,5 +1,7 @@
module code
import log
import os
import freeflowuniverse.herolib.core.texttools
pub struct Struct {
@@ -14,6 +16,79 @@ pub mut:
fields []StructField
}
// vgen_function generates a function statement for a function
pub fn (struct_ Struct) vgen() string {
name := if struct_.generics.len > 0 {
'${struct_.name}${vgen_generics(struct_.generics)}'
} else {
struct_.name
}
prefix := if struct_.is_pub {
'pub'
} else {
''
}
priv_fields := struct_.fields.filter(!it.is_mut && !it.is_pub).map(it.vgen())
pub_fields := struct_.fields.filter(!it.is_mut && it.is_pub).map(it.vgen())
mut_fields := struct_.fields.filter(it.is_mut && !it.is_pub).map(it.vgen())
pub_mut_fields := struct_.fields.filter(it.is_mut && it.is_pub).map(it.vgen())
mut struct_str := $tmpl('templates/struct/struct.v.template')
if false {
result := os.execute_opt('echo "${struct_str.replace('$', '\$')}" | v fmt') or {
log.debug(struct_str)
panic(err)
}
return result.output
}
return struct_str
}
pub struct Interface {
pub mut:
name string
description string
is_pub bool
embeds []Interface @[str: skip]
attrs []Attribute
fields []StructField
methods []Function
}
pub fn (iface Interface) vgen() string {
name := texttools.pascal_case(iface.name)
prefix := if iface.is_pub {
'pub'
} else {
''
}
mut fields := iface.fields.filter(!it.is_mut).map(it.vgen())
mut mut_fields := iface.fields.filter(it.is_mut).map(it.vgen())
fields << iface.methods.filter(!it.receiver.mutable).map(function_to_interface_field(it))
mut_fields << iface.methods.filter(it.receiver.mutable).map(function_to_interface_field(it))
mut iface_str := $tmpl('templates/interface/interface.v.template')
if false {
result := os.execute_opt('echo "${iface_str.replace('$', '\$')}" | v fmt') or {
log.debug(iface_str)
panic(err)
}
return result.output
}
return iface_str
}
pub fn function_to_interface_field(f Function) string {
param_types := f.params.map(it.typ.vgen()).join(', ')
return '${f.name}(${param_types}) ${f.result.typ.vgen()}'
}
pub struct StructField {
Param
pub mut:

View File

@@ -0,0 +1,15 @@
// @{iface.description}
@if iface.attrs.len > 0
[
@for attr in iface.attrs
@{attr.name}
@end
]
@end
@{prefix} interface @{name} {
@{fields.join_lines()}
@if mut_fields.len > 0
mut:
@{mut_fields.join_lines()}
@end
}

View File

@@ -21,6 +21,9 @@ pub fn vgen(code []CodeItem) string {
if item is Struct {
str += '\n${item.vgen()}'
}
if item is Interface {
str += '\n${item.vgen()}'
}
if item is CustomCode {
str += '\n${item.vgen()}'
}
@@ -54,91 +57,6 @@ pub fn vgen_generics(generics map[string]string) string {
return '${vstr}]'
}
// vgen_function generates a function statement for a function
pub fn (function Function) vgen(options WriteOptions) string {
mut params_ := function.params.clone()
optionals := function.params.filter(it.is_optional)
options_struct := Struct{
name: '${texttools.snake_case_to_pascal(function.name)}Options'
attrs: [Attribute{
name: 'params'
}]
fields: optionals.map(StructField{
name: it.name
description: it.description
typ: it.typ
})
}
if optionals.len > 0 {
params_ << Param{
name: 'options'
typ: type_from_symbol(options_struct.name)
}
}
params := params_.filter(!it.is_optional).map(it.vgen()).join(', ')
receiver := if function.receiver.vgen().trim_space() != '' {
'(${function.receiver.vgen()})'
} else {''}
// generate anon result param
result := Param{...function.result,
name: ''
}.vgen()
mut function_str := $tmpl('templates/function/function.v.template')
// if options.format {
// result := os.execute_opt('echo "${function_str.replace('$', '\\$')}" | v fmt') or {
// panic('${function_str}\n${err}')
// }
// function_str = result.output
// }
function_str = function_str.split_into_lines().filter(!it.starts_with('import ')).join('\n')
return if options_struct.fields.len != 0 {
'${options_struct.vgen()}\n${function_str}'
} else {
function_str
}
}
// vgen_function generates a function statement for a function
pub fn (struct_ Struct) vgen() string {
name := if struct_.generics.len > 0 {
'${struct_.name}${vgen_generics(struct_.generics)}'
} else {
struct_.name
}
prefix := if struct_.is_pub {
'pub'
} else {
''
}
priv_fields := struct_.fields.filter(!it.is_mut && !it.is_pub).map(it.vgen())
pub_fields := struct_.fields.filter(!it.is_mut && it.is_pub).map(it.vgen())
mut_fields := struct_.fields.filter(it.is_mut && !it.is_pub).map(it.vgen())
pub_mut_fields := struct_.fields.filter(it.is_mut && it.is_pub).map(it.vgen())
mut struct_str := $tmpl('templates/struct/struct.v.template')
if false {
result := os.execute_opt('echo "${struct_str.replace('$', '\$')}" | v fmt') or {
log.debug(struct_str)
panic(err)
}
return result.output
}
return struct_str
// mut struct_str := $tmpl('templates/struct/struct.v.template')
// return struct_str
// result := os.execute_opt('echo "${struct_str.replace('$', '\$')}" | v fmt') or {panic(err)}
// return result.output
}
pub fn (custom CustomCode) vgen() string {
return custom.text
}