Compare commits

..

13 Commits

Author SHA1 Message Date
Mahmoud Emad
7b621243d0 fix: Correct assertion in link_def_test.v
- Corrected the assertion in `test_empty()` to reflect the
  expected number of children in an empty document.
2025-03-24 12:56:17 +02:00
598b312140 ... 2025-03-24 06:44:39 +01:00
0df10f5cb3 Merge branch 'development_grid_deploy' into development
* development_grid_deploy:
  Update shebang
  Add simple filter example
2025-03-24 05:30:21 +01:00
2c748a9fc8 Merge branch 'development_actions007' into development
* development_actions007: (49 commits)
  ...
  bump version to 1.0.22
  add baobab mcp
  feat: Improve path normalization in `namefix`
  feat: Improve Qdrant client library
  test: Skip Jina client for now
  feat: Remove redundant Jina client code
  feat: Remove optional age field from Person struct
  feat: Improve DedupeStore and update tests
  test: Improve test coverage for fenced code block and list item parsers
  test: Improve test coverage for paragraph parsing
  test: Improve test coverage for markdown block parser
  test: Improve list parsing test cases
  feat: Improve Markdown parser list and table detection
  fix: Fix CI
  feat: Improve RadixTree debugging output
  refactor: Simplify ContactsDB methods
  feat: Add calendar VFS implementation
  feat: Add Contacts VFS module
  feat: Add contacts database and VFS implementation
  ...

# Conflicts:
#	.gitignore
#	lib/clients/qdrant/qdrant_client.v
#	lib/core/texttools/namefix.v
2025-03-24 05:30:15 +01:00
a2e1b4fb27 Merge branch 'development_action007_mahmoud' into development
* development_action007_mahmoud:
  feat: Add upsert points functionality to Qdrant client
  feat: Remove unnecessary delete_collection call in example
  feat: Add Qdrant client's retrieve_points functionality
  feat: Improve Qdrant client example
  ...
  feat: Add Jina server health check
  feat: Add multi-vector API support
  feat: Add classifier deletion functionality
  qdrant
  feat: Add classifier listing functionality
  feat: Enhance Jina client with improved classification API
  feat: Add train functionality to Jina client
  feat: Add Jina client training and classification features
  feat: Add reranking functionality to Jina client
  feat: Enhance Jina client with additional embedding parameters
  feat: Add create_embeddings function to Jina client
  fix: Ensure the code compiles and add a test example
  ...
  jina specs
2025-03-24 05:29:51 +01:00
9b0da9f245 Merge branch 'development_ourdb_new' into development
* development_ourdb_new: (115 commits)
  webdav completion wip
  Remove path from fsentry metadata, make vfs and webdav work again with fixes
  feat: Implement database synchronization using binary encoding
  Add documentation and tests for model_property.v
  feat: Add diagrams and README for OurDB syncer
  circle core objects work again
  ...
  ...
  radix tree has now prefix
  names
  models
  ...
  ....
  ...
  ...
  vfs_basics working
  vfs working
  ...
  ...
  ...
  ...

# Conflicts:
#	.gitignore
#	lib/code/generator/installer_client/ask.v
#	lib/code/generator/installer_client/factory.v
#	lib/code/generator/installer_client/generate.v
#	lib/code/generator/installer_client/model.v
#	lib/code/generator/installer_client/readme.md
#	lib/code/generator/installer_client/scanner.v
#	lib/code/generator/installer_client/templates/atemplate.yaml
#	lib/code/generator/installer_client/templates/heroscript_client
#	lib/code/generator/installer_client/templates/heroscript_installer
#	lib/code/generator/installer_client/templates/objname_actions.vtemplate
#	lib/code/generator/installer_client/templates/objname_factory_.vtemplate
#	lib/code/generator/installer_client/templates/objname_model.vtemplate
#	lib/code/generator/installer_client/templates/readme.md
#	lib/code/generator/installer_client_OLD/ask.v
#	lib/code/generator/installer_client_OLD/do.v
#	lib/code/generator/installer_client_OLD/generate.v
#	lib/code/generator/installer_client_OLD/model.v
#	lib/code/generator/installer_client_OLD/readme.md
#	lib/code/generator/installer_client_OLD/scanner.v
#	lib/code/generator/installer_client_OLD/templates/atemplate.yaml
#	lib/code/generator/installer_client_OLD/templates/heroscript_client
#	lib/code/generator/installer_client_OLD/templates/heroscript_installer
#	lib/code/generator/installer_client_OLD/templates/objname_actions.vtemplate
#	lib/code/generator/installer_client_OLD/templates/objname_factory_.vtemplate
#	lib/code/generator/installer_client_OLD/templates/objname_model.vtemplate
#	lib/code/generator/installer_client_OLD/templates/readme.md
#	lib/core/generator/installer_client_OLD/ask.v
#	lib/core/generator/installer_client_OLD/factory.v
#	lib/core/generator/installer_client_OLD/generate.v
#	lib/core/generator/installer_client_OLD/model.v
#	lib/core/generator/installer_client_OLD/readme.md
#	lib/core/generator/installer_client_OLD/scanner.v
#	lib/core/generator/installer_client_OLD/templates/atemplate.yaml
#	lib/core/generator/installer_client_OLD/templates/heroscript_client
#	lib/core/generator/installer_client_OLD/templates/heroscript_installer
#	lib/core/generator/installer_client_OLD/templates/objname_actions.vtemplate
#	lib/core/generator/installer_client_OLD/templates/objname_factory_.vtemplate
#	lib/core/generator/installer_client_OLD/templates/objname_model.vtemplate
#	lib/core/generator/installer_client_OLD/templates/readme.md
#	lib/core/texttools/namefix.v
2025-03-24 05:29:46 +01:00
5b9426ba11 ... 2025-03-23 12:27:31 +01:00
Mahmoud Emad
228abe36a3 feat: Add upsert points functionality to Qdrant client
- Added `upsert_points` method to the Qdrant client to allow
  inserting and updating points in a collection. This enhances
  the client's ability to manage data efficiently.
- Improved error handling in Qdrant client methods to provide
  more informative error messages. This improves the user
  experience by providing better feedback on failed operations.
2025-03-16 14:58:23 +02:00
Mahmoud Emad
c3fe788a5b feat: Remove unnecessary delete_collection call in example
- Removed the `delete_collection` call from the Qdrant example
  to avoid unnecessary collection deletion. This simplifies the
  example and prevents potential issues if the collection doesn't
  exist.
- Updated `RetrievePointsParams` struct to use optional parameters
  for `shard_key`, `with_payload`, and `with_vectors`. This
  improves flexibility and reduces the required parameters.  The
  change simplifies the request structure.
2025-03-16 11:56:55 +02:00
Mahmoud Emad
025e8fba69 feat: Add Qdrant client's retrieve_points functionality
- Added a new `retrieve_points` function to the Qdrant client
  to retrieve points by their IDs. This allows for efficient
  fetching of specific points from a collection.
- Renamed `is_exists` to `is_collection_exists` for clarity
  and consistency.
- Added  `RetrievePointsRequest`, `RetrievePointsParams`, and
  `RetrievePointsResponse` structs for better structured data.
2025-03-16 11:40:52 +02:00
Scott Yeager
59a0519b4e Update shebang 2025-03-14 19:19:14 -07:00
Scott Yeager
dfcaeec85f Add simple filter example 2025-03-14 19:19:02 -07:00
Mahmoud Emad
e374520654 feat: Improve Qdrant client example
- Simplify Qdrant client example script, removing unnecessary
  boilerplate and improving readability.
- Add functions for creating, getting, deleting and listing
  collections.
- Add function to check collection existence.
- Improve error handling and logging.
2025-03-13 16:06:23 +02:00
375 changed files with 8471 additions and 9033 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 {
@@ -53,4 +58,4 @@ pub fn array_to_mcp_tool_contents[U](array []U) []mcp.ToolContent {
contents << result_to_mcp_tool_content(item) contents << result_to_mcp_tool_content(item)
} }
return contents return contents
} }

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_}')
@@ -381,4 +388,4 @@ fn parse_struct_fields(struct_def string) map[string]string {
} }
return fields return fields
} }

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 {
@@ -52,4 +52,4 @@ pub fn array_to_mcp_tool_contents[U](array []U) []mcp.ToolContent {
contents << result_to_mcp_tool_content(item) contents << result_to_mcp_tool_content(item)
} }
return contents return contents
} }

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
@@ -24,11 +24,11 @@ pub fn test_function(config TestConfig) !TestResult {
if config.name == '' { if config.name == '' {
return error('Name cannot be empty') return error('Name cannot be empty')
} }
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,22 +12,22 @@ 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 {
return error('could not find where declaration for type ${type_name} ends') return error('could not find where declaration for type ${type_name} ends')
} }
return content.substr(start_i, closing_i + 1) return content.substr(start_i, closing_i + 1)
} }
return error('type ${type_name} not found in module ${module_path}') return error('type ${type_name} not found in module ${module_path}')
} }
@@ -49,10 +49,8 @@ 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 {
if file.ends_with('.v') && !file.ends_with('_.v') { if file.ends_with('.v') && !file.ends_with('_.v') {
@@ -60,7 +58,7 @@ fn list_v_files(dir string) ![]string {
v_files << filepath v_files << filepath
} }
} }
return v_files return v_files
} }
@@ -69,9 +67,9 @@ fn create_test_files() !(string, string, string) {
// Create a temporary directory for our test files // Create a temporary directory for our test files
test_dir := os.temp_dir() test_dir := os.temp_dir()
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,13 +99,13 @@ 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,22 +113,22 @@ 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 {
// This should fail, so we expect an error // This should fail, so we expect an error
@@ -138,21 +136,21 @@ fn test_get_type_from_module() {
'' ''
} }
assert non_existent == '', 'Expected empty string for non-existent type' assert non_existent == '', 'Expected empty string for non-existent type'
// 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 {}
os.rm(nested_test_file) or {} os.rm(nested_test_file) or {}
println('All tests for get_type_from_module passed successfully!') println('All tests for get_type_from_module passed successfully!')
} }

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,25 +20,23 @@ 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} {'
mut i := content.index(type_str) or { -1 } mut i := content.index(type_str) or { -1 }
mut is_pub := false mut is_pub := false
if i == -1 { if i == -1 {
// Try with pub struct // Try with pub struct
type_str = 'pub struct ${type_name} {' type_str = 'pub struct ${type_name} {'
i = content.index(type_str) or { -1 } i = content.index(type_str) or { -1 }
is_pub = true is_pub = true
} }
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(' ')
@@ -51,22 +49,22 @@ fn get_type_from_module(module_path string, type_name string) !string {
// Find the start of the struct definition including comments // Find the start of the struct definition including comments
mut comment_start := i mut comment_start := i
mut line_start := i mut line_start := i
// 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
} }
} }
// Find the start of the comment block (if any) // Find the start of the comment block (if any)
for j := line_start - 1; j >= 0; j-- { for j := line_start - 1; j >= 0; j-- {
if j == 0 { if j == 0 {
comment_start = 0 comment_start = 0
break break
} }
// If we hit a blank line or a non-comment line, stop // If we hit a blank line or a non-comment line, stop
if content[j] == `\n` { if content[j] == `\n` {
if j > 0 && j < content.len - 1 { if j > 0 && j < content.len - 1 {
@@ -84,15 +82,15 @@ fn get_type_from_module(module_path string, type_name string) !string {
closing_i := find_closing_brace(content, i + type_str.len) or { closing_i := find_closing_brace(content, i + type_str.len) or {
return error('could not find where declaration for type ${type_name} ends') return error('could not find where declaration for type ${type_name} ends')
} }
// Get the full struct definition including the struct declaration line // Get the full struct definition including the struct declaration line
full_struct := content.substr(line_start, closing_i + 1) full_struct := content.substr(line_start, closing_i + 1)
println('Found struct definition:\n${full_struct}') println('Found struct definition:\n${full_struct}')
// Return the full struct definition // Return the full struct definition
return full_struct return full_struct
} }
return error('type ${type_name} not found in module ${module_path}') return error('type ${type_name} not found in module ${module_path}')
} }
@@ -108,17 +106,17 @@ fn get_function_from_module(module_path string, function_name string) !string {
println('Found ${v_files.len} V files in ${module_path}') println('Found ${v_files.len} V files in ${module_path}')
for v_file in v_files { for v_file in v_files {
println('Checking file: ${v_file}') println('Checking file: ${v_file}')
result := get_function_from_file(v_file, function_name) or { result := get_function_from_file(v_file, function_name) or {
println('Function not found in ${v_file}: ${err}') println('Function not found in ${v_file}: ${err}')
continue continue
} }
println('Found function ${function_name} in ${v_file}') println('Found function ${function_name} in ${v_file}')
return result return result
} }
return error('function ${function_name} not found in module ${module_path}') return error('function ${function_name} not found in module ${module_path}')
} }
fn find_closing_brace(content string, start_i int) ?int { fn find_closing_brace(content string, start_i int) ?int {
mut brace_count := 1 mut brace_count := 1
for i := start_i; i < content.len; i++ { for i := start_i; i < content.len; i++ {
@@ -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
@@ -146,16 +141,16 @@ fn get_function_from_file(file_path string, function_name string) !string {
content := os.read_file(file_path) or { content := os.read_file(file_path) or {
return error('Failed to read file: ${file_path}: ${err}') return error('Failed to read file: ${file_path}: ${err}')
} }
lines := content.split_into_lines() lines := content.split_into_lines()
mut result := []string{} mut result := []string{}
mut in_function := false mut in_function := false
mut brace_count := 0 mut brace_count := 0
mut comment_block := []string{} mut comment_block := []string{}
for i, line in lines { for i, line in lines {
trimmed := line.trim_space() trimmed := line.trim_space()
// Collect comments that might be above the function // Collect comments that might be above the function
if trimmed.starts_with('//') { if trimmed.starts_with('//') {
if !in_function { if !in_function {
@@ -165,10 +160,10 @@ fn get_function_from_file(file_path string, function_name string) !string {
} }
continue continue
} }
// 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
@@ -179,11 +174,11 @@ fn get_function_from_file(file_path string, function_name string) !string {
} }
continue continue
} }
// If we're inside the function, keep track of braces // If we're inside the function, keep track of braces
if in_function { if in_function {
result << line result << line
for c in line { for c in line {
if c == `{` { if c == `{` {
brace_count++ brace_count++
@@ -191,7 +186,7 @@ fn get_function_from_file(file_path string, function_name string) !string {
brace_count-- brace_count--
} }
} }
// If brace_count is 0, we've reached the end of the function // If brace_count is 0, we've reached the end of the function
if brace_count == 0 && trimmed.contains('}') { if brace_count == 0 && trimmed.contains('}') {
return result.join('\n') return result.join('\n')
@@ -203,20 +198,18 @@ fn get_function_from_file(file_path string, function_name string) !string {
} }
} }
} }
if !in_function { if !in_function {
return error('Function "${function_name}" not found in ${file_path}') return error('Function "${function_name}" not found in ${file_path}')
} }
return result.join('\n') return result.join('\n')
} }
// 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 {
if file.ends_with('.v') && !file.ends_with('_.v') { if file.ends_with('.v') && !file.ends_with('_.v') {
@@ -224,53 +217,50 @@ fn list_v_files(dir string) ![]string {
v_files << filepath v_files << filepath
} }
} }
return v_files return v_files
} }
// 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

@@ -12,9 +12,9 @@ fn create_test_files() !(string, string, string) {
// Create a temporary directory for our test files // Create a temporary directory for our test files
test_dir := os.temp_dir() test_dir := os.temp_dir()
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,13 +44,13 @@ 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,22 +58,22 @@ 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 {
// This should fail, so we expect an error // This should fail, so we expect an error
@@ -81,20 +81,20 @@ fn test_get_type_from_module() {
'' ''
} }
assert non_existent == '', 'Expected empty string for non-existent type' assert non_existent == '', 'Expected empty string for non-existent type'
// 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 {}
os.rm(nested_test_file) or {} os.rm(nested_test_file) or {}
println('All tests for get_type_from_module passed successfully!') println('All tests for get_type_from_module passed successfully!')
} }

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

@@ -10,60 +10,60 @@ fn main() {
// Set environment variable for testing // Set environment variable for testing
// In production, you would set this in your environment // In production, you would set this in your environment
// osal.env_set(key: 'JINAKEY', value: 'your-api-key') // osal.env_set(key: 'JINAKEY', value: 'your-api-key')
// Check if JINAKEY environment variable exists // Check if JINAKEY environment variable exists
if !osal.env_exists('JINAKEY') { if !osal.env_exists('JINAKEY') {
println('JINAKEY environment variable not set. Please set it before running this example.') println('JINAKEY environment variable not set. Please set it before running this example.')
exit(1) exit(1)
} }
// Create a Jina client instance // Create a Jina client instance
mut client := jina.get(name: 'default')! mut client := jina.get(name: 'default')!
println('Jina client initialized successfully.') println('Jina client initialized successfully.')
// Example: Create embeddings // Example: Create embeddings
model := 'jina-embeddings-v3' model := 'jina-embeddings-v3'
texts := ['Hello, world!', 'How are you doing?'] texts := ['Hello, world!', 'How are you doing?']
println('Creating embeddings for texts: ${texts}') println('Creating embeddings for texts: ${texts}')
result := client.create_embeddings(texts, model, 'retrieval.query')! result := client.create_embeddings(texts, model, 'retrieval.query')!
println('Embeddings created successfully.') println('Embeddings created successfully.')
println('Model: ${result['model']}') println('Model: ${result['model']}')
println('Data count: ${result['data'].arr().len}') println('Data count: ${result['data'].arr().len}')
// Example: List classifiers // Example: List classifiers
println('\nListing classifiers:') println('\nListing classifiers:')
classifiers := client.list_classifiers() or { classifiers := client.list_classifiers() or {
println('Failed to list classifiers: ${err}') println('Failed to list classifiers: ${err}')
return return
} }
println('Classifiers retrieved successfully.') println('Classifiers retrieved successfully.')
// Example: Create a classifier // Example: Create a classifier
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 {
println('Failed to train classifier: ${err}') println('Failed to train classifier: ${err}')
return return
} }
println('Classifier trained successfully.') println('Classifier trained successfully.')
println('Classifier ID: ${training_result['classifier_id']}') println('Classifier ID: ${training_result['classifier_id']}')
} }

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

@@ -13,7 +13,7 @@ openrpc_spec := openrpc.new(path: openrpc_spec_path)!
actor_spec := specification.from_openrpc(openrpc_spec)! 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
} }
@@ -48,15 +49,15 @@ pub fn (mut c Commerce) create_product_template(name string, description string,
if merchant_id !in c.merchants { if merchant_id !in c.merchants {
return error('Merchant not found') return error('Merchant not found')
} }
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
@@ -70,23 +71,23 @@ pub fn (mut c Commerce) create_product(template_id string, merchant_id string, s
if merchant_id !in c.merchants { if merchant_id !in c.merchants {
return error('Merchant not found') return error('Merchant not found')
} }
template := c.templates[template_id] template := c.templates[template_id]
mut total_price := 0.0 mut total_price := 0.0
for component in template.components { for component in template.components {
total_price += component.price total_price += component.price
} }
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
@@ -96,7 +97,7 @@ pub fn (mut c Commerce) create_product(template_id string, merchant_id string, s
pub fn (mut c Commerce) create_order(customer_id string, items []OrderItem) !Order { pub fn (mut c Commerce) create_order(customer_id string, items []OrderItem) !Order {
mut total_amount := 0.0 mut total_amount := 0.0
mut currency := '' mut currency := ''
for item in items { for item in items {
if item.product_id !in c.products { if item.product_id !in c.products {
return error('Product not found: ${item.product_id}') return error('Product not found: ${item.product_id}')
@@ -112,19 +113,19 @@ pub fn (mut c Commerce) create_order(customer_id string, items []OrderItem) !Ord
return error('Mixed currencies are not supported') return error('Mixed currencies are not supported')
} }
} }
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
// Update stock quantities // Update stock quantities
for item in items { for item in items {
mut product := c.products[item.product_id] mut product := c.products[item.product_id]
@@ -134,7 +135,7 @@ pub fn (mut c Commerce) create_order(customer_id string, items []OrderItem) !Ord
} }
c.products[item.product_id] = product c.products[item.product_id] = product
} }
return order return order
} }
@@ -143,7 +144,7 @@ pub fn (mut c Commerce) update_order_status(order_id string, new_status string)
if order_id !in c.orders { if order_id !in c.orders {
return error('Order not found') return error('Order not found')
} }
mut order := c.orders[order_id] mut order := c.orders[order_id]
order.status = new_status order.status = new_status
order.updated_at = time.now().str() order.updated_at = time.now().str()
@@ -156,7 +157,7 @@ pub fn (c Commerce) get_merchant_products(merchant_id string) ![]Product {
if merchant_id !in c.merchants { if merchant_id !in c.merchants {
return error('Merchant not found') return error('Merchant not found')
} }
mut products := []Product{} mut products := []Product{}
for product in c.products.values() { for product in c.products.values() {
if product.merchant_id == merchant_id { if product.merchant_id == merchant_id {
@@ -171,7 +172,7 @@ pub fn (c Commerce) get_merchant_orders(merchant_id string) ![]Order {
if merchant_id !in c.merchants { if merchant_id !in c.merchants {
return error('Merchant not found') return error('Merchant not found')
} }
mut orders := []Order{} mut orders := []Order{}
for order in c.orders.values() { for order in c.orders.values() {
mut includes_merchant := false mut includes_merchant := false

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

@@ -10,4 +10,4 @@ const openapi_spec_path = os.join_path(example_dir, '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_specification := specification.from_openapi(openapi_spec)! actor_specification := specification.from_openapi(openapi_spec)!
println(actor_specification) println(actor_specification)

View File

@@ -10,4 +10,4 @@ const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
// the actor specification obtained from the OpenRPC Specification // the actor specification obtained from the OpenRPC Specification
openrpc_spec := openrpc.new(path: openrpc_spec_path)! openrpc_spec := openrpc.new(path: openrpc_spec_path)!
actor_specification := specification.from_openrpc(openrpc_spec)! actor_specification := specification.from_openrpc(openrpc_spec)!
println(actor_specification) println(actor_specification)

View File

@@ -7,101 +7,101 @@ 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()
println(json.encode_pretty(openapi_specification)) println(json.encode_pretty(openapi_specification))

View File

@@ -8,102 +8,102 @@ 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()
println(json.encode_pretty(openrpc_specification)) println(json.encode_pretty(openrpc_specification))

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'
} }
} }
@@ -79,30 +79,30 @@ assert decoded_agent.services.len == agent.services.len
if decoded_agent.services.len > 0 { if decoded_agent.services.len > 0 {
service1 := decoded_agent.services[0] service1 := decoded_agent.services[0]
original_service := agent.services[0] original_service := agent.services[0]
assert service1.actor == original_service.actor assert service1.actor == original_service.actor
assert service1.description == original_service.description assert service1.description == original_service.description
assert service1.status == original_service.status assert service1.status == original_service.status
assert service1.public == original_service.public assert service1.public == original_service.public
// Verify actions // Verify actions
assert service1.actions.len == original_service.actions.len assert service1.actions.len == original_service.actions.len
if service1.actions.len > 0 { if service1.actions.len > 0 {
action1 := service1.actions[0] action1 := service1.actions[0]
original_action := original_service.actions[0] original_action := original_service.actions[0]
assert action1.action == original_action.action assert action1.action == original_action.action
assert action1.description == original_action.description assert action1.description == original_action.description
assert action1.status == original_action.status assert action1.status == original_action.status
assert action1.public == original_action.public assert action1.public == original_action.public
// Verify params // Verify params
assert action1.params.len == original_action.params.len assert action1.params.len == original_action.params.len
for key, value in original_action.params { for key, value in original_action.params {
assert key in action1.params assert key in action1.params
assert action1.params[key] == value assert action1.params[key] == value
} }
// Verify params_example // Verify params_example
assert action1.params_example.len == original_action.params_example.len assert action1.params_example.len == original_action.params_example.len
for key, value in original_action.params_example { for key, value in original_action.params_example {

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,44 +7,44 @@ 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')
} }
// Initialize the client // Initialize the client
j = jina.obj_init(j) or { j = jina.obj_init(j) or {
println('Error initializing Jina client: ${err}') println('Error initializing Jina client: ${err}')
return return
} }
// Check if authentication works // Check if authentication works
auth_ok := j.check_auth() or { auth_ok := j.check_auth() or {
println('Authentication failed: ${err}') println('Authentication failed: ${err}')
return return
} }
println('Authentication successful: ${auth_ok}') println('Authentication successful: ${auth_ok}')
// Create embeddings // Create embeddings
model := 'jina-embeddings-v2-base-en' model := 'jina-embeddings-v2-base-en'
input := ['Hello world', 'This is a test'] input := ['Hello world', 'This is a test']
embeddings := j.create_embeddings(input, model, 'search') or { embeddings := j.create_embeddings(input, model, 'search') or {
println('Error creating embeddings: ${err}') println('Error creating embeddings: ${err}')
return return
} }
println('Embeddings created successfully!') println('Embeddings created successfully!')
println('Model: ${embeddings.model}') println('Model: ${embeddings.model}')
println('Dimension: ${embeddings.dimension}') println('Dimension: ${embeddings.dimension}')
println('Number of embeddings: ${embeddings.data.len}') println('Number of embeddings: ${embeddings.data.len}')
// If there are embeddings, print the first one (truncated) // If there are embeddings, print the first one (truncated)
if embeddings.data.len > 0 { if embeddings.data.len > 0 {
first_embedding := embeddings.data[0] first_embedding := embeddings.data[0]
println('First embedding (first 5 values): ${first_embedding.embedding[0..5]}') println('First embedding (first 5 values): ${first_embedding.embedding[0..5]}')
} }
// Usage information // Usage information
println('Token usage: ${embeddings.usage.total_tokens} ${embeddings.usage.unit}') println('Token usage: ${embeddings.usage.total_tokens} ${embeddings.usage.unit}')
} }

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 {
@@ -119,4 +103,4 @@ println(another_file_content2.bytestr())
// } // }
// if duplicate_content.len > 0 { // if duplicate_content.len > 0 {
// println(duplicate_content.bytestr()) // println(duplicate_content.bytestr())
// } // }

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,15 +6,18 @@ 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)
server.run() server.run()

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

@@ -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 {
@@ -50,7 +50,7 @@ import net.http
// name_snake := texttools.snake_case(object) // name_snake := texttools.snake_case(object)
// name_pascal := texttools.pascal_case(object) // name_pascal := texttools.pascal_case(object)
// root := get_endpoint_root(params.endpoint) // root := get_endpoint_root(params.endpoint)
// return "async list${name_pascal}(): Promise<${name_pascal}[]> {\n return this.restClient.get<${name_pascal}[]>(`/${root}/${name_snake}`);\n }" // return "async list${name_pascal}(): Promise<${name_pascal}[]> {\n return this.restClient.get<${name_pascal}[]>(`/${root}/${name_snake}`);\n }"
// } // }
@@ -58,7 +58,7 @@ fn get_endpoint_root(root string) string {
return if root == '' { return if root == '' {
'' ''
} else { } else {
"/${root.trim('/')}" '/${root.trim('/')}'
} }
} }
@@ -112,7 +112,7 @@ fn get_endpoint_root(root string) string {
// .map(content_descriptor_to_parameter(it) or {panic(err)}) // .map(content_descriptor_to_parameter(it) or {panic(err)})
// .map(it.typescript()) // .map(it.typescript())
// .join(', ') // .join(', ')
// return_type := content_descriptor_to_parameter(method.result) or {panic(err)}.typ.typescript() // return_type := content_descriptor_to_parameter(method.result) or {panic(err)}.typ.typescript()
// return 'async ${name}(${params}): Promise<${return_type}> {}' // return 'async ${name}(${params}): Promise<${return_type}> {}'
// } // }

View File

@@ -3,198 +3,201 @@ module generator
import x.json2 as json import x.json2 as json
import arrays import arrays
import freeflowuniverse.herolib.core.code import freeflowuniverse.herolib.core.code
import freeflowuniverse.herolib.baobab.specification 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
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 {
@@ -211,8 +211,8 @@ const id_param = Param{
// only_mutable: false // only_mutable: false
// ) // )
// body := 'return ${object_type}List{items:actor.backend.list[${object_type}]()!}' // body := 'return ${object_type}List{items:actor.backend.list[${object_type}]()!}'
// result_struct := generate_list_result_struct(actor, object) // result_struct := generate_list_result_struct(actor, object)
// mut result := Param{} // mut result := Param{}
// result.typ.symbol = result_struct.name // result.typ.symbol = result_struct.name
// result.is_result = true // result.is_result = true

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]
@@ -15,7 +15,7 @@ pub:
pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder { pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
mut files := []IFile{} mut files := []IFile{}
mut folders := []IFolder{} mut folders := []IFolder{}
files = [generate_readme_file(spec)!] files = [generate_readme_file(spec)!]
mut docs_files := []IFile{} mut docs_files := []IFile{}
@@ -33,7 +33,7 @@ pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
// convert actor spec to openrpc spec // convert actor spec to openrpc spec
openapi_spec_raw := spec.to_openapi() openapi_spec_raw := spec.to_openapi()
spec_files << generate_openapi_file(openapi_spec_raw)! spec_files << generate_openapi_file(openapi_spec_raw)!
openapi_spec := openapi.process(openapi_spec_raw)! openapi_spec := openapi.process(openapi_spec_raw)!
folders << generate_openapi_ts_client(openapi_spec)! folders << generate_openapi_ts_client(openapi_spec)!
} }
@@ -41,27 +41,27 @@ 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]
} }
folders << generate_scripts_folder(spec.name, false) folders << generate_scripts_folder(spec.name, false)
folders << generate_examples_folder()! folders << generate_examples_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,25 +1,25 @@
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_actor_module(spec ActorSpecification, params Params) !Module { pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
mut files := []IFile{} mut files := []IFile{}
mut folders := []IFolder{} mut folders := []IFolder{}
files = [ files = [
generate_actor_file(spec)!, generate_actor_file(spec)!,
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
@@ -57,15 +57,15 @@ 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
) )
} }
@@ -75,8 +75,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}]
} }
} }
@@ -86,8 +86,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}]
} }
} }
@@ -99,8 +99,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,18 +1,18 @@
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)
actor_name_pascal := texttools.pascal_case(spec.name) actor_name_pascal := texttools.pascal_case(spec.name)
mut items := []CodeItem{} mut items := []CodeItem{}
items << CustomCode {' items << CustomCode{'
pub struct Client { pub struct Client {
stage.Client stage.Client
} }
@@ -22,12 +22,12 @@ pub fn generate_client_file(spec ActorSpecification) !VFile {
Client: stage.new_client(config)! Client: stage.new_client(config)!
} }
}'} }'}
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'
@@ -36,39 +36,39 @@ 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
} }
} }
pub fn generate_example_client_file(spec ActorSpecification) !VFile { pub fn generate_example_client_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)
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{}
@@ -64,7 +64,7 @@ pub fn generate_method_cmd_function(actor_name string, method ActorMethod) strin
actor_name_snake := texttools.snake_case(actor_name) actor_name_snake := texttools.snake_case(actor_name)
method_name_snake := texttools.snake_case(method.name) method_name_snake := texttools.snake_case(method.name)
method_call := if method.result.name == '' { method_call := if method.result.name == '' {
'${actor_name_snake}.${method_name_snake}()!' '${actor_name_snake}.${method_name_snake}()!'
} else { } else {

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,62 +1,80 @@
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']
pub fn generate_methods_file(spec ActorSpecification) !VFile { pub fn generate_methods_file(spec ActorSpecification) !VFile {
name_snake := texttools.snake_case(spec.name) name_snake := texttools.snake_case(spec.name)
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,60 +1,69 @@
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{}
// TODO: document assumption // TODO: document assumption
// obj_params := method.parameters.filter(if it.schema is Schema {it.schema.typ == 'object'} else {false}).map(schema_to_struct(it.schema as Schema)) // obj_params := method.parameters.filter(if it.schema is Schema {it.schema.typ == 'object'} else {false}).map(schema_to_struct(it.schema as Schema))
@@ -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)!
@@ -81,4 +91,4 @@ pub fn generate_method_example_code(receiver code.Param, method ActorMethod) ![]
body: body body: body
} }
return method_code return method_code
} }

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)
@@ -81,29 +87,25 @@ 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. {
// .new { ts_client_new_body(op, path) }
// .get { ts_client_get_body(op, path) }
// .set { ts_client_set_body(op, path) }
// .delete { ts_client_delete_body(op, path) }
// .list { ts_client_list_body(op, path) }
// else {''}
// }
// } else {''}
// }
// return if operation_is_base_object_method(op) {
// bo_method := operation_to_base_object_method(op)
// match bo_method. {
// .new { ts_client_new_body(op, path) }
// .get { ts_client_get_body(op, path) }
// .set { ts_client_set_body(op, path) }
// .delete { ts_client_delete_body(op, path) }
// .list { ts_client_list_body(op, path) }
// else {''}
// }
// } else {''}
// }
fn get_endpoint(path string) string { fn get_endpoint(path string) string {
return if path == '' { return if path == '' {
'' ''
} else { } else {
"/${path.trim('/')}" '/${path.trim('/')}'
} }
} }
@@ -155,4 +157,4 @@ fn get_endpoint(path string) string {
// pub: // pub:
// typ BaseObjectMethodType // typ BaseObjectMethodType
// object string // the name of the base object // object string // the name of the base object
// } // }

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
@@ -71,4 +71,4 @@ fn test_create_user() ! {
mut client := new_client()! mut client := new_client()!
client.create_user()! client.create_user()!
println('test_create_user passed') println('test_create_user passed')
} }

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)
@@ -113,4 +113,4 @@ pub fn get_indices[T](object T) map[string]string {
} }
} }
return indices return indices
} }

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

@@ -20,4 +20,4 @@ pub fn (mut storer Storer) generic_set[T](obj T) ! {
pub fn (mut storer Storer) delete(id u32) ! { pub fn (mut storer Storer) delete(id u32) ! {
storer.db.delete(id)! storer.db.delete(id)!
} }

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
} }
} }
@@ -122,7 +132,7 @@ pub fn from_openapi(spec_raw OpenAPI) !ActorSpecification {
mut objects := []BaseObject{} mut objects := []BaseObject{}
// get all operations for path as list of tuple [](path_string, http.Method, openapi.Operation) // get all operations for path as list of tuple [](path_string, http.Method, openapi.Operation)
// Extract methods from OpenAPI paths // Extract methods from OpenAPI paths
// for path, item in spec.paths { // for path, item in spec.paths {
// if item.get.operation_id != '' { // if item.get.operation_id != '' {
@@ -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,207 +194,207 @@ 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() ! {
// panic(from_openapi(openapi_spec)!) // panic(from_openapi(openapi_spec)!)
assert from_openapi(openapi_spec)! == actor_spec assert from_openapi(openapi_spec)! == actor_spec
} }

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
@@ -12,7 +12,7 @@ fn openrpc_method_to_actor_method(method Method) ActorMethod {
// Process parameters // Process parameters
for param in method.params { for param in method.params {
if param is ContentDescriptor { if param is ContentDescriptor {
parameters << param parameters << param
} else { } else {
panic('Method param should be inflated') panic('Method param should be inflated')
} }
@@ -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
assert a.summary == b.summary, 'Method summaries do not match for method ${a.name}.'
// Compare descriptions // Compare summaries
assert a.description == b.description, 'Method descriptions do not match for method ${a.name}.' assert a.summary == b.summary, 'Method summaries do not match for method ${a.name}.'
// Compare parameters count // Compare descriptions
assert a.parameters.len == b.parameters.len, 'Parameter counts do not match for method ${a.name}.' assert a.description == b.description, 'Method descriptions do not match for method ${a.name}.'
// Compare each parameter // Compare parameters count
for i, param_a in a.parameters { assert a.parameters.len == b.parameters.len, 'Parameter counts do not match for method ${a.name}.'
assert_params_match(param_a, b.parameters[i], a.name)
}
// Compare result // Compare each parameter
assert_params_match(a.result, b.result, a.name) for i, param_a in a.parameters {
assert_params_match(param_a, b.parameters[i], a.name)
}
// Compare result
assert_params_match(a.result, b.result, a.name)
} }
fn assert_params_match(a openrpc.ContentDescriptor, b openrpc.ContentDescriptor, method_name string) { 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) {
@@ -390,4 +431,4 @@ fn assert_params_match(a openrpc.ContentDescriptor, b openrpc.ContentDescriptor,
// // Compare other schema fields as needed (e.g., properties, additional properties, items, etc.) // // Compare other schema fields as needed (e.g., properties, additional properties, items, etc.)
// // Add more checks here if needed for deeper schema comparisons // // Add more checks here if needed for deeper schema comparisons
// } // }

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
} }
@@ -66,7 +66,7 @@ fn (m ActorMethod) belongs_to_object(obj BaseObject) bool {
.filter(it.schema is Schema) .filter(it.schema is Schema)
.map(it.schema as Schema) .map(it.schema as Schema)
.any(it.id == obj.schema.id) .any(it.id == obj.schema.id)
base_obj_is_result := if m.result.schema is Schema { base_obj_is_result := if m.result.schema is Schema {
m.result.schema.id == obj.schema.id m.result.schema.id == obj.schema.id
} else { } else {
@@ -77,7 +77,7 @@ fn (m ActorMethod) belongs_to_object(obj BaseObject) bool {
return base_obj_is_param || base_obj_is_result return base_obj_is_param || base_obj_is_result
} }
pub fn (s ActorSpecification) validate() ActorSpecification { pub fn (s ActorSpecification) validate() ActorSpecification {
mut validated_objects := []BaseObject{} mut validated_objects := []BaseObject{}
for obj_ in s.objects { for obj_ in s.objects {
mut obj := obj_ mut obj := obj_
@@ -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
} }
} }
@@ -129,14 +129,14 @@ pub fn (s ActorSpecification) method_type(method ActorMethod) MethodCategory {
} }
} }
// a base object method is a method that is a // a base object method is a method that is a
// CRUD+list+filter method of a base object // CRUD+list+filter method of a base object
fn (s ActorSpecification) is_base_object_method(method ActorMethod) bool { fn (s ActorSpecification) is_base_object_method(method ActorMethod) bool {
base_obj_is_param := method.parameters base_obj_is_param := method.parameters
.filter(it.schema is Schema) .filter(it.schema is Schema)
.map(it.schema as Schema) .map(it.schema as Schema)
.any(it.id in s.objects.map(it.schema.id)) .any(it.id in s.objects.map(it.schema.id))
base_obj_is_result := if method.result.schema is Schema { base_obj_is_result := if method.result.schema is Schema {
method.result.schema.id in s.objects.map(it.name()) method.result.schema.id in s.objects.map(it.name())
} else { } else {
@@ -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 {
@@ -199,4 +202,4 @@ fn (s ActorSpecification) is_base_object_delete_method(method ActorMethod) bool
fn (s ActorSpecification) is_base_object_list_method(method ActorMethod) bool { fn (s ActorSpecification) is_base_object_list_method(method ActorMethod) bool {
return s.is_base_object_method(method) && method.name.starts_with('list') return s.is_base_object_method(method) && method.name.starts_with('list')
} }

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,30 +68,30 @@ 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
} }
} }
} }
return op return op
} }

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,16 +1,16 @@
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
} }
} }
pub fn action_to_jsonrpc_response(action Action) jsonrpc.Response { pub fn action_to_jsonrpc_response(action Action) jsonrpc.Response {
return jsonrpc.new_response(action.id, action.result) return jsonrpc.new_response(action.id, action.result)
} }

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)
} }
@@ -35,16 +33,16 @@ pub fn action_from_openapi_request(request openapi.Request) Action {
if request.parameters.len > 0 { if request.parameters.len > 0 {
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 {
@@ -26,4 +26,4 @@ pub fn (mut i OpenRPCInterface) handle(request jsonrpc.Request) !jsonrpc.Respons
action := action_from_jsonrpc_request(request) action := action_from_jsonrpc_request(request)
response := i.client.call_to_action(action)! response := i.client.call_to_action(action)!
return action_to_jsonrpc_response(response) return action_to_jsonrpc_response(response)
} }

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