Merge branch 'development_actions007' of github.com:freeflowuniverse/herolib into development_actions007

This commit is contained in:
Timur Gordon
2025-03-26 19:27:57 +01:00
375 changed files with 8342 additions and 9011 deletions

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ compile_results.log
tmp tmp
compile_summary.log compile_summary.log
.summary_lock .summary_lock
.aider*

View File

@@ -1,27 +1,30 @@
module developer module developer
import freeflowuniverse.herolib.mcp
@[heap] @[heap]
pub struct Developer {} pub struct Developer {}
pub fn result_to_mcp_tool_contents[T](result T) []mcp.ToolContent { pub fn result_to_mcp_tool_contents[T](result T) []mcp.ToolContent {
return [result_to_mcp_tool_content(result)] return [result_to_mcp_tool_content(result)]
} }
pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent { pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent {
return $if T is string { return $if T is string {
mcp.ToolContent{ mcp.ToolContent
typ: 'text' {
typ: 'text'
text: result.str() text: result.str()
} }
} $else $if T is int { } $else $if T is int {
mcp.ToolContent{ mcp.ToolContent
typ: 'number' {
typ: 'number'
number: result.int() number: result.int()
} }
} $else $if T is bool { } $else $if T is bool {
mcp.ToolContent{ mcp.ToolContent
typ: 'boolean' {
typ: 'boolean'
boolean: result.bool() boolean: result.bool()
} }
} $else $if result is $array { } $else $if result is $array {
@@ -29,8 +32,9 @@ pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent {
for item in result { for item in result {
items << result_to_mcp_tool_content(item) items << result_to_mcp_tool_content(item)
} }
return mcp.ToolContent{ return mcp.ToolContent
typ: 'array' {
typ: 'array'
items: items items: items
} }
} $else $if T is $struct { } $else $if T is $struct {
@@ -38,8 +42,9 @@ pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent {
$for field in T.fields { $for field in T.fields {
properties[field.name] = result_to_mcp_tool_content(result.$(field.name)) properties[field.name] = result_to_mcp_tool_content(result.$(field.name))
} }
return mcp.ToolContent{ return mcp.ToolContent
typ: 'object' {
typ: 'object'
properties: properties properties: properties
} }
} $else { } $else {

View File

@@ -2,10 +2,17 @@ module developer
import freeflowuniverse.herolib.core.code import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.mcp import freeflowuniverse.herolib.mcp
import os
// create_mcp_tool_code receives the name of a V language function string, and the path to the module in which it exists. // create_mcp_tool_code receives the name of a V language function string, and the path to the module in which it exists.
// returns an MCP Tool code in v for attaching the function to the mcp server // returns an MCP Tool code in v for attaching the function to the mcp server
pub fn (d &Developer) create_mcp_tool_code(function_name string, module_path string) !string { pub fn (d &Developer) create_mcp_tool_code(function_name string, module_path string) !string {
println('DEBUG: Looking for function ${function_name} in module path: ${module_path}')
if !os.exists(module_path) {
println('DEBUG: Module path does not exist: ${module_path}')
return error('Module path does not exist: ${module_path}')
}
function_ := get_function_from_module(module_path, function_name)! function_ := get_function_from_module(module_path, function_name)!
println('Function string found:\n${function_}') println('Function string found:\n${function_}')

View File

@@ -1,7 +1,7 @@
module developer module developer
import freeflowuniverse.herolib.mcp import freeflowuniverse.herolib.mcp
import x.json2 {Any} import x.json2
pub fn result_to_mcp_tool_contents[T](result T) []mcp.ToolContent { pub fn result_to_mcp_tool_contents[T](result T) []mcp.ToolContent {
return [result_to_mcp_tool_content(result)] return [result_to_mcp_tool_content(result)]
@@ -10,17 +10,17 @@ pub fn result_to_mcp_tool_contents[T](result T) []mcp.ToolContent {
pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent { pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent {
return $if T is string { return $if T is string {
mcp.ToolContent{ mcp.ToolContent{
typ: 'text' typ: 'text'
text: result.str() text: result.str()
} }
} $else $if T is int { } $else $if T is int {
mcp.ToolContent{ mcp.ToolContent{
typ: 'number' typ: 'number'
number: result.int() number: result.int()
} }
} $else $if T is bool { } $else $if T is bool {
mcp.ToolContent{ mcp.ToolContent{
typ: 'boolean' typ: 'boolean'
boolean: result.bool() boolean: result.bool()
} }
} $else $if result is $array { } $else $if result is $array {
@@ -29,7 +29,7 @@ pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent {
items << result_to_mcp_tool_content(item) items << result_to_mcp_tool_content(item)
} }
return mcp.ToolContent{ return mcp.ToolContent{
typ: 'array' typ: 'array'
items: items items: items
} }
} $else $if T is $struct { } $else $if T is $struct {
@@ -38,7 +38,7 @@ pub fn result_to_mcp_tool_content[T](result T) mcp.ToolContent {
properties[field.name] = result_to_mcp_tool_content(result.$(field.name)) properties[field.name] = result_to_mcp_tool_content(result.$(field.name))
} }
return mcp.ToolContent{ return mcp.ToolContent{
typ: 'object' typ: 'object'
properties: properties properties: properties
} }
} $else { } $else {

View File

@@ -0,0 +1,205 @@
module developer
import freeflowuniverse.herolib.mcp
import json
import os
// fn test_parse_struct_fields() {
// // Test case 1: Simple struct with primitive types
// simple_struct := 'pub struct User {
// name string
// age int
// active bool
// }'
// fields := parse_struct_fields(simple_struct)
// assert fields.len == 3
// assert fields['name'] == 'string'
// assert fields['age'] == 'int'
// assert fields['active'] == 'bool'
// // Test case 2: Struct with pub: and mut: sections
// complex_struct := 'pub struct Config {
// pub:
// host string
// port int
// mut:
// connected bool
// retries int
// }'
// fields2 := parse_struct_fields(complex_struct)
// assert fields2.len == 4
// assert fields2['host'] == 'string'
// assert fields2['port'] == 'int'
// assert fields2['connected'] == 'bool'
// assert fields2['retries'] == 'int'
// // Test case 3: Struct with attributes and comments
// struct_with_attrs := 'pub struct ApiResponse {
// // User ID
// id int
// // User full name
// name string @[json: "full_name"]
// // Whether account is active
// active bool
// }'
// fields3 := parse_struct_fields(struct_with_attrs)
// assert fields3.len == 3 // All fields are included
// assert fields3['id'] == 'int'
// assert fields3['active'] == 'bool'
// // Test case 4: Empty struct
// empty_struct := 'pub struct Empty {}'
// fields4 := parse_struct_fields(empty_struct)
// assert fields4.len == 0
// println('test_parse_struct_fields passed')
// }
// fn test_create_mcp_tool_input_schema() {
// d := Developer{}
// // Test case 1: Primitive types
// string_schema := d.create_mcp_tool_input_schema('string') or { panic(err) }
// assert string_schema.typ == 'string'
// int_schema := d.create_mcp_tool_input_schema('int') or { panic(err) }
// assert int_schema.typ == 'integer'
// float_schema := d.create_mcp_tool_input_schema('float') or { panic(err) }
// assert float_schema.typ == 'number'
// bool_schema := d.create_mcp_tool_input_schema('bool') or { panic(err) }
// assert bool_schema.typ == 'boolean'
// // Test case 2: Array type
// array_schema := d.create_mcp_tool_input_schema('[]string') or { panic(err) }
// assert array_schema.typ == 'array'
// // In our implementation, arrays don't have items directly in the schema
// // Test case 3: Struct type
// struct_def := 'pub struct Person {
// name string
// age int
// }'
// struct_schema := d.create_mcp_tool_input_schema(struct_def) or { panic(err) }
// assert struct_schema.typ == 'object'
// assert struct_schema.properties.len == 2
// assert struct_schema.properties['name'].typ == 'string'
// assert struct_schema.properties['age'].typ == 'integer'
// println('test_create_mcp_tool_input_schema passed')
// }
// fn test_create_mcp_tool() {
// d := Developer{}
// // Test case 1: Simple function with primitive types
// simple_fn := '// Get user by ID
// // Returns user information
// pub fn get_user(id int, include_details bool) {
// // Implementation
// }'
// tool1 := d.create_mcp_tool(simple_fn, {}) or { panic(err) }
// assert tool1.name == 'get_user'
// expected_desc1 := 'Get user by ID\nReturns user information'
// assert tool1.description == expected_desc1
// assert tool1.input_schema.typ == 'object'
// assert tool1.input_schema.properties.len == 2
// assert tool1.input_schema.properties['id'].typ == 'integer'
// assert tool1.input_schema.properties['include_details'].typ == 'boolean'
// assert tool1.input_schema.required.len == 2
// assert 'id' in tool1.input_schema.required
// assert 'include_details' in tool1.input_schema.required
// // Test case 2: Method with receiver
// method_fn := '// Update user profile
// pub fn (u User) update_profile(name string, age int) bool {
// // Implementation
// return true
// }'
// tool2 := d.create_mcp_tool(method_fn, {}) or { panic(err) }
// assert tool2.name == 'update_profile'
// assert tool2.description == 'Update user profile'
// assert tool2.input_schema.properties.len == 2
// assert tool2.input_schema.properties['name'].typ == 'string'
// assert tool2.input_schema.properties['age'].typ == 'integer'
// // Test case 3: Function with complex types
// complex_fn := '// Create new configuration
// // Sets up system configuration
// fn create_config(name string, settings Config) !Config {
// // Implementation
// }'
// config_struct := 'pub struct Config {
// server_url string
// max_retries int
// timeout float
// }'
// tool3 := d.create_mcp_tool(complex_fn, {
// 'Config': config_struct
// }) or { panic(err) }
// assert tool3.name == 'create_config'
// expected_desc3 := 'Create new configuration\nSets up system configuration'
// assert tool3.description == expected_desc3
// assert tool3.input_schema.properties.len == 2
// assert tool3.input_schema.properties['name'].typ == 'string'
// assert tool3.input_schema.properties['settings'].typ == 'object'
// // Test case 4: Function with no parameters
// no_params_fn := '// Initialize system
// pub fn initialize() {
// // Implementation
// }'
// tool4 := d.create_mcp_tool(no_params_fn, {}) or { panic(err) }
// assert tool4.name == 'initialize'
// assert tool4.description == 'Initialize system'
// assert tool4.input_schema.properties.len == 0
// assert tool4.input_schema.required.len == 0
// println('test_create_mcp_tool passed')
// }
// fn test_create_mcp_tool_code() {
// d := Developer{}
// // Test with the complex function that has struct parameters and return type
// module_path := "${os.dir(@FILE)}/testdata/mock_module"
// function_name := 'test_function'
// code := d.create_mcp_tool_code(function_name, module_path) or {
// panic('Failed to create MCP tool code: ${err}')
// }
// // Print the code instead of panic for debugging
// println('Generated code:')
// println('----------------------------------------')
// println(code)
// println('----------------------------------------')
// // Verify the generated code contains the expected elements
// assert code.contains('test_function_tool')
// assert code.contains('TestConfig')
// assert code.contains('TestResult')
// // Test with a simple function that has primitive types
// simple_function_name := 'simple_function'
// simple_code := d.create_mcp_tool_code(simple_function_name, module_path) or {
// panic('Failed to create MCP tool code for simple function: ${err}')
// }
// // Verify the simple function code
// assert simple_code.contains('simple_function_tool')
// assert simple_code.contains('name string')
// assert simple_code.contains('count int')
// // println('test_create_mcp_tool_code passed')
// }

View File

@@ -1,32 +1,35 @@
module developer module developer
import freeflowuniverse.herolib.mcp import freeflowuniverse.herolib.mcp
import x.json2 as json {Any} import x.json2 as json { Any }
// import json // import json
const create_mcp_tool_code_tool = mcp.Tool{ const create_mcp_tool_code_tool = mcp.Tool{
name: 'create_mcp_tool_code' name: 'create_mcp_tool_code'
description: 'create_mcp_tool_code receives the name of a V language function string, and the path to the module in which it exists. description: 'create_mcp_tool_code receives the name of a V language function string, and the path to the module in which it exists.
returns an MCP Tool code in v for attaching the function to the mcp server' returns an MCP Tool code in v for attaching the function to the mcp server'
input_schema: mcp.ToolInputSchema{ input_schema: mcp.ToolInputSchema{
typ: 'object' typ: 'object'
properties: {'function_name': mcp.ToolProperty{ properties: {
typ: 'string' 'function_name': mcp.ToolProperty{
items: mcp.ToolItems{ typ: 'string'
typ: '' items: mcp.ToolItems{
enum: [] typ: ''
} enum: []
enum: [] }
}, 'module_path': mcp.ToolProperty{ enum: []
typ: 'string' }
items: mcp.ToolItems{ 'module_path': mcp.ToolProperty{
typ: '' typ: 'string'
enum: [] items: mcp.ToolItems{
} typ: ''
enum: [] enum: []
}} }
required: ['function_name', 'module_path'] enum: []
} }
}
required: ['function_name', 'module_path']
}
} }
pub fn (d &Developer) create_mcp_tool_code_tool_handler(arguments map[string]Any) !mcp.ToolCallResult { pub fn (d &Developer) create_mcp_tool_code_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
@@ -37,59 +40,56 @@ pub fn (d &Developer) create_mcp_tool_code_tool_handler(arguments map[string]Any
} }
return mcp.ToolCallResult{ return mcp.ToolCallResult{
is_error: false is_error: false
content: result_to_mcp_tool_contents[string](result) content: result_to_mcp_tool_contents[string](result)
} }
} }
// Tool definition for the create_mcp_tool function // Tool definition for the create_mcp_tool function
const create_mcp_tool_tool = mcp.Tool{ const create_mcp_tool_tool = mcp.Tool{
name: 'create_mcp_tool' name: 'create_mcp_tool'
description: 'Parses a V language function string and returns an MCP Tool struct. This tool analyzes function signatures, extracts parameters, and generates the appropriate MCP Tool representation.' description: 'Parses a V language function string and returns an MCP Tool struct. This tool analyzes function signatures, extracts parameters, and generates the appropriate MCP Tool representation.'
input_schema: mcp.ToolInputSchema{ input_schema: mcp.ToolInputSchema{
typ: 'object' typ: 'object'
properties: { properties: {
'function': mcp.ToolProperty{ 'function': mcp.ToolProperty{
typ: 'string' typ: 'string'
} }
'types': mcp.ToolProperty{ 'types': mcp.ToolProperty{
typ: 'object' typ: 'object'
} }
} }
required: ['function'] required: ['function']
} }
} }
pub fn (d &Developer) create_mcp_tool_tool_handler(arguments map[string]Any) !mcp.ToolCallResult { pub fn (d &Developer) create_mcp_tool_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
function := arguments['function'].str() function := arguments['function'].str()
types := json.decode[map[string]string](arguments['types'].str())! types := json.decode[map[string]string](arguments['types'].str())!
result := d.create_mcp_tool(function, types) or { result := d.create_mcp_tool(function, types) or { return mcp.error_tool_call_result(err) }
return mcp.error_tool_call_result(err)
}
return mcp.ToolCallResult{ return mcp.ToolCallResult{
is_error: false is_error: false
content: result_to_mcp_tool_contents[string](result.str()) content: result_to_mcp_tool_contents[string](result.str())
} }
} }
// Tool definition for the create_mcp_tool_handler function // Tool definition for the create_mcp_tool_handler function
const create_mcp_tool_handler_tool = mcp.Tool{ const create_mcp_tool_handler_tool = mcp.Tool{
name: 'create_mcp_tool_handler' name: 'create_mcp_tool_handler'
description: 'Generates a tool handler for the create_mcp_tool function. This tool handler accepts function string and types map and returns an MCP ToolCallResult.' description: 'Generates a tool handler for the create_mcp_tool function. This tool handler accepts function string and types map and returns an MCP ToolCallResult.'
input_schema: mcp.ToolInputSchema{ input_schema: mcp.ToolInputSchema{
typ: 'object' typ: 'object'
properties: { properties: {
'function': mcp.ToolProperty{ 'function': mcp.ToolProperty{
typ: 'string' typ: 'string'
}, }
'types': mcp.ToolProperty{ 'types': mcp.ToolProperty{
typ: 'object' typ: 'object'
}, }
'result': mcp.ToolProperty{ 'result': mcp.ToolProperty{
typ: 'string' typ: 'string'
} }
} }
required: ['function', 'result'] required: ['function', 'result']
} }
} }
@@ -103,6 +103,6 @@ pub fn (d &Developer) create_mcp_tool_handler_tool_handler(arguments map[string]
} }
return mcp.ToolCallResult{ return mcp.ToolCallResult{
is_error: false is_error: false
content: result_to_mcp_tool_contents[string](result) content: result_to_mcp_tool_contents[string](result)
} }
} }

31
TOSORT/developer/mcp.v Normal file
View File

@@ -0,0 +1,31 @@
module developer
import freeflowuniverse.herolib.mcp.logger
import freeflowuniverse.herolib.mcp
import freeflowuniverse.herolib.schemas.jsonrpc
// pub fn new_mcp_server(d &Developer) !&mcp.Server {
// logger.info('Creating new Developer MCP server')
// // Initialize the server with the empty handlers map
// mut server := mcp.new_server(mcp.MemoryBackend{
// tools: {
// 'create_mcp_tool': create_mcp_tool_tool
// 'create_mcp_tool_handler': create_mcp_tool_handler_tool
// 'create_mcp_tool_code': create_mcp_tool_code_tool
// }
// tool_handlers: {
// 'create_mcp_tool': d.create_mcp_tool_tool_handler
// 'create_mcp_tool_handler': d.create_mcp_tool_handler_tool_handler
// 'create_mcp_tool_code': d.create_mcp_tool_code_tool_handler
// }
// }, mcp.ServerParams{
// config: mcp.ServerConfiguration{
// server_info: mcp.ServerInfo{
// name: 'developer'
// version: '1.0.0'
// }
// }
// })!
// return server
// }

View File

@@ -5,7 +5,6 @@ import freeflowuniverse.herolib.mcp.logger
mut server := developer.new_mcp_server(&developer.Developer{})! mut server := developer.new_mcp_server(&developer.Developer{})!
server.start() or { server.start() or {
logger.fatal('Error starting server: $err') logger.fatal('Error starting server: ${err}')
exit(1) exit(1)
} }

View File

@@ -3,10 +3,10 @@ module mock_module
// TestConfig represents a configuration for testing // TestConfig represents a configuration for testing
pub struct TestConfig { pub struct TestConfig {
pub: pub:
name string name string
enabled bool enabled bool
count int count int
value float64 value float64
} }
// TestResult represents the result of a test operation // TestResult represents the result of a test operation
@@ -14,7 +14,7 @@ pub struct TestResult {
pub: pub:
success bool success bool
message string message string
code int code int
} }
// test_function is a simple function for testing the MCP tool code generation // test_function is a simple function for testing the MCP tool code generation
@@ -28,7 +28,7 @@ pub fn test_function(config TestConfig) !TestResult {
return TestResult{ return TestResult{
success: config.enabled success: config.enabled
message: 'Test completed for ${config.name}' message: 'Test completed for ${config.name}'
code: if config.enabled { 0 } else { 1 } code: if config.enabled { 0 } else { 1 }
} }
} }

View File

@@ -12,13 +12,13 @@ fn get_type_from_module(module_path string, type_name string) !string {
} }
for v_file in v_files { for v_file in v_files {
content := os.read_file(v_file) or { content := os.read_file(v_file) or { return error('Failed to read file ${v_file}: ${err}') }
return error('Failed to read file ${v_file}: ${err}')
}
type_str := 'struct ${type_name} {' type_str := 'struct ${type_name} {'
i := content.index(type_str) or { -1 } i := content.index(type_str) or { -1 }
if i == -1 { continue } if i == -1 {
continue
}
start_i := i + type_str.len start_i := i + type_str.len
closing_i := find_closing_brace(content, start_i) or { closing_i := find_closing_brace(content, start_i) or {
@@ -49,9 +49,7 @@ fn find_closing_brace(content string, start_i int) ?int {
// Helper function to list V files // Helper function to list V files
fn list_v_files(dir string) ![]string { fn list_v_files(dir string) ![]string {
files := os.ls(dir) or { files := os.ls(dir) or { return error('Error listing directory: ${err}') }
return error('Error listing directory: $err')
}
mut v_files := []string{} mut v_files := []string{}
for file in files { for file in files {
@@ -71,7 +69,7 @@ fn create_test_files() !(string, string, string) {
test_file_path := os.join_path(test_dir, 'test_type.v') test_file_path := os.join_path(test_dir, 'test_type.v')
// Create a test file with a simple struct // Create a test file with a simple struct
test_content := "module test_module test_content := 'module test_module
struct TestType { struct TestType {
name string name string
@@ -83,14 +81,14 @@ struct TestType {
struct OtherType { struct OtherType {
id string id string
} }
" '
os.write_file(test_file_path, test_content) or { os.write_file(test_file_path, test_content) or {
eprintln('Failed to create test file: $err') eprintln('Failed to create test file: ${err}')
return error('Failed to create test file: $err') return error('Failed to create test file: ${err}')
} }
// Create a test file with a nested struct // Create a test file with a nested struct
nested_test_content := "module test_module nested_test_content := 'module test_module
struct NestedType { struct NestedType {
config map[string]string { config map[string]string {
@@ -101,11 +99,11 @@ struct NestedType {
value string value string
} }
} }
" '
nested_test_file := os.join_path(test_dir, 'nested_test.v') nested_test_file := os.join_path(test_dir, 'nested_test.v')
os.write_file(nested_test_file, nested_test_content) or { os.write_file(nested_test_file, nested_test_content) or {
eprintln('Failed to create nested test file: $err') eprintln('Failed to create nested test file: ${err}')
return error('Failed to create nested test file: $err') return error('Failed to create nested test file: ${err}')
} }
return test_dir, test_file_path, nested_test_file return test_dir, test_file_path, nested_test_file
@@ -115,21 +113,21 @@ struct NestedType {
fn test_get_type_from_module() { fn test_get_type_from_module() {
// Create test files // Create test files
test_dir, test_file_path, nested_test_file := create_test_files() or { test_dir, test_file_path, nested_test_file := create_test_files() or {
eprintln('Failed to create test files: $err') eprintln('Failed to create test files: ${err}')
assert false assert false
return return
} }
// Test case 1: Get a simple struct // Test case 1: Get a simple struct
type_content := get_type_from_module(test_dir, 'TestType') or { type_content := get_type_from_module(test_dir, 'TestType') or {
eprintln('Failed to get type: $err') eprintln('Failed to get type: ${err}')
assert false assert false
return return
} }
// Verify the content matches what we expect // Verify the content matches what we expect
expected := "\n\tname string\n\tage int\n\tactive bool\n}" expected := '\n\tname string\n\tage int\n\tactive bool\n}'
assert type_content == expected, 'Expected: "$expected", got: "$type_content"' assert type_content == expected, 'Expected: "${expected}", got: "${type_content}"'
// Test case 2: Try to get a non-existent type // Test case 2: Try to get a non-existent type
non_existent := get_type_from_module(test_dir, 'NonExistentType') or { non_existent := get_type_from_module(test_dir, 'NonExistentType') or {
@@ -141,13 +139,13 @@ fn test_get_type_from_module() {
// Test case 3: Test with nested braces in the struct // Test case 3: Test with nested braces in the struct
nested_type_content := get_type_from_module(test_dir, 'NestedType') or { nested_type_content := get_type_from_module(test_dir, 'NestedType') or {
eprintln('Failed to get nested type: $err') eprintln('Failed to get nested type: ${err}')
assert false assert false
return return
} }
expected_nested := "\n\tconfig map[string]string {\n\t\trequired: true\n\t}\n\tdata []struct {\n\t\tkey string\n\t\tvalue string\n\t}\n}" expected_nested := '\n\tconfig map[string]string {\n\t\trequired: true\n\t}\n\tdata []struct {\n\t\tkey string\n\t\tvalue string\n\t}\n}'
assert nested_type_content == expected_nested, 'Expected: "$expected_nested", got: "$nested_type_content"' assert nested_type_content == expected_nested, 'Expected: "${expected_nested}", got: "${nested_type_content}"'
// Clean up test files // Clean up test files
os.rm(test_file_path) or {} os.rm(test_file_path) or {}

View File

@@ -7,7 +7,7 @@ import log
fn get_module_dir(mod string) string { fn get_module_dir(mod string) string {
module_parts := mod.trim_string_left('freeflowuniverse.herolib').split('.') module_parts := mod.trim_string_left('freeflowuniverse.herolib').split('.')
return '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/${module_parts.join("/")}' return '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/${module_parts.join('/')}'
} }
// given a module path and a type name, returns the type definition of that type within that module // given a module path and a type name, returns the type definition of that type within that module
@@ -20,10 +20,7 @@ fn get_type_from_module(module_path string, type_name string) !string {
for v_file in v_files { for v_file in v_files {
println('Checking file: ${v_file}') println('Checking file: ${v_file}')
content := os.read_file(v_file) or { content := os.read_file(v_file) or { return error('Failed to read file ${v_file}: ${err}') }
return error('Failed to read file ${v_file}: ${err}')
}
// Look for both regular and pub struct declarations // Look for both regular and pub struct declarations
mut type_str := 'struct ${type_name} {' mut type_str := 'struct ${type_name} {'
@@ -38,7 +35,8 @@ fn get_type_from_module(module_path string, type_name string) !string {
} }
if i == -1 { if i == -1 {
type_import := content.split_into_lines().filter(it.contains('import') && it.contains(type_name)) type_import := content.split_into_lines().filter(it.contains('import')
&& it.contains(type_name))
if type_import.len > 0 { if type_import.len > 0 {
log.debug('debugzoooo') log.debug('debugzoooo')
mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ') mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ')
@@ -54,7 +52,7 @@ fn get_type_from_module(module_path string, type_name string) !string {
// Find the start of the line containing the struct definition // Find the start of the line containing the struct definition
for j := i; j >= 0; j-- { for j := i; j >= 0; j-- {
if j == 0 || content[j-1] == `\n` { if j == 0 || content[j - 1] == `\n` {
line_start = j line_start = j
break break
} }
@@ -134,9 +132,6 @@ fn find_closing_brace(content string, start_i int) ?int {
return none return none
} }
// get_function_from_file parses a V file and extracts a specific function block including its comments // get_function_from_file parses a V file and extracts a specific function block including its comments
// ARGS: // ARGS:
// file_path string - path to the V file // file_path string - path to the V file
@@ -167,8 +162,8 @@ fn get_function_from_file(file_path string, function_name string) !string {
} }
// Check if we found the function // Check if we found the function
if !in_function && (trimmed.starts_with('fn ${function_name}(') || if !in_function && (trimmed.starts_with('fn ${function_name}(')
trimmed.starts_with('pub fn ${function_name}(')) { || trimmed.starts_with('pub fn ${function_name}(')) {
in_function = true in_function = true
// Add collected comments // Add collected comments
result << comment_block result << comment_block
@@ -213,9 +208,7 @@ fn get_function_from_file(file_path string, function_name string) !string {
// list_v_files returns all .v files in a directory (non-recursive), excluding generated files ending with _.v // list_v_files returns all .v files in a directory (non-recursive), excluding generated files ending with _.v
fn list_v_files(dir string) ![]string { fn list_v_files(dir string) ![]string {
files := os.ls(dir) or { files := os.ls(dir) or { return error('Error listing directory: ${err}') }
return error('Error listing directory: $err')
}
mut v_files := []string{} mut v_files := []string{}
for file in files { for file in files {
@@ -230,47 +223,44 @@ fn list_v_files(dir string) ![]string {
// test runs v test on the specified file or directory // test runs v test on the specified file or directory
pub fn vtest(fullpath string) !string { pub fn vtest(fullpath string) !string {
logger.info('test $fullpath') logger.info('test ${fullpath}')
if !os.exists(fullpath) { if !os.exists(fullpath) {
return error('File or directory does not exist: $fullpath') return error('File or directory does not exist: ${fullpath}')
} }
if os.is_dir(fullpath) { if os.is_dir(fullpath) {
mut results:="" mut results := ''
for item in list_v_files(fullpath)!{ for item in list_v_files(fullpath)! {
results += vtest(item)! results += vtest(item)!
results += '\n-----------------------\n' results += '\n-----------------------\n'
} }
return results return results
}else{ } else {
cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}' cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}'
logger.debug('Executing command: $cmd') logger.debug('Executing command: ${cmd}')
result := os.execute(cmd) result := os.execute(cmd)
if result.exit_code != 0 { if result.exit_code != 0 {
return error('Test failed for $fullpath with exit code ${result.exit_code}\n${result.output}') return error('Test failed for ${fullpath} with exit code ${result.exit_code}\n${result.output}')
} else { } else {
logger.info('Test completed for $fullpath') logger.info('Test completed for ${fullpath}')
} }
return 'Command: $cmd\nExit code: ${result.exit_code}\nOutput:\n${result.output}' return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
} }
} }
// vvet runs v vet on the specified file or directory // vvet runs v vet on the specified file or directory
pub fn vvet(fullpath string) !string { pub fn vvet(fullpath string) !string {
logger.info('vet $fullpath') logger.info('vet ${fullpath}')
if !os.exists(fullpath) { if !os.exists(fullpath) {
return error('File or directory does not exist: $fullpath') return error('File or directory does not exist: ${fullpath}')
} }
if os.is_dir(fullpath) { if os.is_dir(fullpath) {
mut results := "" mut results := ''
files := list_v_files(fullpath) or { files := list_v_files(fullpath) or { return error('Error listing V files: ${err}') }
return error('Error listing V files: $err')
}
for file in files { for file in files {
results += vet_file(file) or { results += vet_file(file) or {
logger.error('Failed to vet $file: $err') logger.error('Failed to vet ${file}: ${err}')
return error('Failed to vet $file: $err') return error('Failed to vet ${file}: ${err}')
} }
results += '\n-----------------------\n' results += '\n-----------------------\n'
} }
@@ -283,14 +273,14 @@ pub fn vvet(fullpath string) !string {
// vet_file runs v vet on a single file // vet_file runs v vet on a single file
fn vet_file(file string) !string { fn vet_file(file string) !string {
cmd := 'v vet -v -w ${file}' cmd := 'v vet -v -w ${file}'
logger.debug('Executing command: $cmd') logger.debug('Executing command: ${cmd}')
result := os.execute(cmd) result := os.execute(cmd)
if result.exit_code != 0 { if result.exit_code != 0 {
return error('Vet failed for $file with exit code ${result.exit_code}\n${result.output}') return error('Vet failed for ${file} with exit code ${result.exit_code}\n${result.output}')
} else { } else {
logger.info('Vet completed for $file') logger.info('Vet completed for ${file}')
} }
return 'Command: $cmd\nExit code: ${result.exit_code}\nOutput:\n${result.output}' return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
} }
// cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc ${fullpath}' // cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc ${fullpath}'

View File

@@ -14,7 +14,7 @@ fn create_test_files() !(string, string, string) {
test_file_path := os.join_path(test_dir, 'test_type.v') test_file_path := os.join_path(test_dir, 'test_type.v')
// Create a test file with a simple struct // Create a test file with a simple struct
test_content := "module test_module test_content := 'module test_module
struct TestType { struct TestType {
name string name string
@@ -26,14 +26,14 @@ struct TestType {
struct OtherType { struct OtherType {
id string id string
} }
" '
os.write_file(test_file_path, test_content) or { os.write_file(test_file_path, test_content) or {
eprintln('Failed to create test file: $err') eprintln('Failed to create test file: ${err}')
return error('Failed to create test file: $err') return error('Failed to create test file: ${err}')
} }
// Create a test file with a nested struct // Create a test file with a nested struct
nested_test_content := "module test_module nested_test_content := 'module test_module
struct NestedType { struct NestedType {
config map[string]string { config map[string]string {
@@ -44,11 +44,11 @@ struct NestedType {
value string value string
} }
} }
" '
nested_test_file := os.join_path(test_dir, 'nested_test.v') nested_test_file := os.join_path(test_dir, 'nested_test.v')
os.write_file(nested_test_file, nested_test_content) or { os.write_file(nested_test_file, nested_test_content) or {
eprintln('Failed to create nested test file: $err') eprintln('Failed to create nested test file: ${err}')
return error('Failed to create nested test file: $err') return error('Failed to create nested test file: ${err}')
} }
return test_dir, test_file_path, nested_test_file return test_dir, test_file_path, nested_test_file
@@ -58,21 +58,21 @@ struct NestedType {
fn test_get_type_from_module() { fn test_get_type_from_module() {
// Create test files // Create test files
test_dir, test_file_path, nested_test_file := create_test_files() or { test_dir, test_file_path, nested_test_file := create_test_files() or {
eprintln('Failed to create test files: $err') eprintln('Failed to create test files: ${err}')
assert false assert false
return return
} }
// Test case 1: Get a simple struct // Test case 1: Get a simple struct
type_content := get_type_from_module(test_dir, 'TestType') or { type_content := get_type_from_module(test_dir, 'TestType') or {
eprintln('Failed to get type: $err') eprintln('Failed to get type: ${err}')
assert false assert false
return return
} }
// Verify the content matches what we expect // Verify the content matches what we expect
expected := "\n\tname string\n\tage int\n\tactive bool\n}" expected := '\n\tname string\n\tage int\n\tactive bool\n}'
assert type_content == expected, 'Expected: "$expected", got: "$type_content"' assert type_content == expected, 'Expected: "${expected}", got: "${type_content}"'
// Test case 2: Try to get a non-existent type // Test case 2: Try to get a non-existent type
non_existent := get_type_from_module(test_dir, 'NonExistentType') or { non_existent := get_type_from_module(test_dir, 'NonExistentType') or {
@@ -84,13 +84,13 @@ fn test_get_type_from_module() {
// Test case 3: Test with nested braces in the struct // Test case 3: Test with nested braces in the struct
nested_type_content := get_type_from_module(test_dir, 'NestedType') or { nested_type_content := get_type_from_module(test_dir, 'NestedType') or {
eprintln('Failed to get nested type: $err') eprintln('Failed to get nested type: ${err}')
assert false assert false
return return
} }
expected_nested := "\n\tconfig map[string]string {\n\t\trequired: true\n\t}\n\tdata []struct {\n\t\tkey string\n\t\tvalue string\n\t}\n}" expected_nested := '\n\tconfig map[string]string {\n\t\trequired: true\n\t}\n\tdata []struct {\n\t\tkey string\n\t\tvalue string\n\t}\n}'
assert nested_type_content == expected_nested, 'Expected: "$expected_nested", got: "$nested_type_content"' assert nested_type_content == expected_nested, 'Expected: "${expected_nested}", got: "${nested_type_content}"'
// Clean up test files // Clean up test files
os.rm(test_file_path) or {} os.rm(test_file_path) or {}

View File

@@ -0,0 +1,34 @@
module developer
import freeflowuniverse.herolib.mcp
const get_function_from_file_tool = mcp.Tool{
name: 'get_function_from_file'
description: 'get_function_from_file parses a V file and extracts a specific function block including its comments
ARGS:
file_path string - path to the V file
function_name string - name of the function to extract
RETURNS: string - the function block including comments, or empty string if not found'
input_schema: mcp.ToolInputSchema{
typ: 'object'
properties: {
'file_path': mcp.ToolProperty{
typ: 'string'
items: mcp.ToolItems{
typ: ''
enum: []
}
enum: []
}
'function_name': mcp.ToolProperty{
typ: 'string'
items: mcp.ToolItems{
typ: ''
enum: []
}
enum: []
}
}
required: ['file_path', 'function_name']
}
}

View File

@@ -46,17 +46,17 @@ fn main() {
println('\nTraining a classifier:') println('\nTraining a classifier:')
examples := [ examples := [
jina.TrainingExample{ jina.TrainingExample{
text: 'This movie was great!' text: 'This movie was great!'
label: 'positive' label: 'positive'
}, },
jina.TrainingExample{ jina.TrainingExample{
text: 'I did not like this movie.' text: 'I did not like this movie.'
label: 'negative' label: 'negative'
}, },
jina.TrainingExample{ jina.TrainingExample{
text: 'The movie was okay.' text: 'The movie was okay.'
label: 'neutral' label: 'neutral'
} },
] ]
training_result := client.train(examples, model, 'private') or { training_result := client.train(examples, model, 'private') or {

View File

@@ -12,12 +12,11 @@ const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
openrpc_spec := openrpc.new(path: openrpc_spec_path)! openrpc_spec := openrpc.new(path: openrpc_spec_path)!
actor_spec := specification.from_openrpc(openrpc_spec)! actor_spec := specification.from_openrpc(openrpc_spec)!
actor_module := generator.generate_actor_module( actor_module := generator.generate_actor_module(actor_spec,
actor_spec,
interfaces: [.openrpc] interfaces: [.openrpc]
)! )!
actor_module.write(example_dir, actor_module.write(example_dir,
format: true format: true
overwrite: true overwrite: true
)! )!

View File

@@ -14,6 +14,6 @@ actor_spec := specification.from_openrpc(openrpc_spec)!
methods_file := generator.generate_methods_file(actor_spec)! methods_file := generator.generate_methods_file(actor_spec)!
methods_file.write(example_dir, methods_file.write(example_dir,
format: true format: true
overwrite: true overwrite: true
)! )!

View File

@@ -14,6 +14,6 @@ actor_spec := specification.from_openrpc(openrpc_spec_)!
openrpc_spec := actor_spec.to_openrpc() openrpc_spec := actor_spec.to_openrpc()
openrpc_file := generator.generate_openrpc_file(openrpc_spec)! openrpc_file := generator.generate_openrpc_file(openrpc_spec)!
openrpc_file.write(os.join_path(example_dir,'docs'), openrpc_file.write(os.join_path(example_dir, 'docs'),
overwrite: true overwrite: true
)! )!

View File

@@ -5,7 +5,6 @@ import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openapi import freeflowuniverse.herolib.schemas.openapi
import os import os
const example_dir = os.dir(@FILE) const example_dir = os.dir(@FILE)
const specs = ['merchant', 'profiler', 'farmer'] const specs = ['merchant', 'profiler', 'farmer']
@@ -13,13 +12,12 @@ for spec in specs {
openapi_spec_path := os.join_path(example_dir, '${spec}.json') openapi_spec_path := os.join_path(example_dir, '${spec}.json')
openapi_spec := openapi.new(path: openapi_spec_path, process: true)! openapi_spec := openapi.new(path: openapi_spec_path, process: true)!
actor_spec := specification.from_openapi(openapi_spec)! actor_spec := specification.from_openapi(openapi_spec)!
actor_module := generator.generate_actor_folder( actor_module := generator.generate_actor_folder(actor_spec,
actor_spec,
interfaces: [.openapi, .http] interfaces: [.openapi, .http]
)! )!
actor_module.write(example_dir, actor_module.write(example_dir,
format: true format: true
overwrite: true overwrite: true
compile: false compile: false
)! )!
} }

View File

@@ -15,67 +15,67 @@ pub:
name string name string
description string description string
// technical specifications // technical specifications
specs map[string]string specs map[string]string
// price per unit // price per unit
price f64 price f64
// currency code (e.g., 'USD', 'EUR') // currency code (e.g., 'USD', 'EUR')
currency string currency string
} }
pub struct ProductTemplate { pub struct ProductTemplate {
pub: pub:
id string id string
name string name string
description string description string
// components that make up this product template // components that make up this product template
components []ProductComponentTemplate components []ProductComponentTemplate
// merchant who created this template // merchant who created this template
merchant_id string merchant_id string
// category of the product (e.g., 'electronics', 'clothing') // category of the product (e.g., 'electronics', 'clothing')
category string category string
// whether this template is available for use // whether this template is available for use
active bool active bool
} }
pub struct Product { pub struct Product {
pub: pub:
id string id string
template_id string template_id string
// specific instance details that may differ from template // specific instance details that may differ from template
name string name string
description string description string
// actual price of this product instance // actual price of this product instance
price f64 price f64
currency string currency string
// merchant selling this product // merchant selling this product
merchant_id string merchant_id string
// current stock level // current stock level
stock_quantity int stock_quantity int
// whether this product is available for purchase // whether this product is available for purchase
available bool available bool
} }
pub struct OrderItem { pub struct OrderItem {
pub: pub:
product_id string product_id string
quantity int quantity int
price f64 price f64
currency string currency string
} }
pub struct Order { pub struct Order {
pub: pub:
id string id string
// customer identifier // customer identifier
customer_id string customer_id string
// items in the order // items in the order
items []OrderItem items []OrderItem
// total order amount // total order amount
total_amount f64 total_amount f64
currency string currency string
// order status (e.g., 'pending', 'confirmed', 'shipped', 'delivered') // order status (e.g., 'pending', 'confirmed', 'shipped', 'delivered')
status string status string
// timestamps // timestamps
created_at string created_at string
updated_at string updated_at string
} }

View File

@@ -3,7 +3,7 @@ module geomind_poc
import freeflowuniverse.crystallib.core.playbook { PlayBook } import freeflowuniverse.crystallib.core.playbook { PlayBook }
// play_commerce processes heroscript actions for the commerce system // play_commerce processes heroscript actions for the commerce system
pub fn play_commerce(mut plbook playbook.PlayBook) ! { pub fn play_commerce(mut plbook PlayBook) ! {
commerce_actions := plbook.find(filter: 'commerce.')! commerce_actions := plbook.find(filter: 'commerce.')!
mut c := Commerce{} mut c := Commerce{}
@@ -12,20 +12,20 @@ pub fn play_commerce(mut plbook playbook.PlayBook) ! {
'merchant' { 'merchant' {
mut p := action.params mut p := action.params
merchant := c.create_merchant( merchant := c.create_merchant(
name: p.get('name')!, name: p.get('name')!
description: p.get_default('description', '')!, description: p.get_default('description', '')!
contact: p.get('contact')! contact: p.get('contact')!
)! )!
println('Created merchant: ${merchant.name}') println('Created merchant: ${merchant.name}')
} }
'component' { 'component' {
mut p := action.params mut p := action.params
component := c.create_product_component_template( component := c.create_product_component_template(
name: p.get('name')!, name: p.get('name')!
description: p.get_default('description', '')!, description: p.get_default('description', '')!
specs: p.get_map(), specs: p.get_map()
price: p.get_float('price')!, price: p.get_float('price')!
currency: p.get('currency')! currency: p.get('currency')!
)! )!
println('Created component: ${component.name}') println('Created component: ${component.name}')
} }
@@ -39,30 +39,30 @@ pub fn play_commerce(mut plbook playbook.PlayBook) ! {
// In a real implementation, you would fetch the component from storage // In a real implementation, you would fetch the component from storage
// For this example, we create a dummy component // For this example, we create a dummy component
component := ProductComponentTemplate{ component := ProductComponentTemplate{
id: id id: id
name: 'Component' name: 'Component'
description: '' description: ''
specs: map[string]string{} specs: map[string]string{}
price: 0 price: 0
currency: 'USD' currency: 'USD'
} }
components << component components << component
} }
template := c.create_product_template( template := c.create_product_template(
name: p.get('name')!, name: p.get('name')!
description: p.get_default('description', '')!, description: p.get_default('description', '')!
components: components, components: components
merchant_id: p.get('merchant_id')!, merchant_id: p.get('merchant_id')!
category: p.get_default('category', 'General')! category: p.get_default('category', 'General')!
)! )!
println('Created template: ${template.name}') println('Created template: ${template.name}')
} }
'product' { 'product' {
mut p := action.params mut p := action.params
product := c.create_product( product := c.create_product(
template_id: p.get('template_id')!, template_id: p.get('template_id')!
merchant_id: p.get('merchant_id')!, merchant_id: p.get('merchant_id')!
stock_quantity: p.get_int('stock_quantity')! stock_quantity: p.get_int('stock_quantity')!
)! )!
println('Created product: ${product.name} with stock: ${product.stock_quantity}') println('Created product: ${product.name} with stock: ${product.stock_quantity}')
@@ -80,23 +80,23 @@ pub fn play_commerce(mut plbook playbook.PlayBook) ! {
} }
item := OrderItem{ item := OrderItem{
product_id: parts[0] product_id: parts[0]
quantity: parts[1].int() quantity: parts[1].int()
price: parts[2].f64() price: parts[2].f64()
currency: parts[3] currency: parts[3]
} }
items << item items << item
} }
order := c.create_order( order := c.create_order(
customer_id: p.get('customer_id')!, customer_id: p.get('customer_id')!
items: items items: items
)! )!
println('Created order: ${order.id} with ${order.items.len} items') println('Created order: ${order.id} with ${order.items.len} items')
} }
'update_order' { 'update_order' {
mut p := action.params mut p := action.params
order := c.update_order_status( order := c.update_order_status(
order_id: p.get('order_id')!, order_id: p.get('order_id')!
new_status: p.get('status')! new_status: p.get('status')!
)! )!
println('Updated order ${order.id} status to: ${order.status}') println('Updated order ${order.id} status to: ${order.status}')

View File

@@ -1,14 +1,15 @@
module geomind_poc module geomind_poc
import crypto.rand import crypto.rand
import time
// Commerce represents the main e-commerce server handling all operations // Commerce represents the main e-commerce server handling all operations
pub struct Commerce { pub struct Commerce {
mut: mut:
merchants map[string]Merchant merchants map[string]Merchant
templates map[string]ProductTemplate templates map[string]ProductTemplate
products map[string]Product products map[string]Product
orders map[string]Order orders map[string]Order
} }
// generate_id creates a unique identifier // generate_id creates a unique identifier
@@ -20,11 +21,11 @@ fn generate_id() string {
pub fn (mut c Commerce) create_merchant(name string, description string, contact string) !Merchant { pub fn (mut c Commerce) create_merchant(name string, description string, contact string) !Merchant {
merchant_id := generate_id() merchant_id := generate_id()
merchant := Merchant{ merchant := Merchant{
id: merchant_id id: merchant_id
name: name name: name
description: description description: description
contact: contact contact: contact
active: true active: true
} }
c.merchants[merchant_id] = merchant c.merchants[merchant_id] = merchant
return merchant return merchant
@@ -33,12 +34,12 @@ pub fn (mut c Commerce) create_merchant(name string, description string, contact
// create_product_component_template creates a new component template // create_product_component_template creates a new component template
pub fn (mut c Commerce) create_product_component_template(name string, description string, specs map[string]string, price f64, currency string) !ProductComponentTemplate { pub fn (mut c Commerce) create_product_component_template(name string, description string, specs map[string]string, price f64, currency string) !ProductComponentTemplate {
component := ProductComponentTemplate{ component := ProductComponentTemplate{
id: generate_id() id: generate_id()
name: name name: name
description: description description: description
specs: specs specs: specs
price: price price: price
currency: currency currency: currency
} }
return component return component
} }
@@ -50,13 +51,13 @@ pub fn (mut c Commerce) create_product_template(name string, description string,
} }
template := ProductTemplate{ template := ProductTemplate{
id: generate_id() id: generate_id()
name: name name: name
description: description description: description
components: components components: components
merchant_id: merchant_id merchant_id: merchant_id
category: category category: category
active: true active: true
} }
c.templates[template.id] = template c.templates[template.id] = template
return template return template
@@ -78,15 +79,15 @@ pub fn (mut c Commerce) create_product(template_id string, merchant_id string, s
} }
product := Product{ product := Product{
id: generate_id() id: generate_id()
template_id: template_id template_id: template_id
name: template.name name: template.name
description: template.description description: template.description
price: total_price price: total_price
currency: template.components[0].currency // assuming all components use same currency currency: template.components[0].currency // assuming all components use same currency
merchant_id: merchant_id merchant_id: merchant_id
stock_quantity: stock_quantity stock_quantity: stock_quantity
available: true available: true
} }
c.products[product.id] = product c.products[product.id] = product
return product return product
@@ -114,14 +115,14 @@ pub fn (mut c Commerce) create_order(customer_id string, items []OrderItem) !Ord
} }
order := Order{ order := Order{
id: generate_id() id: generate_id()
customer_id: customer_id customer_id: customer_id
items: items items: items
total_amount: total_amount total_amount: total_amount
currency: currency currency: currency
status: 'pending' status: 'pending'
created_at: time.now().str() created_at: time.now().str()
updated_at: time.now().str() updated_at: time.now().str()
} }
c.orders[order.id] = order c.orders[order.id] = order

View File

@@ -5,20 +5,21 @@ import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.schemas.openapi import freeflowuniverse.herolib.schemas.openapi
import os import os
const example_dir = os.join_path('${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/circles/mcc', 'baobab') const example_dir = os.join_path('${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/circles/mcc',
const openapi_spec_path = os.join_path('${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/circles/mcc', 'openapi.json') 'baobab')
const openapi_spec_path = os.join_path('${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/circles/mcc',
'openapi.json')
// the actor specification obtained from the OpenRPC Specification // the actor specification obtained from the OpenRPC Specification
openapi_spec := openapi.new(path: openapi_spec_path)! openapi_spec := openapi.new(path: openapi_spec_path)!
actor_spec := specification.from_openapi(openapi_spec)! actor_spec := specification.from_openapi(openapi_spec)!
actor_module := generator.generate_actor_module( actor_module := generator.generate_actor_module(actor_spec,
actor_spec,
interfaces: [.openapi, .http] interfaces: [.openapi, .http]
)! )!
actor_module.write(example_dir, actor_module.write(example_dir,
format: true format: true
overwrite: true overwrite: true
compile: false compile: false
)! )!

View File

@@ -14,15 +14,14 @@ actor_spec := specification.from_openapi(openapi_spec)!
println(actor_spec) println(actor_spec)
actor_module := generator.generate_actor_module( actor_module := generator.generate_actor_module(actor_spec,
actor_spec,
interfaces: [.openapi, .http] interfaces: [.openapi, .http]
)! )!
actor_module.write(example_dir, actor_module.write(example_dir,
format: false format: false
overwrite: true overwrite: true
compile: false compile: false
)! )!
// os.execvp('bash', ['${example_dir}/meeting_scheduler_actor/scripts/run.sh'])! // os.execvp('bash', ['${example_dir}/meeting_scheduler_actor/scripts/run.sh'])!

View File

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

View File

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

View File

@@ -8,13 +8,10 @@ const build_path = os.join_path(os.dir(@FILE), '/docusaurus')
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel' buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
mut model := bizmodel.generate("test", playbook_path)! mut model := bizmodel.generate('test', playbook_path)!
println(model.sheet) println(model.sheet)
println(model.sheet.export()!) println(model.sheet.export()!)
model.sheet.export(path:"~/Downloads/test.csv")! model.sheet.export(path: '~/Downloads/test.csv')!
model.sheet.export(path:"~/code/github/freeflowuniverse/starlight_template/src/content/test.csv")! model.sheet.export(path: '~/code/github/freeflowuniverse/starlight_template/src/content/test.csv')!

View File

@@ -6,13 +6,13 @@ import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.core.playcmds import freeflowuniverse.herolib.core.playcmds
import os import os
//TODO: need to fix wrong location // TODO: need to fix wrong location
const playbook_path = os.dir(@FILE) + '/playbook' const playbook_path = os.dir(@FILE) + '/playbook'
const build_path = os.join_path(os.dir(@FILE), '/docusaurus') const build_path = os.join_path(os.dir(@FILE), '/docusaurus')
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel' buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
mut model := bizmodel.getset("example")! mut model := bizmodel.getset('example')!
model.workdir = build_path model.workdir = build_path
model.play(mut playbook.new(path: playbook_path)!)! model.play(mut playbook.new(path: playbook_path)!)!
@@ -22,16 +22,13 @@ println(model.sheet.export()!)
// model.sheet.export(path:"~/Downloads/test.csv")! // model.sheet.export(path:"~/Downloads/test.csv")!
// model.sheet.export(path:"~/code/github/freeflowuniverse/starlight_template/src/content/test.csv")! // model.sheet.export(path:"~/code/github/freeflowuniverse/starlight_template/src/content/test.csv")!
report := model.new_report( report := model.new_report(
name: 'example_report' name: 'example_report'
title: 'Example Business Model' title: 'Example Business Model'
)! )!
report.export( report.export(
path: build_path path: build_path
overwrite: true overwrite: true
format: .docusaurus format: .docusaurus
)! )!

View File

@@ -15,9 +15,9 @@ fn main() {
} }
// Get the configured client // Get the configured client
mut client := openai.OpenAI { mut client := openai.OpenAI{
name: 'groq' name: 'groq'
api_key: key api_key: key
server_url: 'https://api.groq.com/openai/v1' server_url: 'https://api.groq.com/openai/v1'
} }
@@ -29,9 +29,9 @@ fn main() {
res := client.chat_completion(model, openai.Messages{ res := client.chat_completion(model, openai.Messages{
messages: [ messages: [
openai.Message{ openai.Message{
role: .user role: .user
content: 'What are the key differences between Groq and other AI inference providers?' content: 'What are the key differences between Groq and other AI inference providers?'
} },
] ]
})! })!

258
examples/clients/qdrant_example.vsh Normal file → Executable file
View File

@@ -1,209 +1,85 @@
#!/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.clients.qdrant import freeflowuniverse.herolib.clients.qdrant
import os import freeflowuniverse.herolib.core.httpconnection
import flag import rand
mut fp := flag.new_flag_parser(os.args) // 1. Get the qdrant client
fp.application('qdrant_example.vsh') mut qdrant_client := qdrant.get()!
fp.version('v0.1.0')
fp.description('Example script demonstrating Qdrant client usage')
fp.skip_executable()
help_requested := fp.bool('help', `h`, false, 'Show help message') // 2. Generate collection name
if help_requested { collection_name := 'collection_' + rand.string(4)
println(fp.usage())
exit(0)
}
additional_args := fp.finalize() or { // 2. Create a new collection
eprintln(err)
println(fp.usage())
exit(1)
}
// Initialize Qdrant client created_collection := qdrant_client.create_collection(
mut client := qdrant.get(name: 'default') or { collection_name: collection_name
// If client doesn't exist, create a new one size: 15
mut new_client := qdrant.QdrantClient{ distance: 'Cosine'
name: 'default' )!
url: 'http://localhost:6333'
}
qdrant.set(new_client) or {
eprintln('Failed to set Qdrant client: ${err}')
exit(1)
}
new_client
}
println('Connected to Qdrant at ${client.url}') println('Created Collection: ${created_collection}')
// Check if Qdrant is healthy // 3. Get the created collection
is_healthy := client.health_check() or { get_collection := qdrant_client.get_collection(
eprintln('Failed to check Qdrant health: ${err}') collection_name: collection_name
exit(1) )!
}
if !is_healthy { println('Get Collection: ${get_collection}')
eprintln('Qdrant is not healthy')
exit(1)
}
println('Qdrant is healthy') // 4. Delete the created collection
// deleted_collection := qdrant_client.delete_collection(
// collection_name: collection_name
// )!
// Get service info // println('Deleted Collection: ${deleted_collection}')
service_info := client.get_service_info() or {
eprintln('Failed to get service info: ${err}')
exit(1)
}
println('Qdrant version: ${service_info.version}') // 5. List all collections
list_collection := qdrant_client.list_collections()!
println('List Collection: ${list_collection}')
// Collection name for our example // 6. Check collection existence
collection_name := 'example_collection' collection_existence := qdrant_client.is_collection_exists(
collection_name: collection_name
)!
println('Collection Existence: ${collection_existence}')
// Check if collection exists and delete it if it does // 7. Retrieve points
collections := client.list_collections() or { collection_points := qdrant_client.retrieve_points(
eprintln('Failed to list collections: ${err}') collection_name: collection_name
exit(1) ids: [
} 0,
3,
100,
]
)!
if collection_name in collections.result { println('Collection Points: ${collection_points}')
println('Collection ${collection_name} already exists, deleting it...')
client.delete_collection(collection_name: collection_name) or {
eprintln('Failed to delete collection: ${err}')
exit(1)
}
println('Collection deleted')
}
// Create a new collection // 8. Upsert points
println('Creating collection ${collection_name}...') upsert_points := qdrant_client.upsert_points(
vectors_config := qdrant.VectorsConfig{ collection_name: collection_name
size: 4 // Small size for example purposes points: [
distance: .cosine qdrant.Point{
} payload: {
'key': 'value'
}
vector: [1.0, 2.0, 3.0]
},
qdrant.Point{
payload: {
'key': 'value'
}
vector: [4.0, 5.0, 6.0]
},
qdrant.Point{
payload: {
'key': 'value'
}
vector: [7.0, 8.0, 9.0]
},
]
)!
client.create_collection( println('Upsert Points: ${upsert_points}')
collection_name: collection_name
vectors: vectors_config
) or {
eprintln('Failed to create collection: ${err}')
exit(1)
}
println('Collection created')
// Upsert some points
println('Upserting points...')
points := [
qdrant.PointStruct{
id: '1'
vector: [f32(0.1), 0.2, 0.3, 0.4]
payload: {
'color': 'red'
'category': 'furniture'
'name': 'chair'
}
},
qdrant.PointStruct{
id: '2'
vector: [f32(0.2), 0.3, 0.4, 0.5]
payload: {
'color': 'blue'
'category': 'electronics'
'name': 'laptop'
}
},
qdrant.PointStruct{
id: '3'
vector: [f32(0.3), 0.4, 0.5, 0.6]
payload: {
'color': 'green'
'category': 'food'
'name': 'apple'
}
}
]
client.upsert_points(
collection_name: collection_name
points: points
wait: true
) or {
eprintln('Failed to upsert points: ${err}')
exit(1)
}
println('Points upserted')
// Get collection info to verify points were added
collection_info := client.get_collection(collection_name: collection_name) or {
eprintln('Failed to get collection info: ${err}')
exit(1)
}
println('Collection has ${collection_info.vectors_count} points')
// Search for points
println('Searching for points similar to [0.1, 0.2, 0.3, 0.4]...')
search_result := client.search(
collection_name: collection_name
vector: [f32(0.1), 0.2, 0.3, 0.4]
limit: 3
) or {
eprintln('Failed to search points: ${err}')
exit(1)
}
println('Search results:')
for i, point in search_result.result {
println(' ${i+1}. ID: ${point.id}, Score: ${point.score}')
if payload := point.payload {
println(' Name: ${payload['name']}')
println(' Category: ${payload['category']}')
println(' Color: ${payload['color']}')
}
}
// Search with filter
println('\nSearching for points with category "electronics"...')
filter := qdrant.Filter{
must: [
qdrant.FieldCondition{
key: 'category'
match: 'electronics'
}
]
}
filtered_search := client.search(
collection_name: collection_name
vector: [f32(0.1), 0.2, 0.3, 0.4]
filter: filter
limit: 3
) or {
eprintln('Failed to search with filter: ${err}')
exit(1)
}
println('Filtered search results:')
for i, point in filtered_search.result {
println(' ${i+1}. ID: ${point.id}, Score: ${point.score}')
if payload := point.payload {
println(' Name: ${payload['name']}')
println(' Category: ${payload['category']}')
println(' Color: ${payload['color']}')
}
}
// Clean up - delete the collection
println('\nCleaning up - deleting collection...')
client.delete_collection(collection_name: collection_name) or {
eprintln('Failed to delete collection: ${err}')
exit(1)
}
println('Collection deleted')
println('Example completed successfully')

View File

@@ -5,44 +5,44 @@ import freeflowuniverse.herolib.core.jobs.model
// Create a test agent with some sample data // Create a test agent with some sample data
mut agent := model.Agent{ mut agent := model.Agent{
pubkey: 'ed25519:1234567890abcdef' pubkey: 'ed25519:1234567890abcdef'
address: '192.168.1.100' address: '192.168.1.100'
port: 9999 port: 9999
description: 'Test agent for binary encoding' description: 'Test agent for binary encoding'
status: model.AgentStatus{ status: model.AgentStatus{
guid: 'agent-123' guid: 'agent-123'
timestamp_first: ourtime.now() timestamp_first: ourtime.now()
timestamp_last: ourtime.now() timestamp_last: ourtime.now()
status: model.AgentState.ok status: model.AgentState.ok
} }
services: [] services: []
signature: 'signature-data-here' signature: 'signature-data-here'
} }
// Add a service // Add a service
mut service := model.AgentService{ mut service := model.AgentService{
actor: 'vm' actor: 'vm'
description: 'Virtual machine management' description: 'Virtual machine management'
status: model.AgentServiceState.ok status: model.AgentServiceState.ok
public: true public: true
actions: [] actions: []
} }
// Add an action to the service // Add an action to the service
mut action := model.AgentServiceAction{ mut action := model.AgentServiceAction{
action: 'create' action: 'create'
description: 'Create a new virtual machine' description: 'Create a new virtual machine'
status: model.AgentServiceState.ok status: model.AgentServiceState.ok
public: true public: true
params: { params: {
'name': 'Name of the VM' 'name': 'Name of the VM'
'memory': 'Memory in MB' 'memory': 'Memory in MB'
'cpu': 'Number of CPU cores' 'cpu': 'Number of CPU cores'
} }
params_example: { params_example: {
'name': 'my-test-vm' 'name': 'my-test-vm'
'memory': '2048' 'memory': '2048'
'cpu': '2' 'cpu': '2'
} }
} }

View File

@@ -1,7 +1,7 @@
module example_actor module example_actor
import os import os
import freeflowuniverse.herolib.hero.baobab.stage {IActor, RunParams} import freeflowuniverse.herolib.hero.baobab.stage { 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 stage.Actor
} }
fn new() !ExampleActor { fn new() !ExampleActor {
return ExampleActor{ return ExampleActor{stage.new_actor('example')}
stage.new_actor('example')
}
} }
pub fn run() ! { pub fn run() ! {

View File

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

View File

@@ -6,4 +6,3 @@ mut db := qdrant_installer.get()!
db.install()! db.install()!
db.start()! db.start()!

View File

@@ -8,5 +8,5 @@ import freeflowuniverse.herolib.core
core.interactive_set()! // make sure the sudo works so we can do things even if it requires those rights core.interactive_set()! // make sure the sudo works so we can do things even if it requires those rights
mut i1:=golang.get()! mut i1 := golang.get()!
i1.install()! i1.install()!

View File

@@ -5,6 +5,4 @@ import freeflowuniverse.herolib.installers.lang.python as python_module
mut python_installer := python_module.get()! mut python_installer := python_module.get()!
python_installer.install()! python_installer.install()!
// python_installer.destroy()! // python_installer.destroy()!

View File

@@ -7,7 +7,7 @@ import json
fn main() { fn main() {
// Initialize Jina client // Initialize Jina client
mut j := jina.Jina{ mut j := jina.Jina{
name: 'test_client' name: 'test_client'
secret: os.getenv('JINAKEY') secret: os.getenv('JINAKEY')
} }

View File

@@ -21,74 +21,64 @@ create_count := fp.int('create', `c`, 5, 'Number of jobs to create')
help_requested := fp.bool('help', `h`, false, 'Show help message') help_requested := fp.bool('help', `h`, false, 'Show help message')
if help_requested { if help_requested {
println(fp.usage()) println(fp.usage())
exit(0) exit(0)
} }
additional_args := fp.finalize() or { additional_args := fp.finalize() or {
eprintln(err) eprintln(err)
println(fp.usage()) println(fp.usage())
exit(1) exit(1)
} }
// Create a new HeroRunner instance // Create a new HeroRunner instance
mut runner := model.new() or { mut runner := model.new() or { panic('Failed to create HeroRunner: ${err}') }
panic('Failed to create HeroRunner: ${err}')
}
println('\n---------BEGIN VFS JOBS EXAMPLE') println('\n---------BEGIN VFS JOBS EXAMPLE')
// Create some jobs // Create some jobs
println('\n---------CREATING JOBS') println('\n---------CREATING JOBS')
for i in 0..create_count { for i in 0 .. create_count {
mut job := runner.jobs.new() mut job := runner.jobs.new()
job.guid = 'job_${i}_${time.now().unix}' job.guid = 'job_${i}_${time.now().unix}'
job.actor = 'example_actor' job.actor = 'example_actor'
job.action = 'test_action' job.action = 'test_action'
job.params = { job.params = {
'param1': 'value1' 'param1': 'value1'
'param2': 'value2' 'param2': 'value2'
} }
// For demonstration, make some jobs older by adjusting their creation time // For demonstration, make some jobs older by adjusting their creation time
if i % 2 == 0 { if i % 2 == 0 {
job.status.created.time = time.now().add_days(-(cleanup_days + 1)) job.status.created.time = time.now().add_days(-(cleanup_days + 1))
} }
runner.jobs.set(job) or { runner.jobs.set(job) or { panic('Failed to set job: ${err}') }
panic('Failed to set job: ${err}') println('Created job with GUID: ${job.guid}')
}
println('Created job with GUID: ${job.guid}')
} }
// List all jobs // List all jobs
println('\n---------LISTING ALL JOBS') println('\n---------LISTING ALL JOBS')
jobs := runner.jobs.list() or { jobs := runner.jobs.list() or { panic('Failed to list jobs: ${err}') }
panic('Failed to list jobs: ${err}')
}
println('Found ${jobs.len} jobs:') println('Found ${jobs.len} jobs:')
for job in jobs { for job in jobs {
days_ago := (time.now().unix - job.status.created.time.unix) / (60 * 60 * 24) days_ago := (time.now().unix - job.status.created.time.unix) / (60 * 60 * 24)
println('- ${job.guid} (created ${days_ago} days ago)') println('- ${job.guid} (created ${days_ago} days ago)')
} }
// Clean up old jobs // Clean up old jobs
println('\n---------CLEANING UP OLD JOBS') println('\n---------CLEANING UP OLD JOBS')
println('Cleaning up jobs older than ${cleanup_days} days...') println('Cleaning up jobs older than ${cleanup_days} days...')
deleted_count := runner.cleanup_jobs(cleanup_days) or { deleted_count := runner.cleanup_jobs(cleanup_days) or { panic('Failed to clean up jobs: ${err}') }
panic('Failed to clean up jobs: ${err}')
}
println('Deleted ${deleted_count} old jobs') println('Deleted ${deleted_count} old jobs')
// List remaining jobs // List remaining jobs
println('\n---------LISTING REMAINING JOBS') println('\n---------LISTING REMAINING JOBS')
remaining_jobs := runner.jobs.list() or { remaining_jobs := runner.jobs.list() or { panic('Failed to list jobs: ${err}') }
panic('Failed to list jobs: ${err}')
}
println('Found ${remaining_jobs.len} remaining jobs:') println('Found ${remaining_jobs.len} remaining jobs:')
for job in remaining_jobs { for job in remaining_jobs {
days_ago := (time.now().unix - job.status.created.time.unix) / (60 * 60 * 24) days_ago := (time.now().unix - job.status.created.time.unix) / (60 * 60 * 24)
println('- ${job.guid} (created ${days_ago} days ago)') println('- ${job.guid} (created ${days_ago} days ago)')
} }
println('\n---------END VFS JOBS EXAMPLE') println('\n---------END VFS JOBS EXAMPLE')

View File

@@ -3,7 +3,6 @@
// Calendar Typescript Client Generation Example // Calendar Typescript Client Generation Example
// This example demonstrates how to generate a typescript client // This example demonstrates how to generate a typescript client
// from a given OpenAPI Specification using the `openapi/codegen` module. // from a given OpenAPI Specification using the `openapi/codegen` module.
import os import os
import freeflowuniverse.herolib.schemas.openapi import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.schemas.openapi.codegen import freeflowuniverse.herolib.schemas.openapi.codegen
@@ -15,5 +14,3 @@ const specification = openapi.new(path: '${dir}/meeting_api.json') or {
// generate typescript client folder and write it in dir // generate typescript client folder and write it in dir
codegen.ts_client_folder(specification)!.write(dir, overwrite: true)! codegen.ts_client_folder(specification)!.write(dir, overwrite: true)!

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env -S v -gc none -cc tcc -d use_openssl -enable-globals -cg run
import freeflowuniverse.herolib.threefold.grid3.deployer
const gigabyte = u64(1024 * 1024 * 1024)
// We can use any of the parameters for the corresponding Grid Proxy query
// https://gridproxy.grid.tf/swagger/index.html#/GridProxy/get_nodes
filter := deployer.FilterNodesArgs{
size: 5
randomize: true
free_mru: 8 * gigabyte
free_sru: 50 * gigabyte
farm_name: 'FreeFarm'
status: 'up'
}
nodes := deployer.filter_nodes(filter)!
println(nodes)

View File

@@ -11,18 +11,18 @@ griddriver.install()!
v := tfgrid3deployer.get()! v := tfgrid3deployer.get()!
println('cred: ${v}') println('cred: ${v}')
deployment_name := 'herzner_dep' deployment_name := 'hetzner_dep'
mut deployment := tfgrid3deployer.new_deployment(deployment_name)! mut deployment := tfgrid3deployer.new_deployment(deployment_name)!
// TODO: find a way to filter hetzner nodes // TODO: find a way to filter hetzner nodes
deployment.add_machine( deployment.add_machine(
name: 'hetzner_vm' name: 'hetzner_vm'
cpu: 1 cpu: 2
memory: 2 memory: 5
planetary: false planetary: false
public_ip4: true public_ip4: false
size: 10 // 10 gig size: 10 // 10 gig
mycelium: tfgrid3deployer.Mycelium{} // mycelium: tfgrid3deployer.Mycelium{}
) )
deployment.deploy()! deployment.deploy()!

View File

@@ -13,8 +13,8 @@ fn main() {
// mut deployment := tfgrid3deployer.get_deployment(deployment_name)! // mut deployment := tfgrid3deployer.get_deployment(deployment_name)!
deployment.add_machine( deployment.add_machine(
name: 'my_vm1' name: 'my_vm1'
cpu: 1 cpu: 2
memory: 2 memory: 4
planetary: false planetary: false
public_ip4: false public_ip4: false
nodes: [167] nodes: [167]
@@ -32,10 +32,10 @@ fn main() {
deployment.add_webname(name: 'mywebname2', backend: 'http://37.27.132.47:8000') deployment.add_webname(name: 'mywebname2', backend: 'http://37.27.132.47:8000')
deployment.deploy()! deployment.deploy()!
deployment.remove_machine('my_vm1')! // deployment.remove_machine('my_vm1')!
deployment.remove_webname('mywebname2')! // deployment.remove_webname('mywebname2')!
deployment.remove_zdb('my_zdb')! // deployment.remove_zdb('my_zdb')!
deployment.deploy()! // deployment.deploy()!
tfgrid3deployer.delete_deployment(deployment_name)! // tfgrid3deployer.delete_deployment(deployment_name)!
} }

View File

@@ -16,7 +16,7 @@ fn deploy_vm() ! {
memory: 2 memory: 2
planetary: false planetary: false
public_ip4: true public_ip4: true
nodes: [node_id] nodes: [node_id]
) )
deployment.deploy()! deployment.deploy()!
println(deployment) println(deployment)
@@ -27,13 +27,13 @@ fn delete_vm() ! {
} }
fn main() { fn main() {
if os.args.len < 2 { if os.args.len < 2 {
println('Please provide a command: "deploy" or "delete"') println('Please provide a command: "deploy" or "delete"')
return return
} }
match os.args[1] { match os.args[1] {
'deploy' { deploy_vm()! } 'deploy' { deploy_vm()! }
'delete' { delete_vm()! } 'delete' { delete_vm()! }
else { println('Invalid command. Use "deploy" or "delete"') } else { println('Invalid command. Use "deploy" or "delete"') }
} }
} }

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env -S v -gc none -d use_openssl -enable-globals -cg run #!/usr/bin/env -S v -gc none -cc tcc -d use_openssl -enable-globals -cg run
//#!/usr/bin/env -S v -gc none -cc tcc -d use_openssl -enable-globals -cg run
import freeflowuniverse.herolib.threefold.grid3.gridproxy import freeflowuniverse.herolib.threefold.grid3.gridproxy
import freeflowuniverse.herolib.threefold.grid3.deployer import freeflowuniverse.herolib.threefold.grid3.deployer
import freeflowuniverse.herolib.installers.threefold.griddriver import freeflowuniverse.herolib.installers.threefold.griddriver
@@ -26,7 +25,7 @@ deployment.add_machine(
public_ip4: false public_ip4: false
size: 10 // 10 gig size: 10 // 10 gig
mycelium: deployer.Mycelium{} mycelium: deployer.Mycelium{}
nodes: [vm_node] nodes: [vm_node]
) )
deployment.deploy()! deployment.deploy()!

View File

@@ -11,13 +11,14 @@ pub struct VFSDedupeDB {
} }
pub fn (mut db VFSDedupeDB) set(args ourdb.OurDBSetArgs) !u32 { pub fn (mut db VFSDedupeDB) set(args ourdb.OurDBSetArgs) !u32 {
return db.store(args.data, return db.store(args.data, dedupestor.Reference{
dedupestor.Reference{owner: u16(1), id: args.id or {panic('VFS Must provide id')}} owner: u16(1)
)! id: args.id or { panic('VFS Must provide id') }
})!
} }
pub fn (mut db VFSDedupeDB) delete(id u32) ! { pub fn (mut db VFSDedupeDB) delete(id u32) ! {
db.DedupeStore.delete(id, dedupestor.Reference{owner: u16(1), id: id})! db.DedupeStore.delete(id, dedupestor.Reference{ owner: u16(1), id: id })!
} }
example_data_dir := os.join_path(os.dir(@FILE), 'example_db') example_data_dir := os.join_path(os.dir(@FILE), 'example_db')
@@ -33,35 +34,23 @@ mut db_data := VFSDedupeDB{
} }
mut db_metadata := ourdb.new( mut db_metadata := ourdb.new(
path: os.join_path(example_data_dir, 'metadata') path: os.join_path(example_data_dir, 'metadata')
incremental_mode: false incremental_mode: false
)! )!
// Create VFS with separate databases for data and metadata // Create VFS with separate databases for data and metadata
mut vfs := vfs_db.new(mut db_data, mut db_metadata) or { mut vfs := vfs_db.new(mut db_data, mut db_metadata) or { panic('Failed to create VFS: ${err}') }
panic('Failed to create VFS: ${err}')
}
println('\n---------BEGIN EXAMPLE') println('\n---------BEGIN EXAMPLE')
println('---------WRITING FILES') println('---------WRITING FILES')
vfs.file_create('/some_file.txt') or { vfs.file_create('/some_file.txt') or { panic('Failed to create file: ${err}') }
panic('Failed to create file: ${err}') vfs.file_create('/another_file.txt') or { panic('Failed to create file: ${err}') }
}
vfs.file_create('/another_file.txt') or {
panic('Failed to create file: ${err}')
}
vfs.file_write('/some_file.txt', 'gibberish'.bytes()) or { vfs.file_write('/some_file.txt', 'gibberish'.bytes()) or { panic('Failed to write file: ${err}') }
panic('Failed to write file: ${err}') vfs.file_write('/another_file.txt', 'abcdefg'.bytes()) or { panic('Failed to write file: ${err}') }
}
vfs.file_write('/another_file.txt', 'abcdefg'.bytes()) or {
panic('Failed to write file: ${err}')
}
println('\n---------READING FILES') println('\n---------READING FILES')
some_file_content := vfs.file_read('/some_file.txt') or { some_file_content := vfs.file_read('/some_file.txt') or { panic('Failed to read file: ${err}') }
panic('Failed to read file: ${err}')
}
println(some_file_content.bytestr()) println(some_file_content.bytestr())
another_file_content := vfs.file_read('/another_file.txt') or { another_file_content := vfs.file_read('/another_file.txt') or {
@@ -69,19 +58,15 @@ another_file_content := vfs.file_read('/another_file.txt') or {
} }
println(another_file_content.bytestr()) println(another_file_content.bytestr())
println("\n---------WRITING DUPLICATE FILE (DB SIZE: ${os.file_size(os.join_path(example_data_dir, 'data/0.db'))})") println('\n---------WRITING DUPLICATE FILE (DB SIZE: ${os.file_size(os.join_path(example_data_dir,
vfs.file_create('/duplicate.txt') or { 'data/0.db'))})')
panic('Failed to create file: ${err}') vfs.file_create('/duplicate.txt') or { panic('Failed to create file: ${err}') }
} vfs.file_write('/duplicate.txt', 'gibberish'.bytes()) or { panic('Failed to write file: ${err}') }
vfs.file_write('/duplicate.txt', 'gibberish'.bytes()) or {
panic('Failed to write file: ${err}')
}
println("\n---------WROTE DUPLICATE FILE (DB SIZE: ${os.file_size(os.join_path(example_data_dir, 'data/0.db'))})") println('\n---------WROTE DUPLICATE FILE (DB SIZE: ${os.file_size(os.join_path(example_data_dir,
'data/0.db'))})')
println('---------READING FILES') println('---------READING FILES')
some_file_content3 := vfs.file_read('/some_file.txt') or { some_file_content3 := vfs.file_read('/some_file.txt') or { panic('Failed to read file: ${err}') }
panic('Failed to read file: ${err}')
}
println(some_file_content3.bytestr()) println(some_file_content3.bytestr())
another_file_content3 := vfs.file_read('/another_file.txt') or { another_file_content3 := vfs.file_read('/another_file.txt') or {
@@ -89,22 +74,21 @@ another_file_content3 := vfs.file_read('/another_file.txt') or {
} }
println(another_file_content3.bytestr()) println(another_file_content3.bytestr())
duplicate_content := vfs.file_read('/duplicate.txt') or { duplicate_content := vfs.file_read('/duplicate.txt') or { panic('Failed to read file: ${err}') }
panic('Failed to read file: ${err}')
}
println(duplicate_content.bytestr()) println(duplicate_content.bytestr())
println("\n---------DELETING DUPLICATE FILE (DB SIZE: ${os.file_size(os.join_path(example_data_dir, 'data/0.db'))})") println('\n---------DELETING DUPLICATE FILE (DB SIZE: ${os.file_size(os.join_path(example_data_dir,
vfs.file_delete('/duplicate.txt') or { 'data/0.db'))})')
panic('Failed to delete file: ${err}') vfs.file_delete('/duplicate.txt') or { panic('Failed to delete file: ${err}') }
}
data_path := os.join_path(example_data_dir, 'data/0.db') data_path2 := os.join_path(example_data_dir, 'data/0.db')
db_file_path := os.join_path(data_path, '0.db') db_file_path := os.join_path(data_path2, '0.db')
println("---------READING FILES (DB SIZE: ${if os.exists(db_file_path) { os.file_size(db_file_path) } else { 0 }})") println('---------READING FILES (DB SIZE: ${if os.exists(db_file_path) {
some_file_content2 := vfs.file_read('/some_file.txt') or { os.file_size(db_file_path)
panic('Failed to read file: ${err}') } else {
} 0
}})')
some_file_content2 := vfs.file_read('/some_file.txt') or { panic('Failed to read file: ${err}') }
println(some_file_content2.bytestr()) println(some_file_content2.bytestr())
another_file_content2 := vfs.file_read('/another_file.txt') or { another_file_content2 := vfs.file_read('/another_file.txt') or {

View File

@@ -16,26 +16,24 @@ os.mkdir_all(metadata_dir)!
// Create separate databases for data and metadata // Create separate databases for data and metadata
mut db_data := ourdb.new( mut db_data := ourdb.new(
path: data_dir path: data_dir
incremental_mode: false incremental_mode: false
)! )!
mut db_metadata := ourdb.new( mut db_metadata := ourdb.new(
path: metadata_dir path: metadata_dir
incremental_mode: false incremental_mode: false
)! )!
// Create VFS with separate databases for data and metadata // Create VFS with separate databases for data and metadata
mut vfs := vfs_db.new_with_separate_dbs( mut vfs := vfs_db.new_with_separate_dbs(mut db_data, mut db_metadata,
mut db_data, data_dir: data_dir
mut db_metadata,
data_dir: data_dir,
metadata_dir: metadata_dir metadata_dir: metadata_dir
)! )!
// Create a root directory if it doesn't exist // Create a root directory if it doesn't exist
if !vfs.exists('/') { if !vfs.exists('/') {
vfs.dir_create('/')! vfs.dir_create('/')!
} }
// Create some files and directories // Create some files and directories
@@ -55,13 +53,13 @@ println('Nested file content: ${vfs.file_read('/test_dir/nested_file.txt')!.byte
println('Root directory contents:') println('Root directory contents:')
root_entries := vfs.dir_list('/')! root_entries := vfs.dir_list('/')!
for entry in root_entries { for entry in root_entries {
println('- ${entry.get_metadata().name} (${entry.get_metadata().file_type})') println('- ${entry.get_metadata().name} (${entry.get_metadata().file_type})')
} }
println('Test directory contents:') println('Test directory contents:')
test_dir_entries := vfs.dir_list('/test_dir')! test_dir_entries := vfs.dir_list('/test_dir')!
for entry in test_dir_entries { for entry in test_dir_entries {
println('- ${entry.get_metadata().name} (${entry.get_metadata().file_type})') println('- ${entry.get_metadata().name} (${entry.get_metadata().file_type})')
} }
// Create a duplicate file with the same content // Create a duplicate file with the same content

View File

@@ -16,59 +16,43 @@ os.mkdir_all(example_data_dir)!
// Create separate databases for data and metadata // Create separate databases for data and metadata
mut db_data := ourdb.new( mut db_data := ourdb.new(
path: os.join_path(example_data_dir, 'data') path: os.join_path(example_data_dir, 'data')
incremental_mode: false incremental_mode: false
)! )!
mut db_metadata := ourdb.new( mut db_metadata := ourdb.new(
path: os.join_path(example_data_dir, 'metadata') path: os.join_path(example_data_dir, 'metadata')
incremental_mode: false incremental_mode: false
)! )!
// Create VFS with separate databases for data and metadata // Create VFS with separate databases for data and metadata
mut vfs := vfs_db.new(mut db_data, mut db_metadata) or { mut vfs := vfs_db.new(mut db_data, mut db_metadata) or { panic('Failed to create VFS: ${err}') }
panic('Failed to create VFS: ${err}')
}
println('\n---------BEGIN DIRECTORY OPERATIONS EXAMPLE') println('\n---------BEGIN DIRECTORY OPERATIONS EXAMPLE')
// Create directories with subdirectories // Create directories with subdirectories
println('\n---------CREATING DIRECTORIES') println('\n---------CREATING DIRECTORIES')
vfs.dir_create('/dir1') or { vfs.dir_create('/dir1') or { panic('Failed to create directory: ${err}') }
panic('Failed to create directory: ${err}')
}
println('Created directory: /dir1') println('Created directory: /dir1')
vfs.dir_create('/dir1/subdir1') or { vfs.dir_create('/dir1/subdir1') or { panic('Failed to create directory: ${err}') }
panic('Failed to create directory: ${err}')
}
println('Created directory: /dir1/subdir1') println('Created directory: /dir1/subdir1')
vfs.dir_create('/dir1/subdir2') or { vfs.dir_create('/dir1/subdir2') or { panic('Failed to create directory: ${err}') }
panic('Failed to create directory: ${err}')
}
println('Created directory: /dir1/subdir2') println('Created directory: /dir1/subdir2')
vfs.dir_create('/dir2') or { vfs.dir_create('/dir2') or { panic('Failed to create directory: ${err}') }
panic('Failed to create directory: ${err}')
}
println('Created directory: /dir2') println('Created directory: /dir2')
vfs.dir_create('/dir2/subdir1') or { vfs.dir_create('/dir2/subdir1') or { panic('Failed to create directory: ${err}') }
panic('Failed to create directory: ${err}')
}
println('Created directory: /dir2/subdir1') println('Created directory: /dir2/subdir1')
vfs.dir_create('/dir2/subdir1/subsubdir1') or { vfs.dir_create('/dir2/subdir1/subsubdir1') or { panic('Failed to create directory: ${err}') }
panic('Failed to create directory: ${err}')
}
println('Created directory: /dir2/subdir1/subsubdir1') println('Created directory: /dir2/subdir1/subsubdir1')
// List directories // List directories
println('\n---------LISTING ROOT DIRECTORY') println('\n---------LISTING ROOT DIRECTORY')
root_entries := vfs.dir_list('/') or { root_entries := vfs.dir_list('/') or { panic('Failed to list directory: ${err}') }
panic('Failed to list directory: ${err}')
}
println('Root directory contains:') println('Root directory contains:')
for entry in root_entries { for entry in root_entries {
entry_type := if entry.get_metadata().file_type == .directory { 'directory' } else { 'file' } entry_type := if entry.get_metadata().file_type == .directory { 'directory' } else { 'file' }
@@ -76,9 +60,7 @@ for entry in root_entries {
} }
println('\n---------LISTING /dir1 DIRECTORY') println('\n---------LISTING /dir1 DIRECTORY')
dir1_entries := vfs.dir_list('/dir1') or { dir1_entries := vfs.dir_list('/dir1') or { panic('Failed to list directory: ${err}') }
panic('Failed to list directory: ${err}')
}
println('/dir1 directory contains:') println('/dir1 directory contains:')
for entry in dir1_entries { for entry in dir1_entries {
entry_type := if entry.get_metadata().file_type == .directory { 'directory' } else { 'file' } entry_type := if entry.get_metadata().file_type == .directory { 'directory' } else { 'file' }
@@ -87,9 +69,7 @@ for entry in dir1_entries {
// Write a file in a subdirectory // Write a file in a subdirectory
println('\n---------WRITING FILE IN SUBDIRECTORY') println('\n---------WRITING FILE IN SUBDIRECTORY')
vfs.file_create('/dir1/subdir1/test_file.txt') or { vfs.file_create('/dir1/subdir1/test_file.txt') or { panic('Failed to create file: ${err}') }
panic('Failed to create file: ${err}')
}
println('Created file: /dir1/subdir1/test_file.txt') println('Created file: /dir1/subdir1/test_file.txt')
test_content := 'This is a test file in a subdirectory' test_content := 'This is a test file in a subdirectory'
@@ -104,13 +84,15 @@ file_content := vfs.file_read('/dir1/subdir1/test_file.txt') or {
panic('Failed to read file: ${err}') panic('Failed to read file: ${err}')
} }
println('File content: ${file_content.bytestr()}') println('File content: ${file_content.bytestr()}')
println('Content verification: ${if file_content.bytestr() == test_content { 'SUCCESS' } else { 'FAILED' }}') println('Content verification: ${if file_content.bytestr() == test_content {
'SUCCESS'
} else {
'FAILED'
}}')
// List the subdirectory to see the file // List the subdirectory to see the file
println('\n---------LISTING /dir1/subdir1 DIRECTORY') println('\n---------LISTING /dir1/subdir1 DIRECTORY')
subdir1_entries := vfs.dir_list('/dir1/subdir1') or { subdir1_entries := vfs.dir_list('/dir1/subdir1') or { panic('Failed to list directory: ${err}') }
panic('Failed to list directory: ${err}')
}
println('/dir1/subdir1 directory contains:') println('/dir1/subdir1 directory contains:')
for entry in subdir1_entries { for entry in subdir1_entries {
entry_type := if entry.get_metadata().file_type == .directory { 'directory' } else { 'file' } entry_type := if entry.get_metadata().file_type == .directory { 'directory' } else { 'file' }
@@ -119,9 +101,7 @@ for entry in subdir1_entries {
// Delete the file // Delete the file
println('\n---------DELETING FILE') println('\n---------DELETING FILE')
vfs.file_delete('/dir1/subdir1/test_file.txt') or { vfs.file_delete('/dir1/subdir1/test_file.txt') or { panic('Failed to delete file: ${err}') }
panic('Failed to delete file: ${err}')
}
println('Deleted file: /dir1/subdir1/test_file.txt') println('Deleted file: /dir1/subdir1/test_file.txt')
// List the subdirectory again to verify the file is gone // List the subdirectory again to verify the file is gone
@@ -158,7 +138,11 @@ deep_file_content := vfs.file_read('/dir2/subdir1/subsubdir1/deep_file.txt') or
panic('Failed to read file: ${err}') panic('Failed to read file: ${err}')
} }
println('File content: ${deep_file_content.bytestr()}') println('File content: ${deep_file_content.bytestr()}')
println('Content verification: ${if deep_file_content.bytestr() == deep_content { 'SUCCESS' } else { 'FAILED' }}') println('Content verification: ${if deep_file_content.bytestr() == deep_content {
'SUCCESS'
} else {
'FAILED'
}}')
// Clean up by deleting directories (optional) // Clean up by deleting directories (optional)
println('\n---------CLEANING UP') println('\n---------CLEANING UP')

View File

@@ -6,14 +6,17 @@ import freeflowuniverse.herolib.data.ourdb
import os import os
import log import log
const database_path := os.join_path(os.dir(@FILE), 'database') const database_path = os.join_path(os.dir(@FILE), 'database')
mut metadata_db := ourdb.new(path:os.join_path(database_path, 'metadata'))! mut metadata_db := ourdb.new(path: os.join_path(database_path, 'metadata'))!
mut data_db := ourdb.new(path:os.join_path(database_path, 'data'))! mut data_db := ourdb.new(path: os.join_path(database_path, 'data'))!
mut vfs := vfs_db.new(mut metadata_db, mut data_db)! mut vfs := vfs_db.new(mut metadata_db, mut data_db)!
mut server := webdav.new_server(vfs: vfs, user_db: { mut server := webdav.new_server(
'admin': '123' vfs: vfs
})! user_db: {
'admin': '123'
}
)!
log.set_level(.debug) log.set_level(.debug)

View File

@@ -91,4 +91,3 @@ println('\nFootnotes:')
for id, footnote in nav.footnotes() { for id, footnote in nav.footnotes() {
println(' [^${id}]: ${footnote.content}') println(' [^${id}]: ${footnote.content}')
} }

View File

@@ -7,8 +7,8 @@ import os
import markdown import markdown
import freeflowuniverse.herolib.data.markdownparser2 import freeflowuniverse.herolib.data.markdownparser2
path2:="${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/links.md" path2 := '${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/links.md'
path1:="${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/test.md" path1 := '${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/test.md'
text := os.read_file(path1)! text := os.read_file(path1)!

View File

@@ -18,10 +18,9 @@ for project in 'projectinca, legal, why'.split(',').map(it.trim_space()) {
)! )!
} }
tree.export( tree.export(
destination: '/tmp/mdexport' destination: '/tmp/mdexport'
reset: true reset: true
//keep_structure: true // keep_structure: true
exclude_errors: false exclude_errors: false
)! )!

View File

@@ -10,8 +10,8 @@ mut docs := starlight.new(
// Create a new starlight site // Create a new starlight site
mut site := docs.get( mut site := docs.get(
url: 'https://git.ourworld.tf/tfgrid/docs_aibox' url: 'https://git.ourworld.tf/tfgrid/docs_aibox'
init:true //init means we put config files if not there init: true // init means we put config files if not there
)! )!
site.dev()! site.dev()!

View File

@@ -375,11 +375,29 @@ check_and_start_redis() {
fi fi
fi fi
elif [[ "${OSNAME}" == "darwin"* ]]; then elif [[ "${OSNAME}" == "darwin"* ]]; then
if brew services list | grep -q "^redis.*started"; then # Check if we're in GitHub Actions
echo "redis is already running." if is_github_actions; then
echo "Running in GitHub Actions on macOS. Starting redis directly..."
if pgrep redis-server > /dev/null; then
echo "redis is already running."
else
echo "redis is not running. Starting it in the background..."
redis-server --daemonize yes
if pgrep redis-server > /dev/null; then
echo "redis started successfully."
else
echo "Failed to start redis. Please check logs for details."
exit 1
fi
fi
else else
echo "redis is not running. Starting it..." # For regular macOS environments, use brew services
brew services start redis if brew services list | grep -q "^redis.*started"; then
echo "redis is already running."
else
echo "redis is not running. Starting it..."
brew services start redis
fi
fi fi
elif [[ "${OSNAME}" == "alpine"* ]]; then elif [[ "${OSNAME}" == "alpine"* ]]; then
if rc-service "redis" status | grep -q "running"; then if rc-service "redis" status | grep -q "running"; then

View File

@@ -1,10 +1,10 @@
module generator module generator
import freeflowuniverse.herolib.core.code {Folder, File} import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.jsonschema.codegen { schema_to_struct } import freeflowuniverse.herolib.schemas.jsonschema.codegen
import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen { content_descriptor_to_parameter } import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen
import freeflowuniverse.herolib.baobab.specification {ActorSpecification, ActorMethod, BaseObject} import freeflowuniverse.herolib.baobab.specification
import net.http import net.http
// pub enum BaseObjectMethodType { // pub enum BaseObjectMethodType {
@@ -58,7 +58,7 @@ fn get_endpoint_root(root string) string {
return if root == '' { return if root == '' {
'' ''
} else { } else {
"/${root.trim('/')}" '/${root.trim('/')}'
} }
} }

View File

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

View File

@@ -1,12 +1,12 @@
module generator module generator
import freeflowuniverse.herolib.baobab.specification {BaseObject} import freeflowuniverse.herolib.baobab.specification
import freeflowuniverse.herolib.core.code { type_from_symbol, VFile, CodeItem, Function, Import, Param, Param, Struct, StructField, Type } import freeflowuniverse.herolib.core.code { Param, Param, type_from_symbol }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
const id_param = Param{ const id_param = Param{
name: 'id' name: 'id'
typ: type_from_symbol('u32') typ: type_from_symbol('u32')
} }
// pub fn generate_object_code(actor Struct, object BaseObject) VFile { // pub fn generate_object_code(actor Struct, object BaseObject) VFile {

View File

@@ -1,7 +1,7 @@
module generator module generator
import freeflowuniverse.herolib.core.code { VFile, CustomCode, Function, Import, Struct } import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.baobab.specification {BaseObject} import freeflowuniverse.herolib.baobab.specification
import rand import rand
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools

View File

@@ -1,10 +1,10 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Result, Object, Param, Folder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode } import freeflowuniverse.herolib.core.code { CodeItem, CustomCode, Function, Import, Object, Param, Result, VFile }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.openrpc {Example, ContentDescriptor} import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, Example }
import freeflowuniverse.herolib.schemas.jsonschema.codegen {schemaref_to_type} import freeflowuniverse.herolib.schemas.jsonschema.codegen { schemaref_to_type }
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification} import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
fn generate_handle_file(spec ActorSpecification) !VFile { fn generate_handle_file(spec ActorSpecification) !VFile {
mut items := []CodeItem{} mut items := []CodeItem{}
@@ -12,14 +12,21 @@ fn generate_handle_file(spec ActorSpecification) !VFile {
for method in spec.methods { for method in spec.methods {
items << generate_method_handle(spec.name, method)! items << generate_method_handle(spec.name, method)!
} }
return VFile { return VFile{
name: 'act' name: 'act'
imports: [ imports: [
Import{mod:'freeflowuniverse.herolib.baobab.stage' types:['Action']} Import{
Import{mod:'freeflowuniverse.herolib.core.texttools'} mod: 'freeflowuniverse.herolib.baobab.stage'
Import{mod:'x.json2 as json'} types: ['Action']
},
Import{
mod: 'freeflowuniverse.herolib.core.texttools'
},
Import{
mod: 'x.json2 as json'
},
] ]
items: items items: items
} }
} }
@@ -74,13 +81,22 @@ pub fn generate_method_handle(actor_name string, method ActorMethod) !Function {
call_stmt := generate_call_stmt(actor_name, method)! call_stmt := generate_call_stmt(actor_name, method)!
body += '${call_stmt}\n' body += '${call_stmt}\n'
body += '${generate_return_stmt(method)!}\n' body += '${generate_return_stmt(method)!}\n'
return Function { return Function{
name: 'handle_${name_fixed}' name: 'handle_${name_fixed}'
description: '// Handler for ${name_fixed}\n' description: '// Handler for ${name_fixed}\n'
receiver: Param{name: 'actor', mutable: true, typ: Object{'${actor_name_pascal}Actor'}} receiver: Param{
params: [Param{name: 'action', typ: Object{'Action'}}] name: 'actor'
result: Param{typ: Result{Object{'Action'}}} mutable: true
body: body typ: Object{'${actor_name_pascal}Actor'}
}
params: [Param{
name: 'action'
typ: Object{'Action'}
}]
result: Param{
typ: Result{Object{'Action'}}
}
body: body
} }
} }
@@ -95,28 +111,41 @@ pub fn generate_example_method_handle(actor_name string, method ActorMethod) !Fu
if method.example.result is Example { if method.example.result is Example {
'return Action{...action, result: json.encode(\'${method.example.result.value}\')}' 'return Action{...action, result: json.encode(\'${method.example.result.value}\')}'
} else { } else {
"return action" 'return action'
} }
} else { "return action" } } else {
return Function { 'return action'
name: 'handle_${name_fixed}_example' }
return Function{
name: 'handle_${name_fixed}_example'
description: '// Handler for ${name_fixed}\n' description: '// Handler for ${name_fixed}\n'
receiver: Param{name: 'actor', mutable: true, typ: Object{'${actor_name_pascal}Actor'}} receiver: Param{
params: [Param{name: 'action', typ: Object{'Action'}}] name: 'actor'
result: Param{typ: Result{Object{'Action'}}} mutable: true
body: body 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(name string, 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() != '' { mut call_stmt := if schemaref_to_type(method.result.schema).vgen().trim_space() != '' {
'${texttools.snake_case(method.result.name)} := ' '${texttools.snake_case(method.result.name)} := '
} else {''} } else {
''
}
method_name := texttools.snake_case(method.name) method_name := texttools.snake_case(method.name)
snake_name := texttools.snake_case(name) snake_name := texttools.snake_case(name)
param_names := method.parameters.map(texttools.snake_case(it.name)) param_names := method.parameters.map(texttools.snake_case(it.name))
call_stmt += 'actor.${snake_name}.${method_name}(${param_names.join(", ")})!' call_stmt += 'actor.${snake_name}.${method_name}(${param_names.join(', ')})!'
return call_stmt return call_stmt
} }
@@ -124,7 +153,7 @@ fn generate_return_stmt(method ActorMethod) !string {
if schemaref_to_type(method.result.schema).vgen().trim_space() != '' { 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{...action, result: json.encode(${texttools.snake_case(method.result.name)})}'
} }
return "return action" return 'return action'
} }
// Helper function to generate a case block for the main router // Helper function to generate a case block for the main router
@@ -138,12 +167,13 @@ fn generate_decode_stmt(name string, param ContentDescriptor) !string {
param_type := schemaref_to_type(param.schema) param_type := schemaref_to_type(param.schema)
if param_type is Object { if param_type is Object {
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})!' return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})!'
} } else if param_type is code.Array {
else if param_type is code.Array {
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})' return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})'
} }
param_symbol := param_type.vgen() param_symbol := param_type.vgen()
return if param_symbol == 'string' { return if param_symbol == 'string' {
'${name}.str()' '${name}.str()'
} else {'${name}.${param_type.vgen()}()'} } else {
'${name}.${param_type.vgen()}()'
}
} }

View File

@@ -1,9 +1,9 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Folder, IFolder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode } import freeflowuniverse.herolib.core.code { File, Folder, IFile, IFolder }
import freeflowuniverse.herolib.schemas.openapi import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification, ActorInterface} import freeflowuniverse.herolib.baobab.specification { ActorInterface, ActorSpecification }
import json import json
@[params] @[params]
@@ -41,15 +41,15 @@ pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
} }
} }
specs_folder := Folder { specs_folder := Folder{
name: 'specs' name: 'specs'
files: spec_files files: spec_files
} }
// folder with docs // folder with docs
folders << Folder { folders << Folder{
name: 'docs' name: 'docs'
files: docs_files files: docs_files
folders: [specs_folder] folders: [specs_folder]
} }
@@ -59,9 +59,9 @@ pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
// create module with code files and docs folder // create module with code files and docs folder
name_fixed := texttools.snake_case(spec.name) name_fixed := texttools.snake_case(spec.name)
return code.Folder{ return Folder{
name: '${name_fixed}' name: '${name_fixed}'
files: files files: files
folders: folders folders: folders
modules: [generate_actor_module(spec, params)!] modules: [generate_actor_module(spec, params)!]
} }
@@ -69,14 +69,14 @@ pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
fn generate_readme_file(spec ActorSpecification) !File { fn generate_readme_file(spec ActorSpecification) !File {
return File{ return File{
name: 'README' name: 'README'
extension: 'md' extension: 'md'
content: '# ${spec.name}\n${spec.description}' content: '# ${spec.name}\n${spec.description}'
} }
} }
pub fn generate_examples_folder() !Folder { pub fn generate_examples_folder() !Folder {
return Folder { return Folder{
name: 'examples' name: 'examples'
} }
} }

View File

@@ -1,9 +1,9 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Folder, IFolder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode } import freeflowuniverse.herolib.core.code { CustomCode, IFile, IFolder, Module, VFile }
import freeflowuniverse.herolib.schemas.openapi import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification, ActorInterface} import freeflowuniverse.herolib.baobab.specification { ActorInterface, ActorSpecification }
import json import json
pub fn generate_module_from_openapi(openapi_path string) !string { pub fn generate_module_from_openapi(openapi_path string) !string {
@@ -28,11 +28,11 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
generate_actor_test_file(spec)!, generate_actor_test_file(spec)!,
generate_specs_file(spec.name, params.interfaces)!, generate_specs_file(spec.name, params.interfaces)!,
generate_handle_file(spec)!, generate_handle_file(spec)!,
generate_methods_file(spec)! generate_methods_file(spec)!,
generate_methods_interface_file(spec)! generate_methods_interface_file(spec)!,
generate_methods_example_file(spec)! generate_methods_example_file(spec)!,
generate_client_file(spec)! generate_client_file(spec)!,
generate_model_file(spec)! generate_model_file(spec)!,
] ]
// generate code files for supported interfaces // generate code files for supported interfaces
@@ -74,11 +74,11 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
// create module with code files and docs folder // create module with code files and docs folder
name_fixed := texttools.snake_case(spec.name) name_fixed := texttools.snake_case(spec.name)
return code.new_module( return code.new_module(
name: '${name_fixed}' name: '${name_fixed}'
description: spec.description description: spec.description
files: files files: files
folders: folders folders: folders
in_src: true in_src: true
) )
} }
@@ -88,8 +88,8 @@ fn generate_actor_file(spec ActorSpecification) !VFile {
name_snake := texttools.snake_case(spec.name) name_snake := texttools.snake_case(spec.name)
name_pascal := texttools.pascal_case(spec.name) name_pascal := texttools.pascal_case(spec.name)
actor_code := $tmpl('./templates/actor.v.template') actor_code := $tmpl('./templates/actor.v.template')
return VFile { return VFile{
name: 'actor' name: 'actor'
items: [CustomCode{actor_code}] items: [CustomCode{actor_code}]
} }
} }
@@ -99,8 +99,8 @@ fn generate_actor_test_file(spec ActorSpecification) !VFile {
actor_name_snake := texttools.snake_case(spec.name) actor_name_snake := texttools.snake_case(spec.name)
actor_name_pascal := texttools.pascal_case(spec.name) actor_name_pascal := texttools.pascal_case(spec.name)
actor_test_code := $tmpl('./templates/actor_test.v.template') actor_test_code := $tmpl('./templates/actor_test.v.template')
return VFile { return VFile{
name: 'actor_test' name: 'actor_test'
items: [CustomCode{actor_test_code}] items: [CustomCode{actor_test_code}]
} }
} }
@@ -112,8 +112,8 @@ fn generate_specs_file(name string, interfaces []ActorInterface) !VFile {
actor_name_snake := texttools.snake_case(name) actor_name_snake := texttools.snake_case(name)
actor_name_pascal := texttools.pascal_case(name) actor_name_pascal := texttools.pascal_case(name)
actor_code := $tmpl('./templates/specifications.v.template') actor_code := $tmpl('./templates/specifications.v.template')
return VFile { return VFile{
name: 'specifications' name: 'specifications'
items: [CustomCode{actor_code}] items: [CustomCode{actor_code}]
} }
} }

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Folder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode } import freeflowuniverse.herolib.core.code { CodeItem, CustomCode, Import, VFile }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification} import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
pub fn generate_command_file(spec ActorSpecification) !VFile { pub fn generate_command_file(spec ActorSpecification) !VFile {
mut items := []CodeItem{} mut items := []CodeItem{}

View File

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

View File

@@ -1,12 +1,12 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result } import freeflowuniverse.herolib.core.code { Array, CodeItem, Function, Import, Param, Result, Struct, VFile }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.openrpc {ContentDescriptor} import freeflowuniverse.herolib.schemas.openrpc
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter, content_descriptor_to_struct} import freeflowuniverse.herolib.schemas.openrpc.codegen { content_descriptor_to_parameter, content_descriptor_to_struct }
import freeflowuniverse.herolib.schemas.jsonschema {Schema} import freeflowuniverse.herolib.schemas.jsonschema { Schema }
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schema_to_struct} import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification} import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
const crud_prefixes = ['new', 'get', 'set', 'delete', 'list'] const crud_prefixes = ['new', 'get', 'set', 'delete', 'list']
@@ -15,48 +15,66 @@ pub fn generate_methods_file(spec ActorSpecification) !VFile {
actor_name_pascal := texttools.pascal_case(spec.name) actor_name_pascal := texttools.pascal_case(spec.name)
receiver := generate_methods_receiver(spec.name) receiver := generate_methods_receiver(spec.name)
receiver_param := Param { receiver_param := Param{
mutable: true mutable: true
name: name_snake[0].ascii_str() // receiver is first letter of domain name: name_snake[0].ascii_str() // receiver is first letter of domain
typ: code.Result{code.Object{receiver.name}} typ: Result{code.Object{receiver.name}}
} }
mut items := [CodeItem(receiver), CodeItem(generate_core_factory(receiver_param))] mut items := [CodeItem(receiver), CodeItem(generate_core_factory(receiver_param))]
for method in spec.methods { for method in spec.methods {
items << generate_method_code(receiver_param, ActorMethod{...method, category: spec.method_type(method)})! items << generate_method_code(receiver_param, ActorMethod{
...method
category: spec.method_type(method)
})!
} }
return VFile { return VFile{
name: 'methods' name: 'methods'
imports: [Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']}] imports: [
items: items Import{
mod: 'freeflowuniverse.herolib.baobab.osis'
types: ['OSIS']
},
]
items: items
} }
} }
fn generate_methods_receiver(name string) code.Struct { fn generate_methods_receiver(name string) Struct {
return code.Struct { return Struct{
is_pub: true is_pub: true
name: '${texttools.pascal_case(name)}' name: '${texttools.pascal_case(name)}'
fields: [code.StructField{is_mut: true, name: 'osis', typ:code.Object{'OSIS'}}] fields: [
code.StructField{
is_mut: true
name: 'osis'
typ: code.Object{'OSIS'}
},
]
} }
} }
fn generate_core_factory(receiver code.Param) code.Function { fn generate_core_factory(receiver Param) Function {
return code.Function { return Function{
is_pub: true is_pub: true
name: 'new_${receiver.typ.symbol()}' name: 'new_${receiver.typ.symbol()}'
body: "return ${receiver.typ.symbol().trim_left('!?')}{osis: osis.new()!}" body: 'return ${receiver.typ.symbol().trim_left('!?')}{osis: osis.new()!}'
result: receiver result: receiver
} }
} }
// returns bodyless method prototype // returns bodyless method prototype
pub fn generate_method_code(receiver code.Param, method ActorMethod) ![]CodeItem { pub fn generate_method_code(receiver Param, method ActorMethod) ![]CodeItem {
result_param := content_descriptor_to_parameter(method.result)! result_param := content_descriptor_to_parameter(method.result)!
mut method_code := []CodeItem{} mut method_code := []CodeItem{}
// TODO: document assumption // TODO: document assumption
obj_params := method.parameters.filter(if it.schema is Schema {it.schema.typ == 'object'} else {false}).map(content_descriptor_to_struct(it)) obj_params := method.parameters.filter(if it.schema is Schema {
it.schema.typ == 'object'
} else {
false
}).map(content_descriptor_to_struct(it))
if obj_param := obj_params[0] { if obj_param := obj_params[0] {
method_code << obj_param method_code << obj_param
} }
@@ -69,7 +87,7 @@ pub fn generate_method_code(receiver code.Param, method ActorMethod) ![]CodeItem
.base_object_set { base_object_set_body(receiver, method)! } .base_object_set { base_object_set_body(receiver, method)! }
.base_object_delete { base_object_delete_body(receiver, method)! } .base_object_delete { base_object_delete_body(receiver, method)! }
.base_object_list { base_object_list_body(receiver, method)! } .base_object_list { base_object_list_body(receiver, method)! }
else {"panic('implement')"} else { "panic('implement')" }
} }
fn_prototype := generate_method_prototype(receiver, method)! fn_prototype := generate_method_prototype(receiver, method)!
@@ -81,15 +99,18 @@ pub fn generate_method_code(receiver code.Param, method ActorMethod) ![]CodeItem
} }
// returns bodyless method prototype // returns bodyless method prototype
pub fn generate_method_prototype(receiver code.Param, method ActorMethod) !Function { pub fn generate_method_prototype(receiver Param, method ActorMethod) !Function {
result_param := content_descriptor_to_parameter(method.result)! result_param := content_descriptor_to_parameter(method.result)!
return Function{ return Function{
name: texttools.snake_case(method.name) name: texttools.snake_case(method.name)
receiver: receiver receiver: receiver
result: Param {...result_param, typ: Result{result_param.typ}} result: Param{
summary: method.summary ...result_param
typ: Result{result_param.typ}
}
summary: method.summary
description: method.description description: method.description
params: method.parameters.map(content_descriptor_to_parameter(it)!) params: method.parameters.map(content_descriptor_to_parameter(it)!)
} }
} }

View File

@@ -1,58 +1,67 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result } import freeflowuniverse.herolib.core.code { CodeItem, Function, Import, Param, Result, Struct, VFile }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.openrpc {Example} import freeflowuniverse.herolib.schemas.openrpc { Example }
import freeflowuniverse.herolib.schemas.jsonschema {Schema} import freeflowuniverse.herolib.schemas.jsonschema
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schema_to_struct} import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter} import freeflowuniverse.herolib.schemas.openrpc.codegen { content_descriptor_to_parameter }
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification} import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
pub fn generate_methods_example_file(spec ActorSpecification) !VFile { pub fn generate_methods_example_file(spec ActorSpecification) !VFile {
name_snake := texttools.snake_case(spec.name) name_snake := texttools.snake_case(spec.name)
name_pascal := texttools.pascal_case(spec.name) name_pascal := texttools.pascal_case(spec.name)
receiver := generate_example_methods_receiver(spec.name) receiver := generate_example_methods_receiver(spec.name)
receiver_param := Param { receiver_param := Param{
mutable: true mutable: true
name: name_snake[0].ascii_str() name: name_snake[0].ascii_str()
typ: code.Result{code.Object{receiver.name}} typ: Result{code.Object{receiver.name}}
} }
mut items := [CodeItem(receiver), CodeItem(generate_core_example_factory(receiver_param))] mut items := [CodeItem(receiver), CodeItem(generate_core_example_factory(receiver_param))]
for method in spec.methods { for method in spec.methods {
items << generate_method_example_code(receiver_param, ActorMethod{...method, category: spec.method_type(method)})! items << generate_method_example_code(receiver_param, ActorMethod{
...method
category: spec.method_type(method)
})!
} }
return VFile { return VFile{
name: 'methods_example' name: 'methods_example'
imports: [ imports: [
Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']}, Import{
Import{mod: 'x.json2 as json'} mod: 'freeflowuniverse.herolib.baobab.osis'
types: ['OSIS']
},
Import{
mod: 'x.json2 as json'
},
] ]
items: items items: items
} }
} }
fn generate_core_example_factory(receiver code.Param) code.Function { fn generate_core_example_factory(receiver Param) Function {
return code.Function { return Function{
is_pub: true is_pub: true
name: 'new_${texttools.snake_case(receiver.typ.symbol())}' name: 'new_${texttools.snake_case(receiver.typ.symbol())}'
body: "return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}" body: 'return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}'
result: receiver result: receiver
} }
} }
fn generate_example_methods_receiver(name string) code.Struct { fn generate_example_methods_receiver(name string) Struct {
return code.Struct { return Struct{
is_pub: true is_pub: true
name: '${texttools.pascal_case(name)}Example' name: '${texttools.pascal_case(name)}Example'
embeds: [code.Struct{name:'OSIS'}] embeds: [Struct{
name: 'OSIS'
}]
} }
} }
// returns bodyless method prototype // returns bodyless method prototype
pub fn generate_method_example_code(receiver code.Param, method ActorMethod) ![]CodeItem { pub fn generate_method_example_code(receiver Param, method ActorMethod) ![]CodeItem {
result_param := content_descriptor_to_parameter(method.result)! result_param := content_descriptor_to_parameter(method.result)!
mut method_code := []CodeItem{} mut method_code := []CodeItem{}
@@ -63,16 +72,17 @@ pub fn generate_method_example_code(receiver code.Param, method ActorMethod) ![]
// } // }
// check if method is a Base Object CRUD Method and // check if method is a Base Object CRUD Method and
// if so generate the method's body // if so generate the method's body
body := if !method_is_void(method)! { body := if !method_is_void(method)! {
if method.example.result is Example { if method.example.result is Example {
"json_str := '${method.example.result.value}' "json_str := '${method.example.result.value}'
return ${generate_decode_stmt('json_str', method.result)!}" return ${generate_decode_stmt('json_str',
method.result)!}"
} else { } else {
"return ${result_param.typ.empty_value()}" 'return ${result_param.typ.empty_value()}'
} }
} else { } else {
"" ''
} }
fn_prototype := generate_method_prototype(receiver, method)! fn_prototype := generate_method_prototype(receiver, method)!

View File

@@ -1,15 +1,20 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result } import freeflowuniverse.herolib.core.code { CodeItem, Import, Param, VFile }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter} import freeflowuniverse.herolib.schemas.openrpc.codegen
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification} import freeflowuniverse.herolib.baobab.specification { ActorSpecification }
pub fn generate_methods_interface_file(spec ActorSpecification) !VFile { pub fn generate_methods_interface_file(spec ActorSpecification) !VFile {
return VFile { return VFile{
name: 'methods_interface' name: 'methods_interface'
imports: [Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']}] imports: [
items: [code.CodeItem(generate_methods_interface_declaration(spec)!)] Import{
mod: 'freeflowuniverse.herolib.baobab.osis'
types: ['OSIS']
},
]
items: [CodeItem(generate_methods_interface_declaration(spec)!)]
} }
} }
@@ -18,14 +23,14 @@ pub fn generate_methods_interface_declaration(spec ActorSpecification) !code.Int
name_snake := texttools.snake_case(spec.name) name_snake := texttools.snake_case(spec.name)
name_pascal := texttools.pascal_case(spec.name) name_pascal := texttools.pascal_case(spec.name)
receiver := generate_methods_receiver(spec.name) receiver := generate_methods_receiver(spec.name)
receiver_param := Param { receiver_param := Param{
mutable: true mutable: true
name: name_snake[0].ascii_str() name: name_snake[0].ascii_str()
typ: code.Object{receiver.name} typ: code.Object{receiver.name}
} }
return code.Interface { return code.Interface{
is_pub: true is_pub: true
name: 'I${name_pascal}' name: 'I${name_pascal}'
methods: spec.methods.map(generate_method_prototype(receiver_param, it)!) methods: spec.methods.map(generate_method_prototype(receiver_param, it)!)
} }
} }

View File

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

View File

@@ -1,24 +1,24 @@
module generator module generator
import json import json
import freeflowuniverse.herolib.core.code { VFile, File, Folder, Function, Module, Struct } import freeflowuniverse.herolib.core.code { File, Folder }
import freeflowuniverse.herolib.schemas.openapi { Components, OpenAPI, Operation } import freeflowuniverse.herolib.schemas.openapi { OpenAPI, Operation }
import freeflowuniverse.herolib.schemas.openapi.codegen import freeflowuniverse.herolib.schemas.openapi.codegen
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schema_to_type} import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen { schema_to_type }
import net.http import net.http
pub fn generate_openapi_file(specification OpenAPI) !File { pub fn generate_openapi_file(specification OpenAPI) !File {
openapi_json := specification.encode_json() openapi_json := specification.encode_json()
return File{ return File{
name: 'openapi' name: 'openapi'
extension: 'json' extension: 'json'
content: openapi_json content: openapi_json
} }
} }
pub fn generate_openapi_ts_client(specification OpenAPI) !Folder { pub fn generate_openapi_ts_client(specification OpenAPI) !Folder {
return codegen.ts_client_folder(specification, return codegen.ts_client_folder(specification,
body_generator: body_generator body_generator: body_generator
custom_client_code: ' private restClient: HeroRestClient; custom_client_code: ' private restClient: HeroRestClient;
constructor(heroKeysClient: any, debug: boolean = true) { constructor(heroKeysClient: any, debug: boolean = true) {
@@ -28,22 +28,29 @@ pub fn generate_openapi_ts_client(specification OpenAPI) !Folder {
)! )!
} }
fn body_generator(op openapi.Operation, path_ string, method http.Method) string { fn body_generator(op Operation, path_ string, method http.Method) string {
path := path_.replace('{','\${') path := path_.replace('{', '\${')
return match method { return match method {
.post { .post {
if schema := op.payload_schema() { if schema := op.payload_schema() {
symbol := schema_to_type(schema).typescript() symbol := schema_to_type(schema).typescript()
"return this.restClient.post<${symbol}>('${path}', data);" "return this.restClient.post<${symbol}>('${path}', data);"
} else {''} } else {
''
}
} }
.get { .get {
if schema := op.response_schema() { if schema := op.response_schema() {
// if op.params.len // if op.params.len
symbol := schema_to_type(schema).typescript() symbol := schema_to_type(schema).typescript()
"return this.restClient.get<${symbol}>('${path}', data);" "return this.restClient.get<${symbol}>('${path}', data);"
} else {''} } else {
} else {''} ''
}
}
else {
''
}
} }
// return if operation_is_base_object_method(op) { // return if operation_is_base_object_method(op) {
// bo_method := operation_to_base_object_method(op) // bo_method := operation_to_base_object_method(op)
@@ -58,7 +65,6 @@ fn body_generator(op openapi.Operation, path_ string, method http.Method) string
// } else {''} // } else {''}
} }
// pub fn operation_is_base_object_method(op openapi.Operation, base_objs []string) BaseObjectMethod { // pub fn operation_is_base_object_method(op openapi.Operation, base_objs []string) BaseObjectMethod {
// // name := texttools.pascal_case(op.operation_id) // // name := texttools.pascal_case(op.operation_id)
@@ -82,28 +88,24 @@ fn body_generator(op openapi.Operation, path_ string, method http.Method) string
// } // }
// } // }
// return if operation_is_base_object_method(op) {
// bo_method := operation_to_base_object_method(op)
// match bo_method. {
// return if operation_is_base_object_method(op) { // .new { ts_client_new_body(op, path) }
// bo_method := operation_to_base_object_method(op) // .get { ts_client_get_body(op, path) }
// match bo_method. { // .set { ts_client_set_body(op, path) }
// .new { ts_client_new_body(op, path) } // .delete { ts_client_delete_body(op, path) }
// .get { ts_client_get_body(op, path) } // .list { ts_client_list_body(op, path) }
// .set { ts_client_set_body(op, path) } // else {''}
// .delete { ts_client_delete_body(op, path) } // }
// .list { ts_client_list_body(op, path) } // } else {''}
// else {''}
// }
// } else {''}
// } // }
fn get_endpoint(path string) string { fn get_endpoint(path string) string {
return if path == '' { return if path == '' {
'' ''
} else { } else {
"/${path.trim('/')}" '/${path.trim('/')}'
} }
} }

View File

@@ -1,8 +1,8 @@
module generator module generator
import json import json
import freeflowuniverse.herolib.core.code { VFile, File, Function, Module, Struct } import freeflowuniverse.herolib.core.code { File, Function, Struct, VFile }
import freeflowuniverse.herolib.schemas.openrpc { Components, OpenRPC } import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
import freeflowuniverse.herolib.schemas.openrpc.codegen { generate_client_file, generate_client_test_file } import freeflowuniverse.herolib.schemas.openrpc.codegen { generate_client_file, generate_client_test_file }
pub fn generate_openrpc_file(spec OpenRPC) !File { pub fn generate_openrpc_file(spec OpenRPC) !File {
@@ -19,8 +19,8 @@ pub fn generate_openrpc_client_file(spec OpenRPC) !VFile {
// objects_map[object.structure.name] = object.structure // objects_map[object.structure.name] = object.structure
// } // }
client_file := generate_client_file(spec, objects_map)! client_file := generate_client_file(spec, objects_map)!
return VFile { return VFile{
...client_file, ...client_file
name: 'client_openrpc' name: 'client_openrpc'
} }
} }
@@ -35,8 +35,8 @@ pub fn generate_openrpc_client_test_file(spec OpenRPC) !VFile {
// methods_map[method.func.name] = method.func // methods_map[method.func.name] = method.func
// } // }
file := generate_client_test_file(spec, methods_map, objects_map)! file := generate_client_test_file(spec, methods_map, objects_map)!
return VFile { return VFile{
...file, ...file
name: 'client_openrpc_test' name: 'client_openrpc_test'
} }
} }

View File

@@ -1,13 +1,13 @@
module generator module generator
import freeflowuniverse.herolib.core.code { Folder, File } import freeflowuniverse.herolib.core.code { File, Folder }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
// generates the folder with runnable scripts of the actor // generates the folder with runnable scripts of the actor
pub fn generate_scripts_folder(name string, example bool) Folder { pub fn generate_scripts_folder(name string, example bool) Folder {
actor_name := '${texttools.snake_case(name)}_actor' actor_name := '${texttools.snake_case(name)}_actor'
return Folder { return Folder{
name: 'scripts' name: 'scripts'
files: [ files: [
generate_run_script(actor_name), generate_run_script(actor_name),
generate_docs_script(actor_name), generate_docs_script(actor_name),
@@ -22,54 +22,54 @@ pub fn generate_scripts_folder(name string, example bool) Folder {
// Function to generate a script for running an actor // Function to generate a script for running an actor
fn generate_run_script(actor_name string) File { fn generate_run_script(actor_name string) File {
actor_title := texttools.title_case(actor_name) actor_title := texttools.title_case(actor_name)
dollar := '$' dollar := '$'
return File{ return File{
name: 'run' name: 'run'
extension:'sh' extension: 'sh'
content: $tmpl('./templates/run.sh.template') content: $tmpl('./templates/run.sh.template')
} }
} }
// Function to generate a script for running an actor // Function to generate a script for running an actor
fn generate_docs_script(actor_name string) File { fn generate_docs_script(actor_name string) File {
dollar := '$' dollar := '$'
return File{ return File{
name: 'docs' name: 'docs'
extension:'vsh' extension: 'vsh'
content: $tmpl('./templates/docs.vsh.template') content: $tmpl('./templates/docs.vsh.template')
} }
} }
// Function to generate a script for running an actor // Function to generate a script for running an actor
fn generate_run_actor_script(name string) File { fn generate_run_actor_script(name string) File {
name_snake := texttools.snake_case(name) name_snake := texttools.snake_case(name)
name_pascal := texttools.pascal_case(name) name_pascal := texttools.pascal_case(name)
return File{ return File{
name: 'run_actor' name: 'run_actor'
extension:'vsh' extension: 'vsh'
content: $tmpl('./templates/run_actor.vsh.template') content: $tmpl('./templates/run_actor.vsh.template')
} }
} }
// Function to generate a script for running an example actor // Function to generate a script for running an example actor
fn generate_run_actor_example_script(name string) File { fn generate_run_actor_example_script(name string) File {
name_snake := texttools.snake_case(name) name_snake := texttools.snake_case(name)
name_pascal := texttools.pascal_case(name) name_pascal := texttools.pascal_case(name)
return File{ return File{
name: 'run_actor_example' name: 'run_actor_example'
extension:'vsh' extension: 'vsh'
content: $tmpl('./templates/run_actor_example.vsh.template') content: $tmpl('./templates/run_actor_example.vsh.template')
} }
} }
// Function to generate a script for running an HTTP server // Function to generate a script for running an HTTP server
fn generate_run_http_server_script(name string) File { fn generate_run_http_server_script(name string) File {
port := 8080 port := 8080
name_snake := texttools.snake_case(name) name_snake := texttools.snake_case(name)
return File{ return File{
name: 'run_http_server' name: 'run_http_server'
extension:'vsh' extension: 'vsh'
content: $tmpl('./templates/run_http_server.vsh.template') content: $tmpl('./templates/run_http_server.vsh.template')
} }
} }

View File

@@ -9,7 +9,7 @@ fn mock_response() ! {
mut redis := redisclient.new('localhost:6379')! mut redis := redisclient.new('localhost:6379')!
mut rpc_q := redis.rpc_get('actor_pet_store') mut rpc_q := redis.rpc_get('actor_pet_store')
for { for {
rpc_q.process(fn(method string, data string)!string{ rpc_q.process(fn (method string, data string) !string {
return json.encode(method) return json.encode(method)
})! })!
time.sleep(time.millisecond * 100) // Prevent CPU spinning time.sleep(time.millisecond * 100) // Prevent CPU spinning

View File

@@ -17,11 +17,11 @@ import os
pub fn test_generate_get_method() { pub fn test_generate_get_method() {
generator := ActorGenerator{'test'} generator := ActorGenerator{'test'}
actor_struct := code.Struct{ actor_struct := code.Struct{
name: 'TestActor' name: 'TestActor'
fields: [ fields: [
code.StructField{ code.StructField{
name: 'test_struct_map' name: 'test_struct_map'
typ: code.Type{ typ: code.Type{
symbol: 'map[string]&TestStruct' symbol: 'map[string]&TestStruct'
} }
}, },

View File

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

View File

@@ -12,7 +12,7 @@ pub struct Indexer {
@[params] @[params]
pub struct IndexerConfig { pub struct IndexerConfig {
db_path string db_path string
reset bool reset bool
} }
pub fn new_indexer(config IndexerConfig) !Indexer { pub fn new_indexer(config IndexerConfig) !Indexer {
@@ -81,7 +81,7 @@ fn (mut backend Indexer) create_root_object_table(object RootObject) ! {
} }
// deletes an indexer table belonging to a root object // deletes an indexer table belonging to a root object
fn (mut backend Indexer) delete_table(object RootObject)! { fn (mut backend Indexer) delete_table(object RootObject) ! {
panic('implement') panic('implement')
} }
@@ -105,7 +105,7 @@ fn get_table[T]() string {
// returns the lists of the indices of a root objects db table, and corresponding values // 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 { pub fn get_indices[T](object T) map[string]string {
mut indices := map[string]string mut indices := map[string]string{}
$for field in T.fields { $for field in T.fields {
if field.attrs.contains('index') { if field.attrs.contains('index') {
value := object.$(field.name) value := object.$(field.name)

View File

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

View File

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

View File

@@ -1,19 +1,19 @@
module specification module specification
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.code { Struct, Function } import freeflowuniverse.herolib.core.code { Struct }
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef } import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
import freeflowuniverse.herolib.schemas.openapi { Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec, MediaType } import freeflowuniverse.herolib.schemas.openapi { MediaType, OpenAPI, Parameter }
import freeflowuniverse.herolib.schemas.openrpc { ExamplePairing, Example, ExampleRef, ContentDescriptor, ErrorSpec } import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec, Example, ExamplePairing, ExampleRef }
// Helper function: Convert OpenAPI parameter to ContentDescriptor // Helper function: Convert OpenAPI parameter to ContentDescriptor
fn openapi_param_to_content_descriptor(param Parameter) ContentDescriptor { fn openapi_param_to_content_descriptor(param Parameter) ContentDescriptor {
return ContentDescriptor{ return ContentDescriptor{
name: param.name, name: param.name
summary: param.description, summary: param.description
description: param.description, description: param.description
required: param.required, required: param.required
schema: param.schema schema: param.schema
} }
} }
@@ -22,9 +22,9 @@ fn openapi_param_to_example(param Parameter) ?Example {
if param.schema is Schema { if param.schema is Schema {
if param.schema.example.str() != '' { if param.schema.example.str() != '' {
return Example{ return Example{
name: 'Example ${param.name}', name: 'Example ${param.name}'
description: 'Example ${param.description}', description: 'Example ${param.description}'
value: param.schema.example value: param.schema.example
} }
} }
} }
@@ -32,21 +32,25 @@ fn openapi_param_to_example(param Parameter) ?Example {
} }
// Helper function: Convert OpenAPI operation to ActorMethod // Helper function: Convert OpenAPI operation to ActorMethod
fn openapi_operation_to_actor_method(info openapi.OperationInfo) ActorMethod { fn openapi_operation_to_actor_method(info OperationInfo) ActorMethod {
mut parameters := []ContentDescriptor{} mut parameters := []ContentDescriptor{}
mut example_parameters:= []Example{} mut example_parameters := []Example{}
for param in info.operation.parameters { for param in info.operation.parameters {
parameters << openapi_param_to_content_descriptor(param) parameters << openapi_param_to_content_descriptor(param)
example_parameters << openapi_param_to_example(param) or { example_parameters << openapi_param_to_example(param) or { continue }
continue
}
} }
if schema_ := info.operation.payload_schema() { if schema_ := info.operation.payload_schema() {
// TODO: document assumption // TODO: document assumption
schema := Schema{...schema_, title: texttools.pascal_case(info.operation.operation_id)} schema := Schema{
parameters << ContentDescriptor {name: 'data', schema: SchemaRef(schema)} ...schema_
title: texttools.pascal_case(info.operation.operation_id)
}
parameters << ContentDescriptor{
name: 'data'
schema: SchemaRef(schema)
}
} }
mut success_responses := map[string]MediaType{} mut success_responses := map[string]MediaType{}
@@ -57,54 +61,60 @@ fn openapi_operation_to_actor_method(info openapi.OperationInfo) ActorMethod {
} }
} }
if success_responses.len > 1 || success_responses.len == 0 { if success_responses.len > 1 || success_responses.len == 0 {
panic('Actor specification must specify one successful response.') panic('Actor specification must specify one successful response.')
} }
response_success := success_responses.values()[0] response_success := success_responses.values()[0]
mut result := ContentDescriptor{ mut result := ContentDescriptor{
name: "result", name: 'result'
description: "The response of the operation.", description: 'The response of the operation.'
required: true, required: true
schema: response_success.schema schema: response_success.schema
} }
example_result := if response_success.example.str() != '' { example_result := if response_success.example.str() != '' {
Example{ Example{
name: 'Example response', name: 'Example response'
value: response_success.example value: response_success.example
} }
} else {Example{}} } else {
Example{}
}
pairing := if example_result != Example{} || example_parameters.len > 0 { pairing := if example_result != Example{} || example_parameters.len > 0 {
ExamplePairing{ ExamplePairing{
params: example_parameters.map(ExampleRef(it)) params: example_parameters.map(ExampleRef(it))
result: ExampleRef(example_result) result: ExampleRef(example_result)
} }
} else {ExamplePairing{}} } else {
ExamplePairing{}
}
mut errors := []ErrorSpec{} mut errors := []ErrorSpec{}
for status, response in info.operation.responses { for status, response in info.operation.responses {
if status.int() >= 400 { if status.int() >= 400 {
error_schema := if response.content.len > 0 { error_schema := if response.content.len > 0 {
response.content.values()[0].schema response.content.values()[0].schema
} else {Schema{}} } else {
Schema{}
}
errors << ErrorSpec{ errors << ErrorSpec{
code: status.int(), code: status.int()
message: response.description, message: response.description
data: error_schema, // Extend if error schema is defined data: error_schema // Extend if error schema is defined
} }
} }
} }
return ActorMethod{ return ActorMethod{
name: info.operation.operation_id, name: info.operation.operation_id
description: info.operation.description, description: info.operation.description
summary: info.operation.summary, summary: info.operation.summary
parameters: parameters, parameters: parameters
example: pairing example: pairing
result: result, result: result
errors: errors, errors: errors
} }
} }
@@ -112,7 +122,7 @@ fn openapi_operation_to_actor_method(info openapi.OperationInfo) ActorMethod {
fn openapi_schema_to_struct(name string, schema SchemaRef) Struct { fn openapi_schema_to_struct(name string, schema SchemaRef) Struct {
// Assuming schema properties can be mapped to Struct fields // Assuming schema properties can be mapped to Struct fields
return Struct{ return Struct{
name: name, name: name
} }
} }
@@ -153,16 +163,18 @@ pub fn from_openapi(spec_raw OpenAPI) !ActorSpecification {
// Extract objects from OpenAPI components.schemas // Extract objects from OpenAPI components.schemas
for name, schema in spec.components.schemas { for name, schema in spec.components.schemas {
objects << BaseObject{schema: schema as Schema} objects << BaseObject{
schema: schema as Schema
}
} }
return ActorSpecification{ return ActorSpecification{
openapi: spec_raw openapi: spec_raw
name: spec.info.title, name: spec.info.title
description: spec.info.description, description: spec.info.description
structure: Struct{}, // Assuming no top-level structure for this use case structure: Struct{} // Assuming no top-level structure for this use case
interfaces: [.openapi], // Default to OpenAPI for input interfaces: [.openapi] // Default to OpenAPI for input
methods: spec.get_operations().map(openapi_operation_to_actor_method(it)), methods: spec.get_operations().map(openapi_operation_to_actor_method(it))
objects: objects, objects: objects
} }
} }

View File

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

View File

@@ -1,7 +1,7 @@
module specification module specification
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC, Method, ContentDescriptor, ErrorSpec } import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec, Method, OpenRPC }
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef } import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema }
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
// Helper function: Convert OpenRPC Method to ActorMethod // Helper function: Convert OpenRPC Method to ActorMethod
@@ -32,12 +32,12 @@ fn openrpc_method_to_actor_method(method Method) ActorMethod {
} }
return ActorMethod{ return ActorMethod{
name: method.name name: method.name
description: method.description description: method.description
summary: method.summary summary: method.summary
parameters: parameters parameters: parameters
result: method.result as ContentDescriptor result: method.result as ContentDescriptor
errors: errors errors: errors
} }
} }
@@ -86,9 +86,10 @@ pub fn from_openrpc(spec OpenRPC) !ActorSpecification {
if schema is Schema { if schema is Schema {
if schema.typ == 'object' { if schema.typ == 'object' {
objects << BaseObject{ objects << BaseObject{
schema: Schema {...schema, schema: Schema{
...schema
title: texttools.pascal_case(key) title: texttools.pascal_case(key)
id: texttools.snake_case(key) id: texttools.snake_case(key)
} }
} }
} }
@@ -96,10 +97,10 @@ pub fn from_openrpc(spec OpenRPC) !ActorSpecification {
} }
return ActorSpecification{ return ActorSpecification{
name: spec.info.title name: spec.info.title
description: spec.info.description description: spec.info.description
interfaces: [.openrpc] interfaces: [.openrpc]
methods: methods methods: methods
objects: objects objects: objects
} }
} }

View File

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

View File

@@ -1,21 +1,21 @@
module specification module specification
import freeflowuniverse.herolib.core.code { Struct, Function } import freeflowuniverse.herolib.core.code { Struct }
import freeflowuniverse.herolib.schemas.openapi import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.schemas.openrpc {ExamplePairing, ContentDescriptor, ErrorSpec} import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec, ExamplePairing }
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference} import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema }
pub struct ActorSpecification { pub struct ActorSpecification {
pub mut: pub mut:
version string = '1.0.0' version string = '1.0.0'
openapi ?openapi.OpenAPI openapi ?openapi.OpenAPI
openrpc ?openrpc.OpenRPC openrpc ?openrpc.OpenRPC
name string @[omitempty] name string @[omitempty]
description string @[omitempty] description string @[omitempty]
structure Struct @[omitempty] structure Struct @[omitempty]
interfaces []ActorInterface @[omitempty] interfaces []ActorInterface @[omitempty]
methods []ActorMethod @[omitempty] methods []ActorMethod @[omitempty]
objects []BaseObject @[omitempty] objects []BaseObject @[omitempty]
} }
pub enum ActorInterface { pub enum ActorInterface {
@@ -28,24 +28,24 @@ pub enum ActorInterface {
pub struct ActorMethod { pub struct ActorMethod {
pub: pub:
name string @[omitempty] name string @[omitempty]
description string @[omitempty] description string @[omitempty]
summary string summary string
example ExamplePairing example ExamplePairing
parameters []ContentDescriptor parameters []ContentDescriptor
result ContentDescriptor result ContentDescriptor
errors []ErrorSpec errors []ErrorSpec
category MethodCategory category MethodCategory
} }
pub struct BaseObject { pub struct BaseObject {
pub mut: pub mut:
schema Schema schema Schema
new_method ?ActorMethod new_method ?ActorMethod
get_method ?ActorMethod get_method ?ActorMethod
set_method ?ActorMethod set_method ?ActorMethod
delete_method ?ActorMethod delete_method ?ActorMethod
list_method ?ActorMethod list_method ?ActorMethod
filter_method ?ActorMethod filter_method ?ActorMethod
other_methods []ActorMethod other_methods []ActorMethod
} }
@@ -101,13 +101,13 @@ pub fn (s ActorSpecification) validate() ActorSpecification {
if m := methods.filter(it.is_list_method())[0] { if m := methods.filter(it.is_list_method())[0] {
obj.list_method = m obj.list_method = m
} }
validated_objects << BaseObject { validated_objects << BaseObject{
...obj ...obj
other_methods: methods.filter(!it.is_crudlf_method()) other_methods: methods.filter(!it.is_crudlf_method())
} }
} }
return ActorSpecification { return ActorSpecification{
...s, ...s
objects: validated_objects objects: validated_objects
} }
} }
@@ -150,35 +150,38 @@ fn (s ActorSpecification) is_base_object_method(method ActorMethod) bool {
fn (m ActorMethod) is_new_method() bool { fn (m ActorMethod) is_new_method() bool {
return m.name.starts_with('new') return m.name.starts_with('new')
} }
fn (m ActorMethod) is_get_method() bool { fn (m ActorMethod) is_get_method() bool {
return m.name.starts_with('get') return m.name.starts_with('get')
} }
fn (m ActorMethod) is_set_method() bool { fn (m ActorMethod) is_set_method() bool {
return m.name.starts_with('set') return m.name.starts_with('set')
} }
fn (m ActorMethod) is_delete_method() bool { fn (m ActorMethod) is_delete_method() bool {
return m.name.starts_with('delete') return m.name.starts_with('delete')
} }
fn (m ActorMethod) is_list_method() bool { fn (m ActorMethod) is_list_method() bool {
return m.name.starts_with('list') return m.name.starts_with('list')
} }
fn (m ActorMethod) is_filter_method() bool { fn (m ActorMethod) is_filter_method() bool {
return m.name.starts_with('filter') return m.name.starts_with('filter')
} }
fn (m ActorMethod) is_crudlf_method() bool { fn (m ActorMethod) is_crudlf_method() bool {
return m.is_new_method() || return m.is_new_method() || m.is_get_method() || m.is_set_method() || m.is_delete_method()
m.is_get_method() || || m.is_list_method() || m.is_filter_method()
m.is_set_method() ||
m.is_delete_method() ||
m.is_list_method() ||
m.is_filter_method()
} }
pub fn (o BaseObject) name() string { pub fn (o BaseObject) name() string {
return if o.schema.id.trim_space() != '' { return if o.schema.id.trim_space() != '' {
o.schema.id.trim_space() o.schema.id.trim_space()
} else {o.schema.title.trim_space()} } else {
o.schema.title.trim_space()
}
} }
fn (s ActorSpecification) is_base_object_new_method(method ActorMethod) bool { fn (s ActorSpecification) is_base_object_new_method(method ActorMethod) bool {

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
module specification module specification
import freeflowuniverse.herolib.schemas.openrpc {OpenRPC, Components} import freeflowuniverse.herolib.schemas.openrpc { Components, OpenRPC }
import freeflowuniverse.herolib.schemas.jsonschema {SchemaRef} import freeflowuniverse.herolib.schemas.jsonschema { SchemaRef }
import freeflowuniverse.herolib.schemas.jsonschema.codegen { struct_to_schema } import freeflowuniverse.herolib.schemas.jsonschema.codegen
// pub fn from_openrpc(spec openrpc.OpenRPC) !ActorSpecification { // pub fn from_openrpc(spec openrpc.OpenRPC) !ActorSpecification {
// // Extract Actor metadata from OpenRPC info // // Extract Actor metadata from OpenRPC info
@@ -39,7 +39,6 @@ import freeflowuniverse.herolib.schemas.jsonschema.codegen { struct_to_schema }
// } // }
// } // }
pub fn (specification ActorSpecification) to_openrpc() OpenRPC { pub fn (specification ActorSpecification) to_openrpc() OpenRPC {
mut schemas := map[string]SchemaRef{} mut schemas := map[string]SchemaRef{}
for obj in specification.objects { for obj in specification.objects {
@@ -49,11 +48,11 @@ pub fn (specification ActorSpecification) to_openrpc() OpenRPC {
// } // }
} }
return OpenRPC{ return OpenRPC{
info: openrpc.Info{ info: openrpc.Info{
title: specification.name.title() title: specification.name.title()
version: '1.0.0' version: '1.0.0'
} }
methods: specification.methods.map(method_to_openrpc_method(it)) methods: specification.methods.map(method_to_openrpc_method(it))
components: Components{ components: Components{
schemas: schemas schemas: schemas
} }
@@ -61,12 +60,12 @@ pub fn (specification ActorSpecification) to_openrpc() OpenRPC {
} }
pub fn method_to_openrpc_method(method ActorMethod) openrpc.Method { pub fn method_to_openrpc_method(method ActorMethod) openrpc.Method {
return openrpc.Method { return openrpc.Method{
name: method.name name: method.name
summary: method.summary summary: method.summary
description: method.description description: method.description
params: method.parameters.map(openrpc.ContentDescriptorRef(it)) params: method.parameters.map(openrpc.ContentDescriptorRef(it))
result: openrpc.ContentDescriptorRef(method.result) result: openrpc.ContentDescriptorRef(method.result)
errors: method.errors.map(openrpc.ErrorRef(it)) errors: method.errors.map(openrpc.ErrorRef(it))
} }
} }

View File

@@ -7,9 +7,9 @@ pub mut:
id string id string
name string name string
priority int = 10 // 0 is highest, do 10 as default priority int = 10 // 0 is highest, do 10 as default
params string // json encoded params params string // json encoded params
result string // can be used to remember outputs result string // can be used to remember outputs
// run bool = true // certain actions can be defined but meant to be executed directly // run bool = true // certain actions can be defined but meant to be executed directly
comments string comments string
done bool // if done then no longer need to process done bool // if done then no longer need to process
} }

View File

@@ -18,7 +18,7 @@ pub:
pub struct ClientConfig { pub struct ClientConfig {
ActorConfig ActorConfig
pub: pub:
redis_url string = 'localhost:6379' // url to redis server running redis_url string = 'localhost:6379' // url to redis server running
} }
pub fn new_client(config ActorConfig) !Client { pub fn new_client(config ActorConfig) !Client {
@@ -40,7 +40,7 @@ pub fn (mut p Client) call_to_action(action Action, params Params) !Action {
wait: true wait: true
})! })!
return Action { return Action{
...action ...action
result: response_data result: response_data
} }

View File

@@ -1,6 +1,6 @@
module stage module stage
import freeflowuniverse.herolib.baobab.osis {OSIS} import freeflowuniverse.herolib.baobab.osis { OSIS }
import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.core.redisclient
@[heap] @[heap]
@@ -19,8 +19,8 @@ mut:
@[params] @[params]
pub struct ActorConfig { pub struct ActorConfig {
pub: pub:
name string name string
version string version string
redis_url string = 'localhost:6379' redis_url string = 'localhost:6379'
} }
@@ -35,7 +35,7 @@ pub fn (config ActorConfig) redis_queue_name() string {
pub fn new_actor(config ActorConfig) !Actor { pub fn new_actor(config ActorConfig) !Actor {
return Actor{ return Actor{
ActorConfig: config ActorConfig: config
osis: osis.new()! osis: osis.new()!
} }
} }
@@ -45,20 +45,22 @@ pub fn (a ActorConfig) get_redis_rpc() !redisclient.RedisRpc {
} }
pub fn (a ActorConfig) version(v string) ActorConfig { pub fn (a ActorConfig) version(v string) ActorConfig {
return ActorConfig {...a, return ActorConfig{
...a
version: v version: v
} }
} }
pub fn (a ActorConfig) example() ActorConfig { pub fn (a ActorConfig) example() ActorConfig {
return ActorConfig {...a, return ActorConfig{
...a
version: 'example' version: 'example'
} }
} }
pub fn (mut a IActor) handle(method string, data string) !string { pub fn (mut a IActor) handle(method string, data string) !string {
action := a.act( action := a.act(
name: method name: method
params: data params: data
)! )!
return action.result return action.result

View File

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

View File

@@ -1,12 +1,12 @@
module interfaces module interfaces
import freeflowuniverse.herolib.schemas.jsonrpc import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.baobab.stage {Action} import freeflowuniverse.herolib.baobab.stage { Action }
pub fn action_from_jsonrpc_request(request jsonrpc.Request) Action { pub fn action_from_jsonrpc_request(request jsonrpc.Request) Action {
return Action{ return Action{
id: request.id id: request.id
name: request.method name: request.method
params: request.params params: request.params
} }
} }

View File

@@ -1,8 +1,8 @@
module interfaces module interfaces
import rand import rand
import x.json2 as json {Any} import x.json2 as json { Any }
import freeflowuniverse.herolib.baobab.stage {Action, Client} import freeflowuniverse.herolib.baobab.stage { Action, Client }
import freeflowuniverse.herolib.schemas.jsonrpc import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.schemas.openapi import freeflowuniverse.herolib.schemas.openapi
@@ -18,9 +18,7 @@ pub fn new_openapi_interface(client Client) &OpenAPIInterface {
pub fn (mut i OpenAPIInterface) handle(request openapi.Request) !openapi.Response { pub fn (mut i OpenAPIInterface) handle(request openapi.Request) !openapi.Response {
// Convert incoming OpenAPI request to a procedure call // Convert incoming OpenAPI request to a procedure call
action := action_from_openapi_request(request) action := action_from_openapi_request(request)
response := i.client.call_to_action(action) or { response := i.client.call_to_action(action) or { return err }
return err
}
return action_to_openapi_response(response) return action_to_openapi_response(response)
} }
@@ -36,15 +34,15 @@ pub fn action_from_openapi_request(request openapi.Request) Action {
params << json.encode(request.parameters) params << json.encode(request.parameters)
} }
return Action { return Action{
id: rand.uuid_v4() id: rand.uuid_v4()
name: request.operation.operation_id name: request.operation.operation_id
params: json.encode(params.str()) params: json.encode(params.str())
} }
} }
pub fn action_to_openapi_response(action Action) openapi.Response { pub fn action_to_openapi_response(action Action) openapi.Response {
return openapi.Response { return openapi.Response{
body: action.result body: action.result
} }
} }

View File

@@ -1,15 +1,15 @@
module interfaces module interfaces
import freeflowuniverse.herolib.baobab.stage {Client} import freeflowuniverse.herolib.baobab.stage { Client }
import freeflowuniverse.herolib.schemas.jsonrpc import freeflowuniverse.herolib.schemas.jsonrpc
// handler for test echoes JSONRPC Request as JSONRPC Response // handler for test echoes JSONRPC Request as JSONRPC Response
fn handler(request jsonrpc.Request) !jsonrpc.Response { fn handler(request jsonrpc.Request) !jsonrpc.Response {
return jsonrpc.Response { return jsonrpc.Response{
jsonrpc: request.jsonrpc jsonrpc: request.jsonrpc
id: request.id id: request.id
result: request.params result: request.params
} }
} }
pub struct OpenRPCInterface { pub struct OpenRPCInterface {

View File

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

View File

@@ -1,7 +1,7 @@
module interfaces module interfaces
import freeflowuniverse.herolib.schemas.openapi { OpenAPI } import freeflowuniverse.herolib.schemas.openapi { OpenAPI }
import freeflowuniverse.herolib.baobab.stage {Client, ClientConfig} import freeflowuniverse.herolib.baobab.stage { ClientConfig }
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC } import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
import veb import veb

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