...
This commit is contained in:
51
libarchive/baobab/README.md
Normal file
51
libarchive/baobab/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Base Object and Actor Backend
|
||||
|
||||
This is Hero’s backend, designed around the concept of base objects and actors to enable modular, domain-specific operations.
|
||||
|
||||
## Base Object
|
||||
|
||||
Base objects are digital representations of real-world entities. Examples include projects, publications, books, stories (agile), and calendar events. These objects:
|
||||
• Serve as the primary data units that actors operate on.
|
||||
• Contain indexable fields for efficient retrieval.
|
||||
• Share a common base class with attributes like:
|
||||
• Name: The object’s identifier.
|
||||
• Description: A brief summary of the object.
|
||||
• Remarks: A list of additional notes or metadata.
|
||||
|
||||
Base objects are stored, indexed, retrieved, and updated using OSIS (Object Storage and Indexing System).
|
||||
|
||||
## Actor
|
||||
|
||||
Actors are domain-specific operation handlers that work on base objects. For instance, a Project Manager Actor might manage operations on stories, sprints, or projects.
|
||||
|
||||
Key Features of Actors:
|
||||
• Domain-Specific Languages (DSLs): Actor methods form intuitive, logical DSLs for interacting with base objects.
|
||||
• Specification-Driven:
|
||||
• Actors are generated from specifications.
|
||||
• Code written for actor methods can be parsed back into specifications.
|
||||
• Code Generation: Specifications enable automated boilerplate code generation, reducing manual effort.
|
||||
|
||||
## Modules
|
||||
|
||||
### OSIS: Object Storage and Indexing System
|
||||
|
||||
OSIS is a module designed for efficient storage and indexing of root objects based on specific fields. It enables seamless management of data across various backends, with built-in support for field-based filtering and searching.
|
||||
|
||||
#### Key Components
|
||||
|
||||
**Indexer:**
|
||||
* Creates and manages SQL tables based on base object specifications.
|
||||
* Enables indexing of specific fields, making them searchable and filterable.
|
||||
|
||||
**Storer**:
|
||||
* Handles actual data storage in different databases.
|
||||
* Supports diverse encoding and encryption methods for secure data management.
|
||||
|
||||
By integrating OSIS, the backend achieves both high-performance data querying and flexible, secure storage solutions.
|
||||
|
||||
### Example Actor Module
|
||||
|
||||
The Example Actor module is a reference and testable example of a generated actor within Baobab. It demonstrates the structure of actor modules generated from specifications and can also be parsed back into specifications. This module serves two key purposes:
|
||||
|
||||
1. Acts as a reference for developers working on Baobab to understand and program against actor specifications.
|
||||
2. Provides a compilable, generatable module for testing and validating Baobab’s code generation tools.
|
||||
68
libarchive/baobab/actor/client.v
Normal file
68
libarchive/baobab/actor/client.v
Normal file
@@ -0,0 +1,68 @@
|
||||
module actor
|
||||
|
||||
import json
|
||||
import freeflowuniverse.herolib.clients.redisclient
|
||||
import freeflowuniverse.herolib.baobab.action { ProcedureCall, ProcedureResponse }
|
||||
|
||||
// Processor struct for managing procedure calls
|
||||
pub struct Client {
|
||||
pub mut:
|
||||
rpc redisclient.RedisRpc // Redis RPC mechanism
|
||||
}
|
||||
|
||||
// Parameters for processing a procedure call
|
||||
@[params]
|
||||
pub struct Params {
|
||||
pub:
|
||||
timeout int = 60 // Timeout in seconds
|
||||
}
|
||||
|
||||
pub struct ClientConfig {
|
||||
pub:
|
||||
redis_url string // url to redis server running
|
||||
redis_queue string // name of redis queue
|
||||
}
|
||||
|
||||
pub fn new_client(config ClientConfig) !Client {
|
||||
mut redis := redisclient.new(config.redis_url)!
|
||||
mut rpc_q := redis.rpc_get(config.redis_queue)
|
||||
|
||||
return Client{
|
||||
rpc: rpc_q
|
||||
}
|
||||
}
|
||||
|
||||
// Process the procedure call
|
||||
pub fn (mut p Client) monologue(call ProcedureCall, params Params) ! {
|
||||
// Use RedisRpc's `call` to send the call and wait for the response
|
||||
response_data := p.rpc.call(redisclient.RPCArgs{
|
||||
cmd: call.method
|
||||
data: call.params
|
||||
timeout: u64(params.timeout * 1000) // Convert seconds to milliseconds
|
||||
wait: true
|
||||
})!
|
||||
// TODO: check error type
|
||||
}
|
||||
|
||||
// Process the procedure call
|
||||
pub fn (mut p Client) call_to_action(action Procedure, params Params) !ProcedureResponse {
|
||||
// Use RedisRpc's `call` to send the call and wait for the response
|
||||
response_data := p.rpc.call(redisclient.RPCArgs{
|
||||
cmd: call.method
|
||||
data: call.params
|
||||
timeout: u64(params.timeout * 1000) // Convert seconds to milliseconds
|
||||
wait: true
|
||||
}) or {
|
||||
// TODO: check error type
|
||||
return ProcedureResponse{
|
||||
error: err.msg()
|
||||
}
|
||||
// return ProcedureError{
|
||||
// reason: .timeout
|
||||
// }
|
||||
}
|
||||
|
||||
return ProcedureResponse{
|
||||
result: response_data
|
||||
}
|
||||
}
|
||||
63
libarchive/baobab/generator/README.md
Normal file
63
libarchive/baobab/generator/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Generator
|
||||
|
||||
The Generator synchronizes actor code and specifications, allowing bidirectional transformation between the two.
|
||||
|
||||
This a
|
||||
|
||||
|
||||
|
||||
## Development Workflow
|
||||
|
||||
A sample development workflow using the generator would be like:
|
||||
1. generating actor specification from an actor openrpc / openapi specification (see [specification reflection](specification/#reflection))
|
||||
2. generating actor code from the actor specification
|
||||
3. updating actor code by filling in method prototypes
|
||||
4. adding methods to the actor to develop actor further
|
||||
5. parsing specification back from actor
|
||||
|
||||
6. regenerating actor from the specification
|
||||
this allows for
|
||||
|
||||
- a tool which takes dir as input
|
||||
- is just some v files which define models
|
||||
- outputs a generated code dir with
|
||||
- heroscript to memory for the model
|
||||
- supporting v script for manipulated model
|
||||
- name of actor e.g. ProjectManager, module would be project_manager
|
||||
|
||||
## how does the actor work
|
||||
|
||||
- is a global e.g. projectmanager_factory
|
||||
- with double map
|
||||
- key1: cid
|
||||
- object: ProjectManager Object
|
||||
|
||||
- Object: Project Manager
|
||||
- has as properties:
|
||||
- db_$rootobjectname which is map
|
||||
- key: oid
|
||||
- val: the Model which represents the rootobject
|
||||
|
||||
- on factory
|
||||
- actions_process
|
||||
- process heroscript through path or text (params)
|
||||
- action_process
|
||||
- take 1 action as input
|
||||
- ${rootobjectname}_export
|
||||
- export all known objects as heroscript in chosen dir
|
||||
- name of heroscript would be ${rootobjectname}_define.md
|
||||
- ${rootobjectname}_get(oid)
|
||||
- returns rootobject as copy
|
||||
- ${rootobjectname}_list()!
|
||||
- returns list as copy
|
||||
- ${rootobjectname}_set(oid,obj)!
|
||||
- ${rootobjectname}_delete(oid)!
|
||||
- ${rootobjectname}_new()!
|
||||
|
||||
- in action we have
|
||||
- define
|
||||
- export/import
|
||||
- get
|
||||
- list
|
||||
|
||||
|
||||
118
libarchive/baobab/generator/_archive/client_typescript.v
Normal file
118
libarchive/baobab/generator/_archive/client_typescript.v
Normal file
@@ -0,0 +1,118 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import net.http
|
||||
|
||||
// pub enum BaseObjectMethodType {
|
||||
// new
|
||||
// get
|
||||
// set
|
||||
// delete
|
||||
// list
|
||||
// other
|
||||
// }
|
||||
|
||||
// pub struct BaseObjectMethod {
|
||||
// pub:
|
||||
// typ BaseObjectMethodType
|
||||
// object string // the name of the base object
|
||||
// }
|
||||
|
||||
// pub fn ts_client_get_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async get${name_pascal}(id: string): Promise<${name_pascal}> {\n return this.restClient.get<${name_pascal}>(`/${root}/${name_snake}/\${id}`);\n }"
|
||||
// }
|
||||
|
||||
// pub fn ts_client_set_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async set${name_pascal}(id: string, ${name_snake}: Partial<${name_pascal}>): Promise<${name_pascal}> {\n return this.restClient.put<${name_pascal}>(`/${root}/${name_snake}/\${id}`, ${name_snake});\n }"
|
||||
// }
|
||||
|
||||
// pub fn ts_client_delete_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async delete${name_pascal}(id: string): Promise<void> {\n return this.restClient.delete<void>(`/${root}/${name_snake}/\${id}`);\n }"
|
||||
// }
|
||||
|
||||
// pub fn ts_client_list_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async list${name_pascal}(): Promise<${name_pascal}[]> {\n return this.restClient.get<${name_pascal}[]>(`/${root}/${name_snake}`);\n }"
|
||||
// }
|
||||
|
||||
fn get_endpoint_root(root string) string {
|
||||
return if root == '' {
|
||||
''
|
||||
} else {
|
||||
'/${root.trim('/')}'
|
||||
}
|
||||
}
|
||||
|
||||
// // generates a Base Object's `create` method
|
||||
// pub fn ts_client_new_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async create${name_snake}(object: Omit<${name_pascal}, 'id'>): Promise<${name_pascal}> {
|
||||
// return this.restClient.post<${name_pascal}>('${root}/${name_snake}', board);
|
||||
// }"
|
||||
// }
|
||||
|
||||
// pub fn ts_client_get_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async get${name_pascal}(id: string): Promise<${name_pascal}> {\n return this.restClient.get<${name_pascal}>(`/${root}/${name_snake}/\${id}`);\n }"
|
||||
// }
|
||||
|
||||
// pub fn ts_client_set_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async set${name_pascal}(id: string, ${name_snake}: Partial<${name_pascal}>): Promise<${name_pascal}> {\n return this.restClient.put<${name_pascal}>(`/${root}/${name_snake}/\${id}`, ${name_snake});\n }"
|
||||
// }
|
||||
|
||||
// pub fn ts_client_delete_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async delete${name_pascal}(id: string): Promise<void> {\n return this.restClient.delete<void>(`/${root}/${name_snake}/\${id}`);\n }"
|
||||
// }
|
||||
|
||||
// pub fn ts_client_list_fn(object string, params TSClientFunctionParams) string {
|
||||
// name_snake := texttools.snake_case(object)
|
||||
// name_pascal := texttools.pascal_case(object)
|
||||
// root := get_endpoint_root(params.endpoint)
|
||||
|
||||
// return "async list${name_pascal}(): Promise<${name_pascal}[]> {\n return this.restClient.get<${name_pascal}[]>(`/${root}/${name_snake}`);\n }"
|
||||
// }
|
||||
|
||||
// // generates a function prototype given an `ActorMethod`
|
||||
// pub fn ts_client_fn_prototype(method ActorMethod) string {
|
||||
// name := texttools.pascal_case(method.name)
|
||||
// params := method.parameters
|
||||
// .map(content_descriptor_to_parameter(it) or {panic(err)})
|
||||
// .map(it.typescript())
|
||||
// .join(', ')
|
||||
|
||||
// return_type := content_descriptor_to_parameter(method.result) or {panic(err)}.typ.typescript()
|
||||
// return 'async ${name}(${params}): Promise<${return_type}> {}'
|
||||
// }
|
||||
205
libarchive/baobab/generator/_archive/client_typescript_test.v
Normal file
205
libarchive/baobab/generator/_archive/client_typescript_test.v
Normal file
@@ -0,0 +1,205 @@
|
||||
module generator
|
||||
|
||||
import x.json2 as json
|
||||
import arrays
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
|
||||
const specification = specification.ActorSpecification{
|
||||
name: 'Pet Store'
|
||||
description: 'A sample API for a pet store'
|
||||
structure: code.Struct{}
|
||||
interfaces: [.openapi]
|
||||
methods: [
|
||||
specification.ActorMethod{
|
||||
name: 'listPets'
|
||||
summary: 'List all pets'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example limit'
|
||||
description: 'Example Maximum number of pets to return'
|
||||
value: 10
|
||||
}),
|
||||
]
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: json.raw_decode('[
|
||||
{"id": 1, "name": "Fluffy", "tag": "dog"},
|
||||
{"id": 2, "name": "Whiskers", "tag": "cat"}
|
||||
]')!
|
||||
})
|
||||
}
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'limit'
|
||||
summary: 'Maximum number of pets to return'
|
||||
description: 'Maximum number of pets to return'
|
||||
required: false
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
...jsonschema.schema_u32
|
||||
example: 10
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pets'
|
||||
description: 'A paged array of pets'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
|
||||
id: 'pet'
|
||||
title: 'Pet'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/PetId'
|
||||
})
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: [
|
||||
'id',
|
||||
'name',
|
||||
]
|
||||
}))
|
||||
})
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 400
|
||||
message: 'Invalid request'
|
||||
},
|
||||
]
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'createPet'
|
||||
summary: 'Create a new pet'
|
||||
example: openrpc.ExamplePairing{
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: '[]'
|
||||
})
|
||||
}
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 400
|
||||
message: 'Invalid input'
|
||||
},
|
||||
]
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'getPet'
|
||||
summary: 'Get a pet by ID'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example petId'
|
||||
description: 'Example ID of the pet to retrieve'
|
||||
value: 1
|
||||
}),
|
||||
]
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: json.raw_decode('{"id": 1, "name": "Fluffy", "tag": "dog"}')!
|
||||
})
|
||||
}
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'petId'
|
||||
summary: 'ID of the pet to retrieve'
|
||||
description: 'ID of the pet to retrieve'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
...jsonschema.schema_u32
|
||||
format: 'uint32'
|
||||
example: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 404
|
||||
message: 'Pet not found'
|
||||
},
|
||||
]
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'deletePet'
|
||||
summary: 'Delete a pet by ID'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example petId'
|
||||
description: 'Example ID of the pet to delete'
|
||||
value: 1
|
||||
}),
|
||||
]
|
||||
}
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'petId'
|
||||
summary: 'ID of the pet to delete'
|
||||
description: 'ID of the pet to delete'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
...jsonschema.schema_u32
|
||||
example: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 404
|
||||
message: 'Pet not found'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
objects: [
|
||||
specification.BaseObject{
|
||||
schema: jsonschema.Schema{
|
||||
title: 'Pet'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': jsonschema.schema_u32
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['id', 'name']
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn test_typescript_client_folder() {
|
||||
client := typescript_client_folder(specification)
|
||||
}
|
||||
47
libarchive/baobab/generator/_archive/generate_objects.v
Normal file
47
libarchive/baobab/generator/_archive/generate_objects.v
Normal file
@@ -0,0 +1,47 @@
|
||||
module generator
|
||||
|
||||
// pub fn generate_object_code(actor Struct, object BaseObject) VFile {
|
||||
// obj_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// mut items := []CodeItem{}
|
||||
// items = [generate_new_method(actor, object), generate_get_method(actor, object),
|
||||
// generate_set_method(actor, object), generate_delete_method(actor, object),
|
||||
// generate_list_result_struct(actor, object), generate_list_method(actor, object)]
|
||||
|
||||
// items << generate_object_methods(actor, object)
|
||||
// mut file := code.new_file(
|
||||
// mod: texttools.name_fix(actor.name)
|
||||
// name: obj_name
|
||||
// imports: [
|
||||
// Import{
|
||||
// mod: object.structure.mod
|
||||
// types: [object_type]
|
||||
// },
|
||||
// Import{
|
||||
// mod: 'freeflowuniverse.herolib.baobab.backend'
|
||||
// types: ['FilterParams']
|
||||
// },
|
||||
// ]
|
||||
// items: items
|
||||
// )
|
||||
|
||||
// if object.structure.fields.any(it.attrs.any(it.name == 'index')) {
|
||||
// // can't filter without indices
|
||||
// filter_params := generate_filter_params(actor, object)
|
||||
// file.items << filter_params.map(CodeItem(it))
|
||||
// file.items << generate_filter_method(actor, object)
|
||||
// }
|
||||
|
||||
// return file
|
||||
// }
|
||||
|
||||
// pub fn (a Actor) generate_model_files() ![]VFile {
|
||||
// structs := a.objects.map(it.structure)
|
||||
// return a.objects.map(code.new_file(
|
||||
// mod: texttools.name_fix(a.name)
|
||||
// name: '${texttools.name_fix(it.structure.name)}_model'
|
||||
// // imports: [Import{mod:'freeflowuniverse.herolib.baobab.stage'}]
|
||||
// items: [it.structure]
|
||||
// ))
|
||||
// }
|
||||
406
libarchive/baobab/generator/_archive/write_object_methods.v
Normal file
406
libarchive/baobab/generator/_archive/write_object_methods.v
Normal file
@@ -0,0 +1,406 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.core.code { Param, Param, type_from_symbol }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
const id_param = Param{
|
||||
name: 'id'
|
||||
typ: type_from_symbol('u32')
|
||||
}
|
||||
|
||||
// pub fn generate_object_code(actor Struct, object BaseObject) VFile {
|
||||
// obj_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// mut items := []CodeItem{}
|
||||
// items = [generate_new_method(actor, object), generate_get_method(actor, object),
|
||||
// generate_set_method(actor, object), generate_delete_method(actor, object),
|
||||
// generate_list_result_struct(actor, object), generate_list_method(actor, object)]
|
||||
|
||||
// items << generate_object_methods(actor, object)
|
||||
// mut file := code.new_file(
|
||||
// mod: texttools.name_fix(actor.name)
|
||||
// name: obj_name
|
||||
// imports: [
|
||||
// Import{
|
||||
// mod: object.structure.mod
|
||||
// types: [object_type]
|
||||
// },
|
||||
// Import{
|
||||
// mod: 'freeflowuniverse.herolib.baobab.backend'
|
||||
// types: ['FilterParams']
|
||||
// },
|
||||
// ]
|
||||
// items: items
|
||||
// )
|
||||
|
||||
// if object.structure.fields.any(it.attrs.any(it.name == 'index')) {
|
||||
// // can't filter without indices
|
||||
// filter_params := generate_filter_params(actor, object)
|
||||
// file.items << filter_params.map(CodeItem(it))
|
||||
// file.items << generate_filter_method(actor, object)
|
||||
// }
|
||||
|
||||
// return file
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_get_method(actor Struct, object BaseObject) Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// get_method := Function{
|
||||
// name: 'get_${object_name}'
|
||||
// description: 'gets the ${object_name} with the given object id'
|
||||
// receiver: Param{
|
||||
// mutable: true
|
||||
// name: 'actor'
|
||||
// typ: type_from_symbol(actor.name)
|
||||
// }
|
||||
// }
|
||||
// params: [generator.id_param]
|
||||
// result: Param{
|
||||
// typ: type_from_symbol(object.structure.name)
|
||||
// is_result: true
|
||||
// }
|
||||
// body: 'return actor.backend.get[${object_type}](id)!'
|
||||
// }
|
||||
// return get_method
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_set_method(actor Struct, object BaseObject) Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// param_getters := generate_param_getters(
|
||||
// structure: object.structure
|
||||
// prefix: ''
|
||||
// only_mutable: true
|
||||
// )
|
||||
// body := 'actor.backend.set[${object_type}](${object_name})!'
|
||||
// get_method := Function{
|
||||
// name: 'set_${object_name}'
|
||||
// description: 'updates the ${object.structure.name} with the given object id'
|
||||
// receiver: Param{
|
||||
// mutable: true
|
||||
// name: 'actor'
|
||||
// typ: type_from_symbol(actor.name)
|
||||
// }
|
||||
// }
|
||||
// params: [
|
||||
// Param{
|
||||
// name: object_name
|
||||
// typ: Type{
|
||||
// symbol: object_type
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// result: Param{
|
||||
// is_result: true
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// return get_method
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_delete_method(actor Struct, object BaseObject) Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// body := 'actor.backend.delete[${object_type}](id)!'
|
||||
// get_method := Function{
|
||||
// name: 'delete_${object_name}'
|
||||
// description: 'deletes the ${object.structure.name} with the given object id'
|
||||
// receiver: Param{
|
||||
// mutable: true
|
||||
// name: 'actor'
|
||||
// typ: Type{
|
||||
// symbol: actor.name
|
||||
// }
|
||||
// }
|
||||
// params: [generator.id_param]
|
||||
// result: Param{
|
||||
// is_result: true
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// return get_method
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_new_method(actor Struct, object BaseObject) Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// param_getters := generate_param_getters(
|
||||
// structure: object.structure
|
||||
// prefix: ''
|
||||
// only_mutable: false
|
||||
// )
|
||||
// body := 'return actor.backend.new[${object_type}](${object_name})!'
|
||||
// new_method := Function{
|
||||
// name: 'new_${object_name}'
|
||||
// description: 'news the ${object.structure.name} with the given object id'
|
||||
// receiver: Param{
|
||||
// name: 'actor'
|
||||
// typ: Type{
|
||||
// symbol: actor.name
|
||||
// }
|
||||
// mutable: true
|
||||
// }
|
||||
// params: [
|
||||
// Param{
|
||||
// name: object_name
|
||||
// typ: Type{
|
||||
// symbol: object_type
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// result: Param{
|
||||
// is_result: true
|
||||
// typ: Type{
|
||||
// symbol: 'u32'
|
||||
// }
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// return new_method
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_list_result_struct(actor Struct, object BaseObject) Struct {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
// return Struct{
|
||||
// name: '${object_type}List'
|
||||
// is_pub: true
|
||||
// fields: [
|
||||
// StructField{
|
||||
// name: 'items'
|
||||
// typ: Type{
|
||||
// symbol: '[]${object_type}'
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_list_method(actor Struct, object BaseObject) Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// list_struct := Struct{
|
||||
// name: '${object_type}List'
|
||||
// fields: [
|
||||
// StructField{
|
||||
// name: 'items'
|
||||
// typ: Type{
|
||||
// symbol: '[]${object_type}'
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
|
||||
// param_getters := generate_param_getters(
|
||||
// structure: object.structure
|
||||
// prefix: ''
|
||||
// only_mutable: false
|
||||
// )
|
||||
// body := 'return ${object_type}List{items:actor.backend.list[${object_type}]()!}'
|
||||
|
||||
// result_struct := generate_list_result_struct(actor, object)
|
||||
// mut result := Param{}
|
||||
// result.typ.symbol = result_struct.name
|
||||
// result.is_result = true
|
||||
// new_method := Function{
|
||||
// name: 'list_${object_name}'
|
||||
// description: 'lists all of the ${object_name} objects'
|
||||
// receiver: Param{
|
||||
// name: 'actor'
|
||||
// typ: Type{
|
||||
// symbol: actor.name
|
||||
// }
|
||||
// mutable: true
|
||||
// }
|
||||
// params: []
|
||||
// result: result
|
||||
// body: body
|
||||
// }
|
||||
// return new_method
|
||||
// }
|
||||
|
||||
// fn generate_filter_params(actor Struct, object BaseObject) []Struct {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// return [
|
||||
// Struct{
|
||||
// name: 'Filter${object_type}Params'
|
||||
// fields: [
|
||||
// StructField{
|
||||
// name: 'filter'
|
||||
// typ: Type{
|
||||
// symbol: '${object_type}Filter'
|
||||
// }
|
||||
// },
|
||||
// StructField{
|
||||
// name: 'params'
|
||||
// typ: Type{
|
||||
// symbol: 'FilterParams'
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
// Struct{
|
||||
// name: '${object_type}Filter'
|
||||
// fields: object.structure.fields.filter(it.attrs.any(it.name == 'index'))
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_filter_method(actor Struct, object BaseObject) Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// param_getters := generate_param_getters(
|
||||
// structure: object.structure
|
||||
// prefix: ''
|
||||
// only_mutable: false
|
||||
// )
|
||||
// params_type := 'Filter${object_type}Params'
|
||||
// body := 'return actor.backend.filter[${object_type}, ${object_type}Filter](filter.filter, filter.params)!'
|
||||
// return Function{
|
||||
// name: 'filter_${object_name}'
|
||||
// description: 'lists all of the ${object_name} objects'
|
||||
// receiver: Param{
|
||||
// name: 'actor'
|
||||
// typ: Type{
|
||||
// symbol: actor.name
|
||||
// }
|
||||
// mutable: true
|
||||
// }
|
||||
// params: [
|
||||
// Param{
|
||||
// name: 'filter'
|
||||
// typ: Type{
|
||||
// symbol: params_type
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// result: Param{
|
||||
// typ: Type{
|
||||
// symbol: '[]${object_type}'
|
||||
// }
|
||||
// is_result: true
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// }
|
||||
|
||||
// // // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// // fn generate_object_methods(actor Struct, object BaseObject) []Function {
|
||||
// // object_name := texttools.snake_case(object.structure.name)
|
||||
// // object_type := object.structure.name
|
||||
|
||||
// // mut funcs := []Function{}
|
||||
// // for method in object.methods {
|
||||
// // mut params := [Param{
|
||||
// // name: 'id'
|
||||
// // typ: Type{
|
||||
// // symbol: 'u32'
|
||||
// // }
|
||||
// // }]
|
||||
// // params << method.params
|
||||
// // funcs << Function{
|
||||
// // name: method.name
|
||||
// // description: method.description
|
||||
// // receiver: Param{
|
||||
// // name: 'actor'
|
||||
// // typ: Type{
|
||||
// // symbol: actor.name
|
||||
// // }
|
||||
// // mutable: true
|
||||
// // }
|
||||
// // params: params
|
||||
// // result: method.result
|
||||
// // body: 'obj := actor.backend.get[${method.receiver.typ.symbol}](id)!
|
||||
// // obj.${method.name}(${method.params.map(it.name).join(',')})
|
||||
// // actor.backend.set[${method.receiver.typ.symbol}](obj)!
|
||||
// // '
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // return funcs
|
||||
// // }
|
||||
|
||||
// @[params]
|
||||
// struct GenerateParamGetters {
|
||||
// structure Struct
|
||||
// prefix string
|
||||
// only_mutable bool // if true generates param.get methods for only mutable struct fields. Used for updating.
|
||||
// }
|
||||
|
||||
// fn generate_param_getters(params GenerateParamGetters) []string {
|
||||
// mut param_getters := []string{}
|
||||
// fields := if params.only_mutable {
|
||||
// params.structure.fields.filter(it.is_mut && it.is_pub)
|
||||
// } else {
|
||||
// params.structure.fields.filter(it.is_pub)
|
||||
// }
|
||||
// for field in fields {
|
||||
// if field.typ.symbol.starts_with_capital() {
|
||||
// subgetters := generate_param_getters(GenerateParamGetters{
|
||||
// ...params
|
||||
// structure: field.structure
|
||||
// prefix: '${field.name}_'
|
||||
// })
|
||||
// // name of the tested object, used for param declaration
|
||||
// // ex: fruits []Fruit becomes fruit_name
|
||||
// nested_name := field.structure.name.to_lower()
|
||||
// if field.typ.is_map {
|
||||
// param_getters.insert(0, '${nested_name}_key := params.get(\'${nested_name}_key\')!')
|
||||
// param_getters << '${field.name}: {${nested_name}_key: ${field.structure.name}}{'
|
||||
// } else if field.typ.is_array {
|
||||
// param_getters << '${field.name}: [${field.structure.name}{'
|
||||
// } else {
|
||||
// param_getters << '${field.name}: ${field.structure.name}{'
|
||||
// }
|
||||
// param_getters << subgetters
|
||||
// param_getters << if field.typ.is_array { '}]' } else { '}' }
|
||||
// continue
|
||||
// }
|
||||
|
||||
// mut get_method := '${field.name}: params.get'
|
||||
// if field.typ.symbol != 'string' {
|
||||
// // TODO: check if params method actually exists
|
||||
// 'get_${field.typ.symbol}'
|
||||
// }
|
||||
|
||||
// if field.default != '' {
|
||||
// get_method += '_default'
|
||||
// }
|
||||
|
||||
// get_method = get_method + "('${params.prefix}${field.name}')!"
|
||||
// param_getters << get_method
|
||||
// }
|
||||
// return param_getters
|
||||
// }
|
||||
|
||||
// @[params]
|
||||
// struct GetChildField {
|
||||
// parent Struct @[required]
|
||||
// child Struct @[required]
|
||||
// }
|
||||
|
||||
// fn get_child_field(params GetChildField) StructField {
|
||||
// fields := params.parent.fields.filter(it.typ.symbol == 'map[string]&${params.child.name}')
|
||||
// if fields.len != 1 {
|
||||
// panic('this should never happen')
|
||||
// }
|
||||
// return fields[0]
|
||||
// }
|
||||
168
libarchive/baobab/generator/_archive/write_object_tests.v
Normal file
168
libarchive/baobab/generator/_archive/write_object_tests.v
Normal file
@@ -0,0 +1,168 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import rand
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// pub fn generate_object_test_code(actor Struct, object BaseObject) !VFile {
|
||||
// consts := CustomCode{"const db_dir = '\${os.home_dir()}/hero/db'
|
||||
// const actor_name = '${actor.name}_test_actor'"}
|
||||
|
||||
// clean_code := 'mut actor := get(name: actor_name)!\nactor.backend.reset()!'
|
||||
|
||||
// testsuite_begin := Function{
|
||||
// name: 'testsuite_begin'
|
||||
// body: clean_code
|
||||
// }
|
||||
|
||||
// testsuite_end := Function{
|
||||
// name: 'testsuite_end'
|
||||
// body: clean_code
|
||||
// }
|
||||
|
||||
// actor_name := texttools.name_fix(actor.name)
|
||||
// object_name := texttools.snake_case(object.schema.name)
|
||||
// object_type := object.structure.name
|
||||
// // TODO: support modules outside of crystal
|
||||
|
||||
// mut file := VFile{
|
||||
// name: '${object_name}_test'
|
||||
// mod: texttools.name_fix(actor_name)
|
||||
// imports: [
|
||||
// Import{
|
||||
// mod: 'os'
|
||||
// },
|
||||
// Import{
|
||||
// mod: '${object.structure.mod}'
|
||||
// types: [object_type]
|
||||
// },
|
||||
// ]
|
||||
// items: [
|
||||
// consts,
|
||||
// testsuite_begin,
|
||||
// testsuite_end,
|
||||
// generate_new_method_test(actor, object)!,
|
||||
// generate_get_method_test(actor, object)!,
|
||||
// ]
|
||||
// }
|
||||
|
||||
// if object.structure.fields.any(it.attrs.any(it.name == 'index')) {
|
||||
// // can't filter without indices
|
||||
// file.items << generate_filter_test(actor, object)!
|
||||
// }
|
||||
|
||||
// return file
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_new_method_test(actor Struct, object BaseObject) !Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// required_fields := object.structure.fields.filter(it.attrs.any(it.name == 'required'))
|
||||
// mut fields := []string{}
|
||||
// for field in required_fields {
|
||||
// mut field_decl := '${field.name}: ${get_mock_value(field.typ.symbol())!}'
|
||||
// fields << field_decl
|
||||
// }
|
||||
|
||||
// body := 'mut actor := get(name: actor_name)!
|
||||
// mut ${object_name}_id := actor.new_${object_name}(${object_type}{${fields.join(',')}})!
|
||||
// assert ${object_name}_id == 1
|
||||
|
||||
// ${object_name}_id = actor.new_${object_name}(${object_type}{${fields.join(',')}})!
|
||||
// assert ${object_name}_id == 2'
|
||||
// return Function{
|
||||
// name: 'test_new_${object_name}'
|
||||
// description: 'news the ${object_type} with the given object id'
|
||||
// result: code.Param{
|
||||
// is_result: true
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_get_method_test(actor Struct, object BaseObject) !Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// required_fields := object.structure.fields.filter(it.attrs.any(it.name == 'required'))
|
||||
// mut fields := []string{}
|
||||
// for field in required_fields {
|
||||
// mut field_decl := '${field.name}: ${get_mock_value(field.typ.symbol())!}'
|
||||
// fields << field_decl
|
||||
// }
|
||||
|
||||
// body := 'mut actor := get(name: actor_name)!
|
||||
// mut ${object_name} := ${object_type}{${fields.join(',')}}
|
||||
// ${object_name}.id = actor.new_${object_name}(${object_name})!
|
||||
// assert ${object_name} == actor.get_${object_name}(${object_name}.id)!'
|
||||
// return Function{
|
||||
// name: 'test_get_${object_name}'
|
||||
// description: 'news the ${object_type} with the given object id'
|
||||
// result: code.Param{
|
||||
// is_result: true
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// fn generate_filter_test(actor Struct, object BaseObject) !Function {
|
||||
// object_name := texttools.snake_case(object.structure.name)
|
||||
// object_type := object.structure.name
|
||||
|
||||
// index_fields := object.structure.fields.filter(it.attrs.any(it.name == 'index'))
|
||||
// if index_fields.len == 0 {
|
||||
// return error('Cannot generate filter method test for object without any index fields')
|
||||
// }
|
||||
|
||||
// mut index_tests := []string{}
|
||||
// for i, field in index_fields {
|
||||
// val := get_mock_value(field.typ.symbol())!
|
||||
// index_field := '${field.name}: ${val}' // index field assignment line
|
||||
// mut fields := [index_field]
|
||||
// fields << get_required_fields(object.structure)!
|
||||
// index_tests << '${object_name}_id${i} := actor.new_${object_name}(${object_type}{${fields.join(',')}})!
|
||||
// ${object_name}_list${i} := actor.filter_${object_name}(
|
||||
// filter: ${object_type}Filter{${index_field}}
|
||||
// )!
|
||||
// assert ${object_name}_list${i}.len == 1
|
||||
// assert ${object_name}_list${i}[0].${field.name} == ${val}
|
||||
// '
|
||||
// }
|
||||
|
||||
// body := 'mut actor := get(name: actor_name)!
|
||||
// \n${index_tests.join('\n\n')}'
|
||||
|
||||
// return Function{
|
||||
// name: 'test_filter_${object_name}'
|
||||
// description: 'news the ${object_type} with the given object id'
|
||||
// result: code.Param{
|
||||
// is_result: true
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn get_required_fields(s Struct) ![]string {
|
||||
// required_fields := s.fields.filter(it.attrs.any(it.name == 'required'))
|
||||
// mut fields := []string{}
|
||||
// for field in required_fields {
|
||||
// fields << '${field.name}: ${get_mock_value(field.typ.symbol())!}'
|
||||
// }
|
||||
// return fields
|
||||
// }
|
||||
|
||||
// fn get_mock_value(typ string) !string {
|
||||
// if typ == 'string' {
|
||||
// return "'mock_string_${rand.string(3)}'"
|
||||
// } else if typ == 'int' || typ == 'u32' {
|
||||
// return '42'
|
||||
// } else {
|
||||
// return error('mock values for types other than strings and numbers are not yet supported')
|
||||
// }
|
||||
// }
|
||||
179
libarchive/baobab/generator/generate_act.v
Normal file
179
libarchive/baobab/generator/generate_act.v
Normal file
@@ -0,0 +1,179 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, CustomCode, Function, Import, Object, Param, Result, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, Example }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen { schemaref_to_type }
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
|
||||
|
||||
fn generate_handle_file(spec ActorSpecification) !VFile {
|
||||
mut items := []CodeItem{}
|
||||
items << CustomCode{generate_handle_function(spec)}
|
||||
for method in spec.methods {
|
||||
items << generate_method_handle(spec.name, method)!
|
||||
}
|
||||
return VFile{
|
||||
name: 'act'
|
||||
imports: [
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.baobab.stage'
|
||||
types: ['Action']
|
||||
},
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.core.texttools'
|
||||
},
|
||||
Import{
|
||||
mod: 'x.json2 as json'
|
||||
},
|
||||
]
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_handle_function(spec ActorSpecification) string {
|
||||
actor_name_pascal := texttools.pascal_case(spec.name)
|
||||
mut operation_handlers := []string{}
|
||||
mut routes := []string{}
|
||||
|
||||
// Iterate over OpenAPI paths and operations
|
||||
for method in spec.methods {
|
||||
operation_id := method.name
|
||||
params := method.parameters.map(it.name).join(', ')
|
||||
|
||||
// Generate route case
|
||||
route := generate_route_case(operation_id, 'handle_${operation_id}')
|
||||
routes << route
|
||||
}
|
||||
|
||||
// Combine the generated handlers and main router into a single file
|
||||
return [
|
||||
'// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY',
|
||||
'',
|
||||
'pub fn (mut actor ${actor_name_pascal}Actor) act(action Action) !Action {',
|
||||
' return match texttools.snake_case(action.name) {',
|
||||
routes.join('\n'),
|
||||
' else {',
|
||||
' return error("Unknown operation: \${action.name}")',
|
||||
' }',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
pub fn generate_method_handle(actor_name string, method ActorMethod) !Function {
|
||||
actor_name_pascal := texttools.pascal_case(actor_name)
|
||||
name_fixed := texttools.snake_case(method.name)
|
||||
mut body := ''
|
||||
if method.parameters.len == 1 {
|
||||
param := method.parameters[0]
|
||||
param_name := texttools.snake_case(param.name)
|
||||
decode_stmt := generate_decode_stmt('action.params', param)!
|
||||
body += '${param_name} := ${decode_stmt}\n'
|
||||
}
|
||||
if method.parameters.len > 1 {
|
||||
body += 'params_arr := json.raw_decode(action.params)!.arr()\n'
|
||||
for i, param in method.parameters {
|
||||
param_name := texttools.snake_case(param.name)
|
||||
decode_stmt := generate_decode_stmt('params_arr[${i}].str()', param)!
|
||||
body += '${param_name} := ${decode_stmt}'
|
||||
}
|
||||
}
|
||||
call_stmt := generate_call_stmt(actor_name, method)!
|
||||
body += '${call_stmt}\n'
|
||||
body += '${generate_return_stmt(method)!}\n'
|
||||
return Function{
|
||||
name: 'handle_${name_fixed}'
|
||||
description: '// Handler for ${name_fixed}\n'
|
||||
receiver: Param{
|
||||
name: 'actor'
|
||||
mutable: true
|
||||
typ: Object{'${actor_name_pascal}Actor'}
|
||||
}
|
||||
params: [Param{
|
||||
name: 'action'
|
||||
typ: Object{'Action'}
|
||||
}]
|
||||
result: Param{
|
||||
typ: Result{Object{'Action'}}
|
||||
}
|
||||
body: body
|
||||
}
|
||||
}
|
||||
|
||||
fn method_is_void(method ActorMethod) !bool {
|
||||
return schemaref_to_type(method.result.schema).vgen().trim_space() == ''
|
||||
}
|
||||
|
||||
pub fn generate_example_method_handle(actor_name string, method ActorMethod) !Function {
|
||||
actor_name_pascal := texttools.pascal_case(actor_name)
|
||||
name_fixed := texttools.snake_case(method.name)
|
||||
body := if !method_is_void(method)! {
|
||||
if method.example.result is Example {
|
||||
'return Action{...action, result: json.encode(\'${method.example.result.value}\')}'
|
||||
} else {
|
||||
'return action'
|
||||
}
|
||||
} else {
|
||||
'return action'
|
||||
}
|
||||
return Function{
|
||||
name: 'handle_${name_fixed}_example'
|
||||
description: '// Handler for ${name_fixed}\n'
|
||||
receiver: Param{
|
||||
name: 'actor'
|
||||
mutable: true
|
||||
typ: Object{'${actor_name_pascal}Actor'}
|
||||
}
|
||||
params: [Param{
|
||||
name: 'action'
|
||||
typ: Object{'Action'}
|
||||
}]
|
||||
result: Param{
|
||||
typ: Result{Object{'Action'}}
|
||||
}
|
||||
body: body
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_call_stmt(name string, method ActorMethod) !string {
|
||||
mut call_stmt := if schemaref_to_type(method.result.schema).vgen().trim_space() != '' {
|
||||
'${texttools.snake_case(method.result.name)} := '
|
||||
} else {
|
||||
''
|
||||
}
|
||||
method_name := texttools.snake_case(method.name)
|
||||
snake_name := texttools.snake_case(name)
|
||||
|
||||
param_names := method.parameters.map(texttools.snake_case(it.name))
|
||||
call_stmt += 'actor.${snake_name}.${method_name}(${param_names.join(', ')})!'
|
||||
return call_stmt
|
||||
}
|
||||
|
||||
fn generate_return_stmt(method ActorMethod) !string {
|
||||
if schemaref_to_type(method.result.schema).vgen().trim_space() != '' {
|
||||
return 'return Action{...action, result: json.encode(${texttools.snake_case(method.result.name)})}'
|
||||
}
|
||||
return 'return action'
|
||||
}
|
||||
|
||||
// Helper function to generate a case block for the main router
|
||||
fn generate_route_case(case string, handler_name string) string {
|
||||
name_fixed := texttools.snake_case(handler_name)
|
||||
return "'${texttools.snake_case(case)}' {actor.${name_fixed}(action)}"
|
||||
}
|
||||
|
||||
// generates decode statement for variable with given name
|
||||
fn generate_decode_stmt(name string, param ContentDescriptor) !string {
|
||||
param_type := schemaref_to_type(param.schema)
|
||||
if param_type is Object {
|
||||
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})!'
|
||||
} else if param_type is code.Array {
|
||||
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})'
|
||||
}
|
||||
param_symbol := param_type.vgen()
|
||||
return if param_symbol == 'string' {
|
||||
'${name}.str()'
|
||||
} else {
|
||||
'${name}.${param_type.vgen()}()'
|
||||
}
|
||||
}
|
||||
82
libarchive/baobab/generator/generate_actor_folder.v
Normal file
82
libarchive/baobab/generator/generate_actor_folder.v
Normal file
@@ -0,0 +1,82 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { File, Folder, IFile, IFolder }
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorInterface, ActorSpecification }
|
||||
import json
|
||||
|
||||
@[params]
|
||||
pub struct Params {
|
||||
pub:
|
||||
interfaces []ActorInterface // the interfaces to be supported
|
||||
}
|
||||
|
||||
pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
|
||||
mut files := []IFile{}
|
||||
mut folders := []IFolder{}
|
||||
|
||||
files = [generate_readme_file(spec)!]
|
||||
|
||||
mut docs_files := []IFile{}
|
||||
mut spec_files := []IFile{}
|
||||
|
||||
// generate code files for supported interfaces
|
||||
for iface in params.interfaces {
|
||||
match iface {
|
||||
.openrpc {
|
||||
// convert actor spec to openrpc spec
|
||||
openrpc_spec := spec.to_openrpc()
|
||||
spec_files << generate_openrpc_file(openrpc_spec)!
|
||||
}
|
||||
.openapi {
|
||||
// convert actor spec to openrpc spec
|
||||
openapi_spec_raw := spec.to_openapi()
|
||||
spec_files << generate_openapi_file(openapi_spec_raw)!
|
||||
|
||||
openapi_spec := openapi.process(openapi_spec_raw)!
|
||||
folders << generate_openapi_ts_client(openapi_spec)!
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
specs_folder := Folder{
|
||||
name: 'specs'
|
||||
files: spec_files
|
||||
}
|
||||
|
||||
// folder with docs
|
||||
folders << Folder{
|
||||
name: 'docs'
|
||||
files: docs_files
|
||||
folders: [specs_folder]
|
||||
}
|
||||
|
||||
folders << generate_scripts_folder(spec.name, false)
|
||||
folders << generate_examples_folder()!
|
||||
|
||||
// create module with code files and docs folder
|
||||
name_fixed := texttools.snake_case(spec.name)
|
||||
|
||||
return Folder{
|
||||
name: '${name_fixed}'
|
||||
files: files
|
||||
folders: folders
|
||||
modules: [generate_actor_module(spec, params)!]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_readme_file(spec ActorSpecification) !File {
|
||||
return File{
|
||||
name: 'README'
|
||||
extension: 'md'
|
||||
content: '# ${spec.name}\n${spec.description}'
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_examples_folder() !Folder {
|
||||
return Folder{
|
||||
name: 'examples'
|
||||
}
|
||||
}
|
||||
118
libarchive/baobab/generator/generate_actor_source.v
Normal file
118
libarchive/baobab/generator/generate_actor_source.v
Normal file
@@ -0,0 +1,118 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CustomCode, IFile, IFolder, Module, VFile }
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorInterface, ActorSpecification }
|
||||
import json
|
||||
|
||||
pub fn generate_module_from_openapi(openapi_path string) !string {
|
||||
// the actor specification obtained from the OpenRPC Specification
|
||||
openapi_spec := openapi.new(path: openapi_path)!
|
||||
actor_spec := specification.from_openapi(openapi_spec)!
|
||||
|
||||
actor_module := generate_actor_module(actor_spec,
|
||||
interfaces: [.openapi, .http]
|
||||
)!
|
||||
|
||||
return actor_module.write_str()!
|
||||
}
|
||||
|
||||
pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
mut files := []IFile{}
|
||||
mut folders := []IFolder{}
|
||||
|
||||
files = [
|
||||
generate_actor_file(spec)!,
|
||||
generate_actor_test_file(spec)!,
|
||||
generate_specs_file(spec.name, params.interfaces)!,
|
||||
generate_handle_file(spec)!,
|
||||
generate_methods_file(spec)!,
|
||||
generate_methods_interface_file(spec)!,
|
||||
generate_methods_example_file(spec)!,
|
||||
generate_client_file(spec)!,
|
||||
generate_model_file(spec)!,
|
||||
]
|
||||
|
||||
// generate code files for supported interfaces
|
||||
for iface in params.interfaces {
|
||||
match iface {
|
||||
.openrpc {
|
||||
// convert actor spec to openrpc spec
|
||||
openrpc_spec := spec.to_openrpc()
|
||||
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
}
|
||||
.openapi {
|
||||
// convert actor spec to openrpc spec
|
||||
openapi_spec_raw := spec.to_openapi()
|
||||
openapi_spec := openapi.process(openapi_spec_raw)!
|
||||
// generate openrpc code files
|
||||
iface_file, iface_test_file := generate_openapi_interface_files(params.interfaces)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
}
|
||||
.http {
|
||||
// interfaces that have http controllers
|
||||
controllers := params.interfaces.filter(it == .openrpc || it == .openapi)
|
||||
// generate openrpc code files
|
||||
iface_file, iface_test_file := generate_http_interface_files(controllers)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
}
|
||||
.command {
|
||||
files << generate_command_file(spec)!
|
||||
}
|
||||
else {
|
||||
return error('unsupported interface ${iface}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create module with code files and docs folder
|
||||
name_fixed := texttools.snake_case(spec.name)
|
||||
return code.new_module(
|
||||
name: '${name_fixed}'
|
||||
description: spec.description
|
||||
files: files
|
||||
folders: folders
|
||||
in_src: true
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_actor_file(spec ActorSpecification) !VFile {
|
||||
dollar := '$'
|
||||
version := spec.version
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
name_pascal := texttools.pascal_case(spec.name)
|
||||
actor_code := $tmpl('./templates/actor.v.template')
|
||||
return VFile{
|
||||
name: 'actor'
|
||||
items: [CustomCode{actor_code}]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_actor_test_file(spec ActorSpecification) !VFile {
|
||||
dollar := '$'
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.pascal_case(spec.name)
|
||||
actor_test_code := $tmpl('./templates/actor_test.v.template')
|
||||
return VFile{
|
||||
name: 'actor_test'
|
||||
items: [CustomCode{actor_test_code}]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_specs_file(name string, interfaces []ActorInterface) !VFile {
|
||||
support_openrpc := ActorInterface.openrpc in interfaces
|
||||
support_openapi := ActorInterface.openapi in interfaces
|
||||
dollar := '$'
|
||||
actor_name_snake := texttools.snake_case(name)
|
||||
actor_name_pascal := texttools.pascal_case(name)
|
||||
actor_code := $tmpl('./templates/specifications.v.template')
|
||||
return VFile{
|
||||
name: 'specifications'
|
||||
items: [CustomCode{actor_code}]
|
||||
}
|
||||
}
|
||||
276
libarchive/baobab/generator/generate_actor_test.v
Normal file
276
libarchive/baobab/generator/generate_actor_test.v
Normal file
@@ -0,0 +1,276 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import os
|
||||
import x.json2 as json
|
||||
|
||||
const actor_spec = specification.ActorSpecification{
|
||||
name: 'Pet Store'
|
||||
description: 'A sample API for a pet store'
|
||||
structure: code.Struct{}
|
||||
interfaces: [.openapi]
|
||||
methods: [
|
||||
specification.ActorMethod{
|
||||
name: 'listPets'
|
||||
summary: 'List all pets'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example limit'
|
||||
description: 'Example Maximum number of pets to return'
|
||||
value: 10
|
||||
}),
|
||||
]
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: json.raw_decode('[
|
||||
{"id": 1, "name": "Fluffy", "tag": "dog"},
|
||||
{"id": 2, "name": "Whiskers", "tag": "cat"}
|
||||
]')!
|
||||
})
|
||||
}
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'limit'
|
||||
summary: 'Maximum number of pets to return'
|
||||
description: 'Maximum number of pets to return'
|
||||
required: false
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
...jsonschema.schema_u32
|
||||
example: 10
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pets'
|
||||
description: 'A paged array of pets'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
|
||||
id: 'pet'
|
||||
title: 'Pet'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/PetId'
|
||||
})
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: [
|
||||
'id',
|
||||
'name',
|
||||
]
|
||||
}))
|
||||
})
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 400
|
||||
message: 'Invalid request'
|
||||
},
|
||||
]
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'newPet'
|
||||
summary: 'Create a new pet'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
id: 'pet'
|
||||
title: 'Pet'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/PetId'
|
||||
})
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['id', 'name']
|
||||
})
|
||||
},
|
||||
]
|
||||
example: openrpc.ExamplePairing{
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: '[]'
|
||||
})
|
||||
}
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'petId'
|
||||
summary: 'ID of the created pet'
|
||||
description: 'ID of the created pet'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
...jsonschema.schema_u32
|
||||
example: 1
|
||||
})
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 400
|
||||
message: 'Invalid input'
|
||||
},
|
||||
]
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'getPet'
|
||||
summary: 'Get a pet by ID'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example petId'
|
||||
description: 'Example ID of the pet to retrieve'
|
||||
value: 1
|
||||
}),
|
||||
]
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: json.raw_decode('{"id": 1, "name": "Fluffy", "tag": "dog"}')!
|
||||
})
|
||||
}
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'petId'
|
||||
summary: 'ID of the pet to retrieve'
|
||||
description: 'ID of the pet to retrieve'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
...jsonschema.schema_u32
|
||||
format: 'uint32'
|
||||
example: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 404
|
||||
message: 'Pet not found'
|
||||
},
|
||||
]
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'deletePet'
|
||||
summary: 'Delete a pet by ID'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example petId'
|
||||
description: 'Example ID of the pet to delete'
|
||||
value: 1
|
||||
}),
|
||||
]
|
||||
}
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'petId'
|
||||
summary: 'ID of the pet to delete'
|
||||
description: 'ID of the pet to delete'
|
||||
required: true
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
...jsonschema.schema_u32
|
||||
example: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
}
|
||||
errors: [
|
||||
openrpc.ErrorSpec{
|
||||
code: 404
|
||||
message: 'Pet not found'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
objects: [
|
||||
specification.BaseObject{
|
||||
schema: jsonschema.Schema{
|
||||
title: 'Pet'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': jsonschema.schema_u32
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['id', 'name']
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const destination = '${os.dir(@FILE)}/testdata'
|
||||
|
||||
fn test_generate_plain_actor_module() {
|
||||
// plain actor module without interfaces
|
||||
actor_module := generate_actor_module(actor_spec)!
|
||||
actor_module.write(destination,
|
||||
format: true
|
||||
overwrite: true
|
||||
test: true
|
||||
)!
|
||||
}
|
||||
|
||||
fn test_generate_actor_module_with_openrpc_interface() {
|
||||
// plain actor module without interfaces
|
||||
actor_module := generate_actor_module(actor_spec, interfaces: [.openrpc])!
|
||||
actor_module.write(destination,
|
||||
format: true
|
||||
overwrite: true
|
||||
test: true
|
||||
)!
|
||||
}
|
||||
|
||||
fn test_generate_actor_module_with_openapi_interface() {
|
||||
// plain actor module without interfaces
|
||||
actor_module := generate_actor_module(actor_spec,
|
||||
interfaces: [.openapi]
|
||||
)!
|
||||
actor_module.write(destination,
|
||||
format: true
|
||||
overwrite: true
|
||||
test: true
|
||||
)!
|
||||
}
|
||||
|
||||
fn test_generate_actor_module_with_all_interfaces() {
|
||||
// plain actor module without interfaces
|
||||
actor_module := generate_actor_module(actor_spec,
|
||||
interfaces: [.openapi, .openrpc, .http]
|
||||
)!
|
||||
actor_module.write(destination,
|
||||
format: true
|
||||
overwrite: true
|
||||
test: true
|
||||
)!
|
||||
}
|
||||
136
libarchive/baobab/generator/generate_clients.v
Normal file
136
libarchive/baobab/generator/generate_clients.v
Normal file
@@ -0,0 +1,136 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, CustomCode, Function, Import, Param, Result, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen { schemaref_to_type }
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen { content_descriptor_to_parameter }
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
|
||||
|
||||
pub fn generate_client_file(spec ActorSpecification) !VFile {
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.pascal_case(spec.name)
|
||||
|
||||
mut items := []CodeItem{}
|
||||
|
||||
items << CustomCode{'
|
||||
pub struct Client {
|
||||
stage.Client
|
||||
}
|
||||
|
||||
fn new_client(config stage.ActorConfig) !Client {
|
||||
return Client {
|
||||
Client: stage.new_client(config)!
|
||||
}
|
||||
}'}
|
||||
|
||||
for method in spec.methods {
|
||||
items << generate_client_method(method)!
|
||||
}
|
||||
|
||||
return VFile{
|
||||
imports: [
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.baobab.stage'
|
||||
},
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.core.redisclient'
|
||||
},
|
||||
Import{
|
||||
mod: 'x.json2 as json'
|
||||
types: ['Any']
|
||||
},
|
||||
]
|
||||
name: 'client_actor'
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_example_client_file(spec ActorSpecification) !VFile {
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.pascal_case(spec.name)
|
||||
|
||||
mut items := []CodeItem{}
|
||||
|
||||
items << CustomCode{"
|
||||
pub struct Client {
|
||||
stage.Client
|
||||
}
|
||||
|
||||
fn new_client() !Client {
|
||||
mut redis := redisclient.new('localhost:6379')!
|
||||
mut rpc_q := redis.rpc_get('actor_example_\${name}')
|
||||
return Client{
|
||||
rpc: rpc_q
|
||||
}
|
||||
}"}
|
||||
|
||||
for method in spec.methods {
|
||||
items << generate_client_method(method)!
|
||||
}
|
||||
|
||||
return VFile{
|
||||
imports: [
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.baobab.stage'
|
||||
},
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.core.redisclient'
|
||||
},
|
||||
Import{
|
||||
mod: 'x.json2 as json'
|
||||
types: ['Any']
|
||||
},
|
||||
]
|
||||
name: 'client'
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_client_method(method ActorMethod) !Function {
|
||||
name_fixed := texttools.snake_case(method.name)
|
||||
|
||||
call_params := if method.parameters.len > 0 {
|
||||
method.parameters.map(texttools.snake_case(it.name)).map('Any(${it}.str())').join(', ')
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
params_stmt := if method.parameters.len == 0 {
|
||||
''
|
||||
} else if method.parameters.len == 1 {
|
||||
'params := json.encode(${texttools.snake_case(method.parameters[0].name)})'
|
||||
} else {
|
||||
'mut params_arr := []Any{}
|
||||
params_arr = [${call_params}]
|
||||
params := json.encode(params_arr.str())
|
||||
'
|
||||
}
|
||||
|
||||
mut client_call_stmt := "action := client.call_to_action(
|
||||
name: '${name_fixed}'"
|
||||
|
||||
if params_stmt != '' {
|
||||
client_call_stmt += 'params: params'
|
||||
}
|
||||
client_call_stmt += ')!'
|
||||
|
||||
result_type := schemaref_to_type(method.result.schema).vgen().trim_space()
|
||||
result_stmt := if result_type == '' {
|
||||
''
|
||||
} else {
|
||||
'return json.decode[${result_type}](action.result)!'
|
||||
}
|
||||
result_param := content_descriptor_to_parameter(method.result)!
|
||||
return Function{
|
||||
receiver: code.new_param(v: 'mut client Client')!
|
||||
result: Param{
|
||||
...result_param
|
||||
typ: Result{result_param.typ}
|
||||
}
|
||||
name: name_fixed
|
||||
body: '${params_stmt}\n${client_call_stmt}\n${result_stmt}'
|
||||
summary: method.summary
|
||||
description: method.description
|
||||
params: method.parameters.map(content_descriptor_to_parameter(it)!)
|
||||
}
|
||||
}
|
||||
78
libarchive/baobab/generator/generate_command.v
Normal file
78
libarchive/baobab/generator/generate_command.v
Normal file
@@ -0,0 +1,78 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, CustomCode, Import, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
|
||||
|
||||
pub fn generate_command_file(spec ActorSpecification) !VFile {
|
||||
mut items := []CodeItem{}
|
||||
items << CustomCode{generate_cmd_function(spec)}
|
||||
for i in spec.methods {
|
||||
items << CustomCode{generate_method_cmd_function(spec.name, i)}
|
||||
}
|
||||
return VFile{
|
||||
name: 'command'
|
||||
imports: [
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.ui.console'
|
||||
},
|
||||
Import{
|
||||
mod: 'cli'
|
||||
types: ['Command', 'Flag']
|
||||
},
|
||||
]
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_cmd_function(spec ActorSpecification) string {
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
mut cmd_function := "
|
||||
pub fn cmd() Command {
|
||||
mut cmd := Command{
|
||||
name: '${actor_name_snake}'
|
||||
usage: ''
|
||||
description: '${spec.description}'
|
||||
}
|
||||
"
|
||||
|
||||
mut method_cmds := []string{}
|
||||
for method in spec.methods {
|
||||
method_cmds << generate_method_cmd(method)
|
||||
}
|
||||
|
||||
cmd_function += '${method_cmds.join_lines()}}'
|
||||
|
||||
return cmd_function
|
||||
}
|
||||
|
||||
pub fn generate_method_cmd(method ActorMethod) string {
|
||||
method_name_snake := texttools.snake_case(method.name)
|
||||
return "
|
||||
mut cmd_${method_name_snake} := Command{
|
||||
sort_flags: true
|
||||
name: '${method_name_snake}'
|
||||
execute: cmd_${method_name_snake}_execute
|
||||
description: '${method.description}'
|
||||
}
|
||||
"
|
||||
}
|
||||
|
||||
pub fn generate_method_cmd_function(actor_name string, method ActorMethod) string {
|
||||
mut operation_handlers := []string{}
|
||||
mut routes := []string{}
|
||||
|
||||
actor_name_snake := texttools.snake_case(actor_name)
|
||||
method_name_snake := texttools.snake_case(method.name)
|
||||
|
||||
method_call := if method.result.name == '' {
|
||||
'${actor_name_snake}.${method_name_snake}()!'
|
||||
} else {
|
||||
'result := ${actor_name_snake}.${method_name_snake}()!'
|
||||
}
|
||||
return '
|
||||
fn cmd_${method_name_snake}_execute(cmd Command) ! {
|
||||
${method_call}
|
||||
}
|
||||
'
|
||||
}
|
||||
48
libarchive/baobab/generator/generate_interface.v
Normal file
48
libarchive/baobab/generator/generate_interface.v
Normal file
@@ -0,0 +1,48 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorInterface }
|
||||
import freeflowuniverse.herolib.core.code { CustomCode, VFile }
|
||||
|
||||
fn generate_openrpc_interface_files(interfaces []ActorInterface) (VFile, VFile) {
|
||||
http := ActorInterface.http in interfaces
|
||||
|
||||
iface_file := VFile{
|
||||
name: 'interface_openrpc'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openrpc.v.template')}]
|
||||
}
|
||||
iface_test_file := VFile{
|
||||
name: 'interface_openrpc_test'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openrpc_test.v.template')}]
|
||||
}
|
||||
return iface_file, iface_test_file
|
||||
}
|
||||
|
||||
fn generate_openapi_interface_files(interfaces []ActorInterface) (VFile, VFile) {
|
||||
http := ActorInterface.http in interfaces
|
||||
dollar := '$'
|
||||
iface_file := VFile{
|
||||
name: 'interface_openapi'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openapi.v.template')}]
|
||||
}
|
||||
iface_test_file := VFile{
|
||||
name: 'interface_openapi_test'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openapi_test.v.template')}]
|
||||
}
|
||||
return iface_file, iface_test_file
|
||||
}
|
||||
|
||||
fn generate_http_interface_files(controllers []ActorInterface) (VFile, VFile) {
|
||||
dollar := '$'
|
||||
openapi := ActorInterface.openapi in controllers
|
||||
openrpc := ActorInterface.openrpc in controllers
|
||||
|
||||
iface_file := VFile{
|
||||
name: 'interface_http'
|
||||
items: [CustomCode{$tmpl('./templates/interface_http.v.template')}]
|
||||
}
|
||||
iface_test_file := VFile{
|
||||
name: 'interface_http_test'
|
||||
items: [CustomCode{$tmpl('./templates/interface_http_test.v.template')}]
|
||||
}
|
||||
return iface_file, iface_test_file
|
||||
}
|
||||
165
libarchive/baobab/generator/generate_methods.v
Normal file
165
libarchive/baobab/generator/generate_methods.v
Normal file
@@ -0,0 +1,165 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, Function, Import, Param, Result, Struct, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
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.codegen as jsonschema_codegen
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
|
||||
import log
|
||||
|
||||
const crud_prefixes = ['new', 'get', 'set', 'delete', 'list']
|
||||
|
||||
pub struct Source {
|
||||
openapi_path ?string
|
||||
openrpc_path ?string
|
||||
}
|
||||
|
||||
pub fn generate_methods_file_str(source Source) !string {
|
||||
actor_spec := if path := source.openapi_path {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
return generate_methods_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
pub fn generate_methods_file(spec ActorSpecification) !VFile {
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.pascal_case(spec.name)
|
||||
|
||||
receiver := generate_methods_receiver(spec.name)
|
||||
receiver_param := Param{
|
||||
mutable: true
|
||||
name: name_snake[0].ascii_str() // receiver is first letter of domain
|
||||
typ: Result{code.Object{receiver.name}}
|
||||
}
|
||||
|
||||
mut items := [CodeItem(receiver), CodeItem(generate_core_factory(receiver_param))]
|
||||
for method in spec.methods {
|
||||
items << generate_method_code(receiver_param, ActorMethod{
|
||||
...method
|
||||
category: spec.method_type(method)
|
||||
})!
|
||||
}
|
||||
|
||||
return VFile{
|
||||
name: 'methods'
|
||||
imports: [
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.baobab.osis'
|
||||
types: ['OSIS']
|
||||
},
|
||||
]
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_methods_receiver(name string) Struct {
|
||||
return Struct{
|
||||
is_pub: true
|
||||
name: '${texttools.pascal_case(name)}'
|
||||
fields: [
|
||||
code.StructField{
|
||||
is_mut: true
|
||||
name: 'osis'
|
||||
typ: code.Object{'OSIS'}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_core_factory(receiver Param) Function {
|
||||
return Function{
|
||||
is_pub: true
|
||||
name: 'new_${receiver.typ.symbol()}'
|
||||
body: 'return ${receiver.typ.symbol().trim_left('!?')}{osis: osis.new()!}'
|
||||
result: receiver
|
||||
}
|
||||
}
|
||||
|
||||
// returns bodyless method prototype
|
||||
pub fn generate_method_code(receiver Param, method ActorMethod) ![]CodeItem {
|
||||
result_param := content_descriptor_to_parameter(method.result)!
|
||||
|
||||
mut method_code := []CodeItem{}
|
||||
// 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))
|
||||
if obj_param := obj_params[0] {
|
||||
method_code << obj_param
|
||||
}
|
||||
|
||||
// check if method is a Base Object CRUD Method and
|
||||
// if so generate the method's body
|
||||
// TODO: smart generation of method body using AI
|
||||
// body := match method.category {
|
||||
// .base_object_new { base_object_new_body(receiver, method)! }
|
||||
// .base_object_get { base_object_get_body(receiver, method)! }
|
||||
// .base_object_set { base_object_set_body(receiver, method)! }
|
||||
// .base_object_delete { base_object_delete_body(receiver, method)! }
|
||||
// .base_object_list { base_object_list_body(receiver, method)! }
|
||||
// else { "panic('implement')" }
|
||||
// }
|
||||
|
||||
body := "panic('implement')"
|
||||
|
||||
fn_prototype := generate_method_prototype(receiver, method)!
|
||||
method_code << Function{
|
||||
...fn_prototype
|
||||
body: body
|
||||
}
|
||||
return method_code
|
||||
}
|
||||
|
||||
// returns bodyless method prototype
|
||||
pub fn generate_method_prototype(receiver Param, method ActorMethod) !Function {
|
||||
result_param := content_descriptor_to_parameter(method.result)!
|
||||
return Function{
|
||||
name: texttools.snake_case(method.name)
|
||||
receiver: receiver
|
||||
result: Param{
|
||||
...result_param
|
||||
typ: Result{result_param.typ}
|
||||
}
|
||||
summary: method.summary
|
||||
description: method.description
|
||||
params: method.parameters.map(content_descriptor_to_parameter(it)!)
|
||||
}
|
||||
}
|
||||
|
||||
fn base_object_new_body(receiver Param, method ActorMethod) !string {
|
||||
parameter := content_descriptor_to_parameter(method.parameters[0])!
|
||||
return 'return ${receiver.name}.osis.new[${parameter.typ.vgen()}](${texttools.snake_case(parameter.name)})!'
|
||||
}
|
||||
|
||||
fn base_object_get_body(receiver Param, method ActorMethod) !string {
|
||||
parameter := content_descriptor_to_parameter(method.parameters[0])!
|
||||
result := content_descriptor_to_parameter(method.result)!
|
||||
return 'return ${receiver.name}.osis.get[${result.typ.vgen()}](${texttools.snake_case(parameter.name)})!'
|
||||
}
|
||||
|
||||
fn base_object_set_body(receiver Param, method ActorMethod) !string {
|
||||
parameter := content_descriptor_to_parameter(method.parameters[0])!
|
||||
return 'return ${receiver.name}.osis.set[${parameter.typ.vgen()}](${parameter.name})!'
|
||||
}
|
||||
|
||||
fn base_object_delete_body(receiver Param, method ActorMethod) !string {
|
||||
parameter := content_descriptor_to_parameter(method.parameters[0])!
|
||||
return '${receiver.name}.osis.delete(${texttools.snake_case(parameter.name)})!'
|
||||
}
|
||||
|
||||
fn base_object_list_body(receiver Param, method ActorMethod) !string {
|
||||
// result := content_descriptor_to_parameter(method.result)!
|
||||
// log.error('result typ: ${result.typ}')
|
||||
// base_object_type := (result.typ as Array).typ
|
||||
// return 'return ${receiver.name}.osis.list[${base_object_type.symbol()}]()!'
|
||||
return 'return'
|
||||
}
|
||||
106
libarchive/baobab/generator/generate_methods_example.v
Normal file
106
libarchive/baobab/generator/generate_methods_example.v
Normal file
@@ -0,0 +1,106 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, Function, Import, Param, Result, Struct, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc { Example }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen { content_descriptor_to_parameter }
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorMethod, ActorSpecification }
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
|
||||
pub fn generate_methods_example_file_str(source Source) !string {
|
||||
actor_spec := if path := source.openapi_path {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
return generate_methods_example_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
pub fn generate_methods_example_file(spec ActorSpecification) !VFile {
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
name_pascal := texttools.pascal_case(spec.name)
|
||||
|
||||
receiver := generate_example_methods_receiver(spec.name)
|
||||
receiver_param := Param{
|
||||
mutable: true
|
||||
name: name_snake[0].ascii_str()
|
||||
typ: Result{code.Object{receiver.name}}
|
||||
}
|
||||
mut items := [CodeItem(receiver), CodeItem(generate_core_example_factory(receiver_param))]
|
||||
for method in spec.methods {
|
||||
items << generate_method_example_code(receiver_param, ActorMethod{
|
||||
...method
|
||||
category: spec.method_type(method)
|
||||
})!
|
||||
}
|
||||
|
||||
return VFile{
|
||||
name: 'methods_example'
|
||||
imports: [
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.baobab.osis'
|
||||
types: ['OSIS']
|
||||
},
|
||||
Import{
|
||||
mod: 'x.json2 as json'
|
||||
},
|
||||
]
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_core_example_factory(receiver Param) Function {
|
||||
return Function{
|
||||
is_pub: true
|
||||
name: 'new_${texttools.snake_case(receiver.typ.symbol())}'
|
||||
body: 'return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}'
|
||||
result: receiver
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_example_methods_receiver(name string) Struct {
|
||||
return Struct{
|
||||
is_pub: true
|
||||
name: '${texttools.pascal_case(name)}Example'
|
||||
embeds: [Struct{
|
||||
name: 'OSIS'
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// returns bodyless method prototype
|
||||
pub fn generate_method_example_code(receiver Param, method ActorMethod) ![]CodeItem {
|
||||
result_param := content_descriptor_to_parameter(method.result)!
|
||||
|
||||
mut method_code := []CodeItem{}
|
||||
// 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))
|
||||
// if obj_param := obj_params[0] {
|
||||
// method_code << Struct{...obj_param, name: method.name}
|
||||
// }
|
||||
|
||||
// check if method is a Base Object CRUD Method and
|
||||
// if so generate the method's body
|
||||
body := if !method_is_void(method)! {
|
||||
if method.example.result is Example {
|
||||
"json_str := '${method.example.result.value}'
|
||||
return ${generate_decode_stmt('json_str',
|
||||
method.result)!}"
|
||||
} else {
|
||||
'return ${result_param.typ.empty_value()}'
|
||||
}
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
fn_prototype := generate_method_prototype(receiver, method)!
|
||||
method_code << Function{
|
||||
...fn_prototype
|
||||
body: body
|
||||
}
|
||||
return method_code
|
||||
}
|
||||
49
libarchive/baobab/generator/generate_methods_interface.v
Normal file
49
libarchive/baobab/generator/generate_methods_interface.v
Normal file
@@ -0,0 +1,49 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, Import, Param, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorSpecification }
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
|
||||
pub fn generate_methods_interface_file_str(source Source) !string {
|
||||
actor_spec := if path := source.openapi_path {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
return generate_methods_interface_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
pub fn generate_methods_interface_file(spec ActorSpecification) !VFile {
|
||||
return VFile{
|
||||
name: 'methods_interface'
|
||||
imports: [
|
||||
Import{
|
||||
mod: 'freeflowuniverse.herolib.baobab.osis'
|
||||
types: ['OSIS']
|
||||
},
|
||||
]
|
||||
items: [CodeItem(generate_methods_interface_declaration(spec)!)]
|
||||
}
|
||||
}
|
||||
|
||||
// returns bodyless method prototype
|
||||
pub fn generate_methods_interface_declaration(spec ActorSpecification) !code.Interface {
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
name_pascal := texttools.pascal_case(spec.name)
|
||||
receiver := generate_methods_receiver(spec.name)
|
||||
receiver_param := Param{
|
||||
mutable: true
|
||||
name: name_snake[0].ascii_str()
|
||||
typ: code.Object{receiver.name}
|
||||
}
|
||||
return code.Interface{
|
||||
is_pub: true
|
||||
name: 'I${name_pascal}'
|
||||
methods: spec.methods.map(generate_method_prototype(receiver_param, it)!)
|
||||
}
|
||||
}
|
||||
32
libarchive/baobab/generator/generate_model.v
Normal file
32
libarchive/baobab/generator/generate_model.v
Normal file
@@ -0,0 +1,32 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, Struct, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen { schema_to_struct }
|
||||
import freeflowuniverse.herolib.baobab.specification { ActorSpecification }
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
|
||||
pub fn generate_model_file_str(source Source) !string {
|
||||
actor_spec := if path := source.openapi_path {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
return generate_model_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
pub fn generate_model_file(spec ActorSpecification) !VFile {
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.pascal_case(spec.name)
|
||||
|
||||
return VFile{
|
||||
name: 'model'
|
||||
items: spec.objects.map(CodeItem(Struct{
|
||||
...schema_to_struct(it.schema)
|
||||
is_pub: true
|
||||
}))
|
||||
}
|
||||
}
|
||||
160
libarchive/baobab/generator/generate_openapi.v
Normal file
160
libarchive/baobab/generator/generate_openapi.v
Normal file
@@ -0,0 +1,160 @@
|
||||
module generator
|
||||
|
||||
import json
|
||||
import freeflowuniverse.herolib.core.code { File, Folder }
|
||||
import freeflowuniverse.herolib.schemas.openapi { OpenAPI, Operation }
|
||||
import freeflowuniverse.herolib.schemas.openapi.codegen
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen { schema_to_type }
|
||||
import net.http
|
||||
|
||||
pub fn generate_openapi_file(specification OpenAPI) !File {
|
||||
openapi_json := specification.encode_json()
|
||||
return File{
|
||||
name: 'openapi'
|
||||
extension: 'json'
|
||||
content: openapi_json
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_openapi_ts_client(specification OpenAPI) !Folder {
|
||||
return codegen.ts_client_folder(specification,
|
||||
body_generator: body_generator
|
||||
custom_client_code: ' private restClient: HeroRestClient;
|
||||
|
||||
constructor(heroKeysClient: any, debug: boolean = true) {
|
||||
this.restClient = new HeroRestClient(heroKeysClient, debug);
|
||||
}
|
||||
'
|
||||
)!
|
||||
}
|
||||
|
||||
fn body_generator(op Operation, path_ string, method http.Method) string {
|
||||
path := path_.replace('{', '\${')
|
||||
return match method {
|
||||
.post {
|
||||
if schema := op.payload_schema() {
|
||||
symbol := schema_to_type(schema).typescript()
|
||||
"return this.restClient.post<${symbol}>('${path}', data);"
|
||||
} else {
|
||||
''
|
||||
}
|
||||
}
|
||||
.get {
|
||||
if schema := op.response_schema() {
|
||||
// if op.params.len
|
||||
symbol := schema_to_type(schema).typescript()
|
||||
"return this.restClient.get<${symbol}>('${path}', data);"
|
||||
} else {
|
||||
''
|
||||
}
|
||||
}
|
||||
else {
|
||||
''
|
||||
}
|
||||
}
|
||||
// return if operation_is_base_object_method(op) {
|
||||
// bo_method := operation_to_base_object_method(op)
|
||||
// match method_type(op) {
|
||||
// .new { ts_client_new_body(op, path) }
|
||||
// .get { ts_client_get_body(op, path) }
|
||||
// .set { ts_client_set_body(op, path) }
|
||||
// .delete { ts_client_delete_body(op, path) }
|
||||
// .list { ts_client_list_body(op, path) }
|
||||
// else {''}
|
||||
// }
|
||||
// } else {''}
|
||||
}
|
||||
|
||||
// pub fn operation_is_base_object_method(op openapi.Operation, base_objs []string) BaseObjectMethod {
|
||||
// // name := texttools.pascal_case(op.operation_id)
|
||||
|
||||
// // if op.operation_id.starts_with('new') {
|
||||
// // if op.&& operation.params.len == 1
|
||||
|
||||
// return true
|
||||
// }
|
||||
|
||||
// pub fn operation_to_base_object_method(op openapi.Operation) BaseObjectMethod {
|
||||
// if op.operation_id.starts_with('update')
|
||||
// }
|
||||
|
||||
// pub fn openapi_ts_client_body(op openapi.Operation, path string, method http.Method) string {
|
||||
// match method {
|
||||
// post {
|
||||
// if schema := op.payload_schema() {
|
||||
// symbol := schema_to_type(schema).typescript()
|
||||
// return "return this.restClient.post<${symbol}>('${path}', data);"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return if operation_is_base_object_method(op) {
|
||||
// bo_method := operation_to_base_object_method(op)
|
||||
// match bo_method. {
|
||||
// .new { ts_client_new_body(op, path) }
|
||||
// .get { ts_client_get_body(op, path) }
|
||||
// .set { ts_client_set_body(op, path) }
|
||||
// .delete { ts_client_delete_body(op, path) }
|
||||
// .list { ts_client_list_body(op, path) }
|
||||
// else {''}
|
||||
// }
|
||||
// } else {''}
|
||||
// }
|
||||
|
||||
fn get_endpoint(path string) string {
|
||||
return if path == '' {
|
||||
''
|
||||
} else {
|
||||
'/${path.trim('/')}'
|
||||
}
|
||||
}
|
||||
|
||||
// // generates a Base Object's `create` method
|
||||
// fn ts_client_new_body(op Operation, path string) string {
|
||||
// // the parameter of a base object new method is always the base object
|
||||
// bo_param := openapi_codegen.parameter_to_param(op.parameters[0])
|
||||
// return "return this.restClient.post<${bo_param.typ.typescript()}>('${get_endpoint(path)}', ${bo_param.name});"
|
||||
// }
|
||||
|
||||
// // generates a Base Object's `create` method
|
||||
// fn ts_client_get_body(op Operation, path string) string {
|
||||
// // the parameter of a base object get method is always the id
|
||||
// id_param := openapi_codegen.parameter_to_param(op.parameters[0])
|
||||
// return "return this.restClient.get<${id_param.typ.typescript()}>('${get_endpoint(path)}', ${id_param.name});"
|
||||
// }
|
||||
|
||||
// // generates a Base Object's `create` method
|
||||
// fn ts_client_set_body(op Operation, path string) string {
|
||||
// // the parameter of a base object set method is always the base object
|
||||
// bo_param := openapi_codegen.parameter_to_param(op.parameters[0])
|
||||
// return "return this.restClient.put<${bo_param.typ.typescript()}>('${get_endpoint(path)}', ${bo_param.name});"
|
||||
// }
|
||||
|
||||
// // generates a Base Object's `delete` method
|
||||
// fn ts_client_delete_body(op Operation, path string) string {
|
||||
// // the parameter of a base object delete method is always the id
|
||||
// id_param := openapi_codegen.parameter_to_param(op.parameters[0])
|
||||
// return "return this.restClient.get<${id_param.typ.typescript()}>('${get_endpoint(path)}', ${id_param.name});"
|
||||
// }
|
||||
|
||||
// // generates a Base Object's `list` method
|
||||
// fn ts_client_list_body(op Operation, path string) string {
|
||||
// // the result parameter of a base object list method is always the array of bo
|
||||
// result_param := openapi_codegen.parameter_to_param(op.parameters[0])
|
||||
// return "return this.restClient.get<${result_param.typ.typescript()}>('${get_endpoint(path)}');"
|
||||
// }
|
||||
|
||||
// pub enum BaseObjectMethodType {
|
||||
// new
|
||||
// get
|
||||
// set
|
||||
// delete
|
||||
// list
|
||||
// other
|
||||
// }
|
||||
|
||||
// pub struct BaseObjectMethod {
|
||||
// pub:
|
||||
// typ BaseObjectMethodType
|
||||
// object string // the name of the base object
|
||||
// }
|
||||
110
libarchive/baobab/generator/generate_openrpc.v
Normal file
110
libarchive/baobab/generator/generate_openrpc.v
Normal file
@@ -0,0 +1,110 @@
|
||||
module generator
|
||||
|
||||
import json
|
||||
import freeflowuniverse.herolib.core.code { File, Function, Struct, VFile }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen { generate_client_file, generate_client_test_file }
|
||||
|
||||
pub fn generate_openrpc_file(spec OpenRPC) !File {
|
||||
return File{
|
||||
name: 'openrpc'
|
||||
extension: 'json'
|
||||
content: json.encode(spec)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_openrpc_client_file(spec OpenRPC) !VFile {
|
||||
mut objects_map := map[string]Struct{}
|
||||
// for object in spec.objects {
|
||||
// objects_map[object.structure.name] = object.structure
|
||||
// }
|
||||
client_file := generate_client_file(spec, objects_map)!
|
||||
return VFile{
|
||||
...client_file
|
||||
name: 'client_openrpc'
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_openrpc_client_test_file(spec OpenRPC) !VFile {
|
||||
mut objects_map := map[string]Struct{}
|
||||
// for object in spec.objects {
|
||||
// objects_map[object.structure.name] = object.structure
|
||||
// }
|
||||
mut methods_map := map[string]Function{}
|
||||
// for method in spec.methods {
|
||||
// methods_map[method.func.name] = method.func
|
||||
// }
|
||||
file := generate_client_test_file(spec, methods_map, objects_map)!
|
||||
return VFile{
|
||||
...file
|
||||
name: 'client_openrpc_test'
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn (actor Actor) generate_openrpc_code() !Module {
|
||||
// openrpc_obj := actor.generate_openrpc()
|
||||
// openrpc_json := openrpc_obj.encode()!
|
||||
|
||||
// openrpc_file := File{
|
||||
// name: 'openrpc'
|
||||
// extension: 'json'
|
||||
// content: openrpc_json
|
||||
// }
|
||||
|
||||
// mut methods_map := map[string]Function{}
|
||||
// for method in actor.methods {
|
||||
// methods_map[method.func.name] = method.func
|
||||
// }
|
||||
|
||||
// mut objects_map := map[string]Struct{}
|
||||
// for object in actor.objects {
|
||||
// objects_map[object.structure.name] = object.structure
|
||||
// }
|
||||
// // actor_struct := generate_actor_struct(actor.name)
|
||||
// actor_struct := actor.structure
|
||||
|
||||
// client_file := openrpc_obj.generate_client_file(objects_map)!
|
||||
// client_test_file := openrpc_obj.generate_client_test_file(methods_map, objects_map)!
|
||||
|
||||
// handler_file := openrpc_obj.generate_handler_file(actor_struct, methods_map, objects_map)!
|
||||
// handler_test_file := openrpc_obj.generate_handler_test_file(actor_struct, methods_map,
|
||||
// objects_map)!
|
||||
|
||||
// server_file := openrpc_obj.generate_server_file()!
|
||||
// server_test_file := openrpc_obj.generate_server_test_file()!
|
||||
|
||||
// return Module{
|
||||
// files: [
|
||||
// client_file,
|
||||
// client_test_file,
|
||||
// handler_file,
|
||||
// handler_test_file,
|
||||
// server_file,
|
||||
// server_test_file,
|
||||
// ]
|
||||
// // misc_files: [openrpc_file]
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn (mut a Actor) export_playground(path string, openrpc_path string) ! {
|
||||
// dollar := '$'
|
||||
// openrpc.export_playground(
|
||||
// dest: pathlib.get_dir(path: '${path}/playground')!
|
||||
// specs: [
|
||||
// pathlib.get(openrpc_path),
|
||||
// ]
|
||||
// )!
|
||||
// mut cli_file := pathlib.get_file(path: '${path}/command/cli.v')!
|
||||
// cli_file.write($tmpl('./templates/playground.v.template'))!
|
||||
// }
|
||||
|
||||
// pub fn param_to_content_descriptor(param Param) openrpc.ContentDescriptor {
|
||||
// if param.name == 'id' && param.typ.symbol ==
|
||||
|
||||
// return openrpc.ContentDescriptor {
|
||||
// name: param.name
|
||||
// summary: param.description
|
||||
// required: param.is_required()
|
||||
// schema:
|
||||
// }
|
||||
// }
|
||||
38
libarchive/baobab/generator/generate_openrpc_test.v
Normal file
38
libarchive/baobab/generator/generate_openrpc_test.v
Normal file
@@ -0,0 +1,38 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Function, Param, Result, Struct, Type }
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
|
||||
const test_actor_specification = ActorSpecification{
|
||||
methods: [
|
||||
ActorMethod{
|
||||
func: Function{
|
||||
name: 'get_object'
|
||||
params: [
|
||||
Param{
|
||||
name: 'id'
|
||||
typ: Type{
|
||||
symbol: 'int'
|
||||
}
|
||||
},
|
||||
]
|
||||
result: Result{
|
||||
typ: Type{
|
||||
symbol: 'Object'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
objects: [BaseObject{
|
||||
structure: Struct{
|
||||
name: 'Object'
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
pub fn test_generate_openrpc() ! {
|
||||
actor := Actor{}
|
||||
object := generate_openrpc(actor)
|
||||
panic(object.encode()!)
|
||||
}
|
||||
75
libarchive/baobab/generator/generate_scripts.v
Normal file
75
libarchive/baobab/generator/generate_scripts.v
Normal file
@@ -0,0 +1,75 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { File, Folder }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// generates the folder with runnable scripts of the actor
|
||||
pub fn generate_scripts_folder(name string, example bool) Folder {
|
||||
actor_name := '${texttools.snake_case(name)}_actor'
|
||||
return Folder{
|
||||
name: 'scripts'
|
||||
files: [
|
||||
generate_run_script(actor_name),
|
||||
generate_docs_script(actor_name),
|
||||
generate_run_actor_script(name),
|
||||
generate_run_actor_example_script(name),
|
||||
generate_run_http_server_script(name),
|
||||
// generate_compile_script(actor_name),
|
||||
// generate_generate_script(actor_name)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Function to generate a script for running an actor
|
||||
fn generate_run_script(actor_name string) File {
|
||||
actor_title := texttools.title_case(actor_name)
|
||||
dollar := '$'
|
||||
return File{
|
||||
name: 'run'
|
||||
extension: 'sh'
|
||||
content: $tmpl('./templates/run.sh.template')
|
||||
}
|
||||
}
|
||||
|
||||
// Function to generate a script for running an actor
|
||||
fn generate_docs_script(actor_name string) File {
|
||||
dollar := '$'
|
||||
return File{
|
||||
name: 'docs'
|
||||
extension: 'vsh'
|
||||
content: $tmpl('./templates/docs.vsh.template')
|
||||
}
|
||||
}
|
||||
|
||||
// Function to generate a script for running an actor
|
||||
fn generate_run_actor_script(name string) File {
|
||||
name_snake := texttools.snake_case(name)
|
||||
name_pascal := texttools.pascal_case(name)
|
||||
return File{
|
||||
name: 'run_actor'
|
||||
extension: 'vsh'
|
||||
content: $tmpl('./templates/run_actor.vsh.template')
|
||||
}
|
||||
}
|
||||
|
||||
// Function to generate a script for running an example actor
|
||||
fn generate_run_actor_example_script(name string) File {
|
||||
name_snake := texttools.snake_case(name)
|
||||
name_pascal := texttools.pascal_case(name)
|
||||
return File{
|
||||
name: 'run_actor_example'
|
||||
extension: 'vsh'
|
||||
content: $tmpl('./templates/run_actor_example.vsh.template')
|
||||
}
|
||||
}
|
||||
|
||||
// Function to generate a script for running an HTTP server
|
||||
fn generate_run_http_server_script(name string) File {
|
||||
port := 8080
|
||||
name_snake := texttools.snake_case(name)
|
||||
return File{
|
||||
name: 'run_http_server'
|
||||
extension: 'vsh'
|
||||
content: $tmpl('./templates/run_http_server.vsh.template')
|
||||
}
|
||||
}
|
||||
41
libarchive/baobab/generator/templates/actor.v.template
Normal file
41
libarchive/baobab/generator/templates/actor.v.template
Normal file
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
import freeflowuniverse.herolib.baobab.stage
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import time
|
||||
|
||||
pub const configuration = stage.ActorConfig {
|
||||
name: '@{name_snake}'
|
||||
version: '@{version}'
|
||||
}
|
||||
|
||||
@@[heap]
|
||||
struct @{name_pascal}Actor {
|
||||
stage.Actor
|
||||
pub mut:
|
||||
@{name_snake} I@{name_pascal}
|
||||
}
|
||||
|
||||
pub fn new(core I@{name_pascal}, config stage.ActorConfig) !&@{name_pascal}Actor {
|
||||
return &@{name_pascal}Actor {
|
||||
Actor: stage.new_actor(config)!
|
||||
@{name_snake}: core
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut a @{name_pascal}Actor) handle(method string, data string) !string {
|
||||
action := a.act(
|
||||
name: method
|
||||
params: data
|
||||
)!
|
||||
return action.result
|
||||
}
|
||||
|
||||
// Actor listens to the Redis queue for method invocations
|
||||
pub fn (mut a @{name_pascal}Actor) run() ! {
|
||||
mut rpc := a.get_redis_rpc()!
|
||||
for {
|
||||
rpc.process(a.handle)!
|
||||
time.sleep(time.millisecond * 100) // Prevent CPU spinning
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
import freeflowuniverse.herolib.baobab.stage
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
|
||||
const name = '@{actor_name_snake}'
|
||||
|
||||
@@[heap]
|
||||
struct @{actor_name_pascal}Actor {
|
||||
stage.Actor
|
||||
}
|
||||
|
||||
pub fn new() !&@{actor_name_pascal}Actor {
|
||||
return &@{actor_name_pascal}Actor {
|
||||
Actor: stage.new_actor('example_@{actor_name_snake}')!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut a @{actor_name_pascal}Actor) handle(method string, data string) !string {
|
||||
action := a.act(
|
||||
name: method
|
||||
params: data
|
||||
)!
|
||||
return action.result
|
||||
}
|
||||
|
||||
// Actor listens to the Redis queue for method invocations
|
||||
pub fn (mut a @{actor_name_pascal}Actor) run() ! {
|
||||
mut redis := redisclient.new('localhost:6379') or { panic(err) }
|
||||
mut rpc := redis.rpc_get('actor_@{dollar}{a.name}')
|
||||
|
||||
println('Actor started and listening for tasks...')
|
||||
for {
|
||||
rpc.process(a.handle)!
|
||||
time.sleep(time.millisecond * 100) // Prevent CPU spinning
|
||||
}
|
||||
}
|
||||
10
libarchive/baobab/generator/templates/actor_test.v.template
Normal file
10
libarchive/baobab/generator/templates/actor_test.v.template
Normal file
@@ -0,0 +1,10 @@
|
||||
const test_port = 8101
|
||||
|
||||
pub fn test_new() ! {
|
||||
new() or { return error('Failed to create actor:\n@{dollar}{err}') }
|
||||
}
|
||||
|
||||
pub fn test_actor_run() ! {
|
||||
mut actor := new()!
|
||||
spawn actor.run()
|
||||
}
|
||||
63
libarchive/baobab/generator/templates/cli.v.template
Normal file
63
libarchive/baobab/generator/templates/cli.v.template
Normal file
@@ -0,0 +1,63 @@
|
||||
module @{name}
|
||||
|
||||
import os
|
||||
import cli { Command }
|
||||
import vweb
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
|
||||
const openrpc_path = '@{dollar}{os.dir(os.dir(@@FILE))}/openrpc.json'
|
||||
const playground_path = '@{dollar}{os.dir(os.dir(@@FILE))}/playground'
|
||||
|
||||
fn do() ! {
|
||||
mut cmd := new_command()
|
||||
cmd.setup()
|
||||
cmd.parse(os.args)
|
||||
}
|
||||
|
||||
pub fn new_command() Command {
|
||||
mut cmd := Command{
|
||||
name: '@{name}'
|
||||
description: 'Your @{name} toolset.'
|
||||
version: '1.0.16'
|
||||
}
|
||||
|
||||
|
||||
mut cmd_run := Command{
|
||||
name: 'run_server'
|
||||
description: 'Run @{name} websocket server.'
|
||||
usage: ''
|
||||
required_args: 0
|
||||
execute: cmd_run_wsserver
|
||||
}
|
||||
|
||||
mut cmd_playground := Command{
|
||||
name: 'playground'
|
||||
description: 'Run @{name} playground server.'
|
||||
usage: ''
|
||||
required_args: 0
|
||||
execute: playground
|
||||
}
|
||||
|
||||
cmd.add_command(cmd_run)
|
||||
cmd.add_command(cmd_playground)
|
||||
return cmd
|
||||
}
|
||||
|
||||
fn cmd_run_wsserver(cmd Command) ! {
|
||||
// accountant.run_wsserver(3000)!
|
||||
}
|
||||
|
||||
fn playground(cmd Command) ! {
|
||||
pg := openrpc.new_playground(
|
||||
dest: pathlib.get_dir(path: playground_path)!
|
||||
specs: [pathlib.get_file(path:openrpc_path)!]
|
||||
)!
|
||||
vweb.run(pg, 8080)
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
do() or { panic(err) }
|
||||
}
|
||||
74
libarchive/baobab/generator/templates/client_test.v
Normal file
74
libarchive/baobab/generator/templates/client_test.v
Normal file
@@ -0,0 +1,74 @@
|
||||
module pet_store_actor
|
||||
|
||||
import freeflowuniverse.herolib.baobab.stage
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
fn mock_response() ! {
|
||||
mut redis := redisclient.new('localhost:6379')!
|
||||
mut rpc_q := redis.rpc_get('actor_pet_store')
|
||||
for {
|
||||
rpc_q.process(fn (method string, data string) !string {
|
||||
return json.encode(method)
|
||||
})!
|
||||
time.sleep(time.millisecond * 100) // Prevent CPU spinning
|
||||
}
|
||||
}
|
||||
|
||||
fn test_list_pets() ! {
|
||||
mut client := new_client()!
|
||||
limit := 10
|
||||
spawn mock_response()
|
||||
pets := client.list_pets(limit)!
|
||||
// assert pets.len <= limit
|
||||
println('test_list_pets passed')
|
||||
}
|
||||
|
||||
fn test_create_pet() ! {
|
||||
mut client := new_client()!
|
||||
client.create_pet()!
|
||||
println('test_create_pet passed')
|
||||
}
|
||||
|
||||
fn test_get_pet() ! {
|
||||
mut client := new_client()!
|
||||
pet_id := 1 // Replace with an actual pet ID in your system
|
||||
pet := client.get_pet(pet_id)!
|
||||
// assert pet.id == pet_id
|
||||
println('test_get_pet passed')
|
||||
}
|
||||
|
||||
fn test_delete_pet() ! {
|
||||
mut client := new_client()!
|
||||
pet_id := 1 // Replace with an actual pet ID in your system
|
||||
client.delete_pet(pet_id)!
|
||||
println('test_delete_pet passed')
|
||||
}
|
||||
|
||||
fn test_list_orders() ! {
|
||||
mut client := new_client()!
|
||||
client.list_orders()!
|
||||
println('test_list_orders passed')
|
||||
}
|
||||
|
||||
fn test_get_order() ! {
|
||||
mut client := new_client()!
|
||||
order_id := 1 // Replace with an actual order ID in your system
|
||||
order := client.get_order(order_id)!
|
||||
// assert order.id == order_id
|
||||
println('test_get_order passed')
|
||||
}
|
||||
|
||||
fn test_delete_order() ! {
|
||||
mut client := new_client()!
|
||||
order_id := 1 // Replace with an actual order ID in your system
|
||||
client.delete_order(order_id)!
|
||||
println('test_delete_order passed')
|
||||
}
|
||||
|
||||
fn test_create_user() ! {
|
||||
mut client := new_client()!
|
||||
client.create_user()!
|
||||
println('test_create_user passed')
|
||||
}
|
||||
81
libarchive/baobab/generator/templates/command.v.template
Normal file
81
libarchive/baobab/generator/templates/command.v.template
Normal file
@@ -0,0 +1,81 @@
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import cli { Command, Flag }
|
||||
import os
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
pub fn cmd_example_actor() Command {
|
||||
mut cmd := Command{
|
||||
name: 'example_actor'
|
||||
usage: ''
|
||||
description: 'create, edit, show mdbooks'
|
||||
required_args: 0
|
||||
execute: cmd_example_actor_execute
|
||||
}
|
||||
|
||||
mut cmd_list := Command{
|
||||
sort_flags: true
|
||||
name: 'list_books'
|
||||
execute: cmd_publisher_list_books
|
||||
description: 'will list existing mdbooks'
|
||||
pre_execute: pre_func
|
||||
}
|
||||
|
||||
mut cmd_open := Command{
|
||||
name: 'open'
|
||||
execute: cmd_publisher_open
|
||||
description: 'will open the publication with the provided name'
|
||||
pre_execute: pre_func
|
||||
}
|
||||
|
||||
cmd_open.add_flag(Flag{
|
||||
flag: .string
|
||||
name: 'name'
|
||||
abbrev: 'n'
|
||||
description: 'name of the publication.'
|
||||
})
|
||||
|
||||
cmd.add_command(cmd_list)
|
||||
cmd.add_command(cmd_open)
|
||||
return cmd
|
||||
}
|
||||
|
||||
fn cmd_publisher_list_books(cmd Command) ! {
|
||||
console.print_header('Books:')
|
||||
books := publisher.list_books()!
|
||||
for book in books {
|
||||
console.print_stdout(book.str())
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_publisher_open(cmd Command) ! {
|
||||
name := cmd.flags.get_string('name') or { '' }
|
||||
publisher.open(name)!
|
||||
}
|
||||
|
||||
fn cmd_execute(cmd Command) ! {
|
||||
mut name := cmd.flags.get_string('name') or { '' }
|
||||
|
||||
if name == '' {
|
||||
console.print_debug('did not find name of book to generate, check in heroscript or specify with --name')
|
||||
publisher_help(cmd)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
edit := cmd.flags.get_bool('edit') or { false }
|
||||
open := cmd.flags.get_bool('open') or { false }
|
||||
if edit || open {
|
||||
// mdbook.book_open(name)!
|
||||
}
|
||||
|
||||
if edit {
|
||||
// publisher.book_edit(name)!
|
||||
}
|
||||
}
|
||||
|
||||
fn publisher_help(cmd Command) {
|
||||
console.clear()
|
||||
console.print_header('Instructions for example actor:')
|
||||
console.print_lf(1)
|
||||
console.print_stdout(cmd.help_message())
|
||||
console.print_lf(5)
|
||||
}
|
||||
51
libarchive/baobab/generator/templates/docs.vsh.template
Executable file
51
libarchive/baobab/generator/templates/docs.vsh.template
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import os
|
||||
|
||||
abs_dir_of_script := dir(@@FILE)
|
||||
|
||||
// Format code
|
||||
println('Formatting code...')
|
||||
if os.system('v fmt -w @{dollar}{abs_dir_of_script}/examples') != 0 {
|
||||
eprintln('Warning: Failed to format examples')
|
||||
}
|
||||
|
||||
if os.system('v fmt -w @{dollar}{abs_dir_of_script}/src') != 0 {
|
||||
eprintln('Warning: Failed to format actor')
|
||||
}
|
||||
|
||||
// Clean existing docs
|
||||
println('Cleaning existing documentation...')
|
||||
|
||||
os.rmdir_all('_docs') or {}
|
||||
os.rmdir_all('docs') or {}
|
||||
os.rmdir_all('vdocs') or {}
|
||||
|
||||
herolib_path := os.join_path(abs_dir_of_script, 'lib')
|
||||
os.chdir(herolib_path) or {
|
||||
panic('Failed to change directory to herolib: @{dollar}{err}')
|
||||
}
|
||||
|
||||
os.rmdir_all('_docs') or {}
|
||||
os.rmdir_all('docs') or {}
|
||||
os.rmdir_all('vdocs') or {}
|
||||
|
||||
// Generate HTML documentation
|
||||
println('Generating HTML documentation...')
|
||||
if os.system('v doc -m -f html . -readme -comments -no-timestamp -o ../docs') != 0 {
|
||||
panic('Failed to generate HTML documentation')
|
||||
}
|
||||
|
||||
os.chdir(abs_dir_of_script) or {
|
||||
panic('Failed to change directory to abs_dir_of_script: @{dollar}{err}')
|
||||
}
|
||||
|
||||
// Generate Markdown documentation
|
||||
println('Generating Markdown documentation...')
|
||||
os.rmdir_all('vdocs') or {}
|
||||
|
||||
if os.system('v doc -m -no-color -f md -o vdocs/herolib/') != 0 {
|
||||
panic('Failed to generate Hero markdown documentation')
|
||||
}
|
||||
|
||||
println('Documentation generation completed successfully!')
|
||||
@@ -0,0 +1,41 @@
|
||||
import freeflowuniverse.herolib.schemas.openapi { OpenAPI }
|
||||
import freeflowuniverse.herolib.baobab.stage {Client, ClientConfig}
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
import freeflowuniverse.herolib.baobab.stage.interfaces { HTTPServer, Context }
|
||||
import veb
|
||||
|
||||
@@[params]
|
||||
pub struct HTTPServerParams {
|
||||
pub:
|
||||
base_url string
|
||||
port int = 8080
|
||||
}
|
||||
|
||||
pub fn new_http_server(params HTTPServerParams) !&HTTPServer {
|
||||
mut s := interfaces.new_http_server()!
|
||||
@if openrpc
|
||||
mut openrpc_controller := new_openrpc_http_controller(HTTPServerParams{
|
||||
...params,
|
||||
base_url: '@{dollar}{params.base_url}/openrpc'
|
||||
})!
|
||||
s.register_controller[openrpc.HTTPController, Context]('/openrpc', mut openrpc_controller)!
|
||||
@end
|
||||
@if openapi
|
||||
mut openapi_ctrl := new_openapi_http_controller(configuration, params)!
|
||||
mut openapi_ex_ctrl := new_openapi_http_controller(configuration.example().example(), params)!
|
||||
|
||||
mut openapi_playground_controller := openapi.new_playground_controller(
|
||||
base_url: '@{dollar}{params.base_url}/playground/openapi'
|
||||
specification_path: openapi_spec_path
|
||||
)!
|
||||
s.register_controller[openapi.HTTPController, Context]('/openapi/v1', mut openapi_ctrl)!
|
||||
s.register_controller[openapi.HTTPController, Context]('/openapi/example', mut openapi_ex_ctrl)!
|
||||
s.register_controller[openapi.PlaygroundController, Context]('/playground/openapi', mut openapi_playground_controller)!
|
||||
@end
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn run_http_server(params HTTPServerParams) ! {
|
||||
mut server := new_http_server(params)!
|
||||
veb.run[HTTPServer, Context](mut server, params.port)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fn test_new_http_server() ! {
|
||||
new_http_server()!
|
||||
}
|
||||
|
||||
fn test_run_http_server() ! {
|
||||
spawn run_http_server()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import freeflowuniverse.herolib.baobab.stage.interfaces
|
||||
import freeflowuniverse.herolib.baobab.stage
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
|
||||
pub fn new_openapi_interface(config stage.ActorConfig) !&interfaces.OpenAPIInterface {
|
||||
// create OpenAPI Handler with actor's client
|
||||
client := new_client(config)!
|
||||
return interfaces.new_openapi_interface(client.Client)
|
||||
}
|
||||
|
||||
@if http
|
||||
// creates HTTP controller with the actor's OpenAPI Handler
|
||||
// and OpenAPI Specification
|
||||
pub fn new_openapi_http_controller(config stage.ActorConfig, params HTTPServerParams) !&openapi.HTTPController {
|
||||
return openapi.new_http_controller(
|
||||
base_url: '@{dollar}{params.base_url}/openapi/@{dollar}{config.version}'
|
||||
specification: openapi_specification
|
||||
specification_path: openapi_spec_path
|
||||
handler: new_openapi_interface(config)!
|
||||
)
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,9 @@
|
||||
fn test_new_openapi_interface() ! {
|
||||
new_openapi_interface()!
|
||||
}
|
||||
|
||||
@if http
|
||||
fn test_new_openapi_http_controller() ! {
|
||||
new_openapi_http_controller()!
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
import freeflowuniverse.herolib.baobab.stage.interfaces
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
|
||||
pub fn new_openrpc_interface() !&interfaces.OpenRPCInterface {
|
||||
// create OpenRPC Handler with actor's client
|
||||
client := new_client()!
|
||||
return interfaces.new_openrpc_interface(client.Client)
|
||||
}
|
||||
|
||||
@if http
|
||||
// creates HTTP controller with the actor's OpenRPC Handler
|
||||
// and OpenRPC Specification
|
||||
pub fn new_openrpc_http_controller(params ServerParams) !&openrpc.HTTPController {
|
||||
return openrpc.new_http_controller(
|
||||
specification: openrpc_specification
|
||||
handler: new_openrpc_interface()!
|
||||
)
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,9 @@
|
||||
fn test_new_openrpc_interface() ! {
|
||||
new_openrpc_interface()!
|
||||
}
|
||||
|
||||
@if http
|
||||
fn test_new_openrpc_http_controller() ! {
|
||||
new_openrpc_http_controller()!
|
||||
}
|
||||
@end
|
||||
16
libarchive/baobab/generator/templates/playground.v.template
Normal file
16
libarchive/baobab/generator/templates/playground.v.template
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env -S v -n -cg -w -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.stages.accountant
|
||||
import vweb
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
const openrpc_path = '@{dollar}{os.dir(os.dir(@@FILE))}/openrpc.json'
|
||||
const playground_path = '@{dollar}{os.dir(os.dir(@@FILE))}/playground'
|
||||
|
||||
pg := openrpc.new_playground(
|
||||
dest: pathlib.get_dir(path: playground_path)!
|
||||
specs: [pathlib.get_file(path:openrpc_path)!]
|
||||
)!
|
||||
vweb.run(pg, 8080)
|
||||
42
libarchive/baobab/generator/templates/run.sh.template
Executable file
42
libarchive/baobab/generator/templates/run.sh.template
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
DIR="@{dollar}(cd "@{dollar}(dirname "@{dollar}{BASH_SOURCE[0]}")" && pwd)"
|
||||
echo "@{dollar}DIR"
|
||||
|
||||
chmod +x @{dollar}{DIR}/run_actor.vsh
|
||||
@{dollar}{DIR}/run_actor.vsh &
|
||||
ACTOR_PID=@{dollar}!
|
||||
|
||||
chmod +x @{dollar}{DIR}/run_actor_example.vsh
|
||||
@{dollar}{DIR}/run_actor_example.vsh &
|
||||
EXAMPLE_ACTOR_PID=@{dollar}!
|
||||
|
||||
chmod +x @{dollar}{DIR}/run_http_server.vsh
|
||||
@{dollar}{DIR}/run_http_server.vsh &
|
||||
HTTP_SERVER_PID=@{dollar}!
|
||||
|
||||
# Print desired output
|
||||
echo "${actor_title} Actor Redis Interface running on redis://localhost:6379"
|
||||
echo "* /queues/${actor_name} -> Action Interface"
|
||||
|
||||
echo ""
|
||||
echo "${actor_title} Actor HTTP Server running on http://localhost:8080"
|
||||
echo "* http://localhost:8080/playground/openapi -> OpenAPI Playground"
|
||||
echo "* http://localhost:8080/openapi -> OpenAPI Interface"
|
||||
# echo "* http://localhost:8080/docs -> Documentation"
|
||||
echo ""
|
||||
|
||||
# Function to clean up when script is killed
|
||||
cleanup() {
|
||||
echo "Stopping background processes..."
|
||||
kill "@{dollar}ACTOR_PID" "@{dollar}HTTP_SERVER_PID" 2>/dev/null
|
||||
wait
|
||||
echo "All processes stopped."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Trap SIGINT (Ctrl+C), SIGTERM, and SIGQUIT to call cleanup
|
||||
trap cleanup SIGINT SIGTERM SIGQUIT
|
||||
|
||||
# Wait for processes to finish
|
||||
wait
|
||||
10
libarchive/baobab/generator/templates/run_actor.vsh.template
Executable file
10
libarchive/baobab/generator/templates/run_actor.vsh.template
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
|
||||
import @{name_snake}
|
||||
|
||||
mut actor := @{name_snake}.new(
|
||||
@{name_snake}.new_@{name_snake}()!,
|
||||
@{name_snake}.configuration
|
||||
)!
|
||||
|
||||
actor.run()!
|
||||
10
libarchive/baobab/generator/templates/run_actor_example.vsh.template
Executable file
10
libarchive/baobab/generator/templates/run_actor_example.vsh.template
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
|
||||
import @{name_snake}
|
||||
|
||||
mut actor := @{name_snake}.new(
|
||||
@{name_snake}.new_@{name_snake}_example()!,
|
||||
@{name_snake}.configuration.example()
|
||||
)!
|
||||
|
||||
actor.run()!
|
||||
7
libarchive/baobab/generator/templates/run_http_server.vsh.template
Executable file
7
libarchive/baobab/generator/templates/run_http_server.vsh.template
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
import @{name_snake}
|
||||
|
||||
@{name_snake}.run_http_server(
|
||||
base_url: 'http://localhost:@{port}'
|
||||
port: @{port}
|
||||
)!
|
||||
@@ -0,0 +1,14 @@
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
@if support_openrpc
|
||||
const openrpc_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/specs/openrpc.json'
|
||||
const openrpc_spec_json = os.read_file(openrpc_spec_path) or { panic(err) }
|
||||
const openrpc_specification = openrpc.decode(openrpc_spec_json)!
|
||||
@end
|
||||
@if support_openapi
|
||||
const openapi_spec_path = '@{dollar}{os.dir(os.dir(@@FILE))}/docs/specs/openapi.json'
|
||||
const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) }
|
||||
const openapi_specification = openapi.json_decode(openapi_spec_json)!
|
||||
@end
|
||||
1
libarchive/baobab/generator/testdata/.gitignore
vendored
Normal file
1
libarchive/baobab/generator/testdata/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pet_store_actor
|
||||
139
libarchive/baobab/generator/write_object_methods_test.v
Normal file
139
libarchive/baobab/generator/write_object_methods_test.v
Normal file
@@ -0,0 +1,139 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import os
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// pub fn (generator ActorGenerator) generate_object_methods(structure code.Struct) []code.Function {
|
||||
// return [
|
||||
// generator.generate_get_method(structure),
|
||||
// // generator.generate_set_method(structure),
|
||||
// // generator.generate_delete_method(structure),
|
||||
// // generator.generate_get_method(structure),
|
||||
// ]
|
||||
// }
|
||||
|
||||
// generate_object_methods generates CRUD actor methods for a provided structure
|
||||
pub fn test_generate_get_method() {
|
||||
generator := ActorGenerator{'test'}
|
||||
actor_struct := code.Struct{
|
||||
name: 'TestActor'
|
||||
fields: [
|
||||
code.StructField{
|
||||
name: 'test_struct_map'
|
||||
typ: code.Type{
|
||||
symbol: 'map[string]&TestStruct'
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
test_struct := code.Struct{
|
||||
name: 'TestStruct'
|
||||
}
|
||||
field := get_child_field(
|
||||
parent: actor_struct
|
||||
child: test_struct
|
||||
)
|
||||
|
||||
method := generator.generate_get_method(
|
||||
actor_name: actor_struct.name
|
||||
actor_field: field
|
||||
root_struct: test_struct
|
||||
)
|
||||
}
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// pub fn (generator ActorGenerator) generate_set_method(structure code.Struct) code.Function {
|
||||
// params_getter := "id := params.get('id')!"
|
||||
// field := generator.get_object_field(structure)
|
||||
// object_getter := 'object := actor.${field.name}[id]'
|
||||
// body := '${params_getter}\n${object_getter}\nreturn object'
|
||||
// get_method := code.Function{
|
||||
// name: 'get_${generator.model_name}'
|
||||
// description: 'gets the ${structure.name} with the given object id'
|
||||
// receiver: code.Param{
|
||||
// name: 'actor'
|
||||
// struct_: generator.actor_struct
|
||||
// }
|
||||
// params: [
|
||||
// code.Param{
|
||||
// name: 'id'
|
||||
// typ: code.Type{
|
||||
// symbol: 'string'
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// result: code.Result{
|
||||
// structure: structure
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// return get_method
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// pub fn (generator ActorGenerator) generate_get_method(structure code.Struct) code.Function {
|
||||
// params_getter := "id := params.get('id')!"
|
||||
// field := generator.get_object_field(structure)
|
||||
// object_getter := 'object := actor.${field.name}[id]'
|
||||
// body := '${params_getter}\n${object_getter}\nreturn object'
|
||||
// get_method := code.Function{
|
||||
// name: 'get_${generator.model_name}'
|
||||
// description: 'gets the ${structure.name} with the given object id'
|
||||
// receiver: code.Param{
|
||||
// name: 'actor'
|
||||
// struct_: generator.actor_struct
|
||||
// }
|
||||
// params: [
|
||||
// code.Param{
|
||||
// name: 'id'
|
||||
// typ: code.Type{
|
||||
// symbol: 'string'
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// result: code.Result{
|
||||
// structure: structure
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// return get_method
|
||||
// }
|
||||
|
||||
// // generate_object_methods generates CRUD actor methods for a provided structure
|
||||
// pub fn (generator ActorGenerator) generate_delete_method(structure code.Struct) code.Function {
|
||||
// params_getter := "id := params.get('id')!"
|
||||
// field := generator.get_object_field(structure)
|
||||
// object_getter := 'object := actor.${field.name}[id]'
|
||||
// body := '${params_getter}\n${object_getter}\nreturn object'
|
||||
// get_method := code.Function{
|
||||
// name: 'get_${generator.model_name}'
|
||||
// description: 'gets the ${structure.name} with the given object id'
|
||||
// receiver: code.Param{
|
||||
// name: 'actor'
|
||||
// struct_: generator.actor_struct
|
||||
// }
|
||||
// params: [
|
||||
// code.Param{
|
||||
// name: 'id'
|
||||
// typ: code.Type{
|
||||
// symbol: 'string'
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// result: code.Result{
|
||||
// structure: structure
|
||||
// }
|
||||
// body: body
|
||||
// }
|
||||
// return get_method
|
||||
// }
|
||||
|
||||
// pub fn (generator ActorGenerator) get_object_field(structure code.Struct) code.StructField {
|
||||
// fields := generator.actor_struct.fields.filter(it.typ.symbol == 'map[string]&${structure.name}')
|
||||
// if fields.len != 1 {
|
||||
// panic('this should never happen')
|
||||
// }
|
||||
// return fields[0]
|
||||
// }
|
||||
33
libarchive/baobab/osis/README.md
Normal file
33
libarchive/baobab/osis/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# OSIS
|
||||
|
||||
Object Storage and Indexing System
|
||||
A system for storing root objects efficiently and indexed by certain fields.
|
||||
|
||||
OSIS comprises of 2 elements:
|
||||
|
||||
- Indexer: responsible for indexing and identifying objects
|
||||
- Storer: responsible of storing in different databases, with varying encodings, and encryption.
|
||||
|
||||
## Indexer
|
||||
|
||||
The indexers primary duty is to be able to create and query sql tables for a given base object specification and it's indices. For instance: I specify a Base Object called Pet, and I specify Pet so that (more on writing specifications here) it's `breed` tag is indexable.
|
||||
|
||||
```
|
||||
struct Pet {
|
||||
breed string @[index]
|
||||
}
|
||||
```
|
||||
|
||||
Given this specification, the indexer is expected to create an sql table with the breed field as a column. This allows the backend to filter and search base objects by their fields. Note that, the object isn't stored on the table, but just it's id. Object storage and modification is handled by the
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
|
||||
## Generic Code
|
||||
|
||||
The solution provided by this module is to create a backend interface with generic CRUD + list + filter methods for root objects that different backends can implement.
|
||||
|
||||
This allows for a single generated actor code to use different backends, without having to generate separate code for each. Having less generated code is less prone to errors, and using the same backend methods for each actor makes it easier modify, fix and add features to the backends. Using the same data manipulation methods in generated code also makes it easier to generate code for the actor as the implementations don't differ for different root objects.
|
||||
|
||||
### Creating a backend
|
||||
8
libarchive/baobab/osis/factory.v
Normal file
8
libarchive/baobab/osis/factory.v
Normal file
@@ -0,0 +1,8 @@
|
||||
module osis
|
||||
|
||||
pub fn new(config OSISConfig) !OSIS {
|
||||
return OSIS{
|
||||
indexer: new_indexer()!
|
||||
storer: new_storer()!
|
||||
}
|
||||
}
|
||||
116
libarchive/baobab/osis/indexer.v
Normal file
116
libarchive/baobab/osis/indexer.v
Normal file
@@ -0,0 +1,116 @@
|
||||
module osis
|
||||
|
||||
import json
|
||||
import db.sqlite
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
pub struct Indexer {
|
||||
db sqlite.DB
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct IndexerConfig {
|
||||
db_path string
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn new_indexer(config IndexerConfig) !Indexer {
|
||||
return Indexer{}
|
||||
}
|
||||
|
||||
// deletes an indexer table belonging to a base object
|
||||
pub fn reset(path string) ! {
|
||||
mut db_file := pathlib.get_file(path: path)!
|
||||
db_file.delete()!
|
||||
}
|
||||
|
||||
pub fn (mut i Indexer) new_generic[T](id u32, object T) !u32 {
|
||||
return i.new(get_table[T](), id, get_indices[T](object))!
|
||||
}
|
||||
|
||||
// new creates a new root object entry in the root_objects table,
|
||||
// and the table belonging to the type of root object with columns for index fields
|
||||
pub fn (mut i Indexer) new(table string, id u32, indices map[string]string) !u32 {
|
||||
insert_query := 'INSERT into ${table} (${indices.keys().join(',')}) values (${indices.values().join(',')})'
|
||||
i.db.exec(insert_query) or {
|
||||
return error('Error inserting object ${id} into table ${table}\n${err}')
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// save the session to redis & mem
|
||||
pub fn (mut backend Indexer) set(obj RootObject) ! {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
// save the session to redis & mem
|
||||
pub fn (mut backend Indexer) delete(id string, obj RootObject) ! {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
pub fn (mut backend Indexer) get(id string, obj RootObject) !RootObject {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
pub fn (mut backend Indexer) get_json(id string, obj RootObject) !string {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
pub fn (mut backend Indexer) list(obj RootObject) ![]u32 {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
// from and to for int f64 time etc.
|
||||
@[params]
|
||||
pub struct FilterParams {
|
||||
// indices map[string]string // map of index values that are being filtered by, in order of priority.
|
||||
limit int // limit to the number of values to be returned, in order of priority
|
||||
fuzzy bool // if fuzzy matching is enabled in matching indices
|
||||
matches_all bool // if results should match all indices or any
|
||||
}
|
||||
|
||||
// filter lists root objects of type T that match provided index parameters and params.
|
||||
pub fn (mut backend Indexer) filter(filter RootObject, params FilterParams) ![]string {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
// create_root_struct_table creates a table for a root_struct with columns for each index field
|
||||
fn (mut backend Indexer) create_root_object_table(object RootObject) ! {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
// deletes an indexer table belonging to a root object
|
||||
fn (mut backend Indexer) delete_table(object RootObject) ! {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
fn (mut backend Indexer) get_table_indices(table_name string) ![]string {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
fn (mut backend Indexer) table_exists(table_name string) !bool {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
// get_table_name returns the name of the table belonging to a root struct
|
||||
fn get_table_name(object RootObject) string {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
// get_table_name returns the name of the table belonging to a root struct
|
||||
fn get_table[T]() string {
|
||||
return typeof[T]()
|
||||
}
|
||||
|
||||
// returns the lists of the indices of a root objects db table, and corresponding values
|
||||
pub fn get_indices[T](object T) map[string]string {
|
||||
mut indices := map[string]string{}
|
||||
$for field in T.fields {
|
||||
if field.attrs.contains('index') {
|
||||
value := object.$(field.name)
|
||||
indices[field.name] = '${value}'
|
||||
}
|
||||
}
|
||||
return indices
|
||||
}
|
||||
16
libarchive/baobab/osis/model.v
Normal file
16
libarchive/baobab/osis/model.v
Normal file
@@ -0,0 +1,16 @@
|
||||
module osis
|
||||
|
||||
pub struct OSIS {
|
||||
pub mut:
|
||||
indexer Indexer // storing indeces
|
||||
storer Storer
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct OSISConfig {
|
||||
pub:
|
||||
directory string
|
||||
name string
|
||||
secret string
|
||||
reset bool
|
||||
}
|
||||
58
libarchive/baobab/osis/osis.v
Normal file
58
libarchive/baobab/osis/osis.v
Normal file
@@ -0,0 +1,58 @@
|
||||
module osis
|
||||
|
||||
import os
|
||||
|
||||
pub fn (mut o OSIS) generic_new[T](obj T) !u32 {
|
||||
id := o.indexer.generic_new[T](obj)!
|
||||
o.storer.generic_new[T](obj)!
|
||||
return id
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) new[T](obj T) !u32 {
|
||||
id := o.storer.new_generic[T](obj)!
|
||||
o.indexer.new_generic[T](id, obj)!
|
||||
return id
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) generic_get[T](id u32) !T {
|
||||
return o.storer.generic_get[T](id)!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) get[T](id u32) !T {
|
||||
return o.storer.generic_get[T](u32(id))!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) generic_set[T](obj T) ! {
|
||||
o.indexer.generic_set[T](obj) or { return error('Failed to set new indices:\n${err}') }
|
||||
o.storer.generic_set[T](obj)!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) generic_delete[T](id u32) ! {
|
||||
o.indexer.generic_delete[T](id)!
|
||||
o.storer.generic_delete[T](id)!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) delete(id u32) ! {
|
||||
o.storer.delete(u32(id))!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) list[T]() ![]T {
|
||||
panic('implement')
|
||||
// ids := o.indexer.generic_list[T]()!
|
||||
// return o.storer.generic_list[T](ids)!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) generic_list[T]() ![]T {
|
||||
ids := o.indexer.generic_list[T]()!
|
||||
return o.storer.generic_list[T](ids)!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) generic_filter[T, D](filter D, params FilterParams) ![]T {
|
||||
ids := o.indexer.generic_filter[T, D](filter, params)!
|
||||
return o.storer.generic_list[T](ids)!
|
||||
}
|
||||
|
||||
pub fn (mut o OSIS) generic_reset[T]() ! {
|
||||
o.indexer.generic_reset[T]()!
|
||||
o.storer.generic_reset[T]()!
|
||||
}
|
||||
135
libarchive/baobab/osis/root_object.v
Normal file
135
libarchive/baobab/osis/root_object.v
Normal file
@@ -0,0 +1,135 @@
|
||||
module osis
|
||||
|
||||
import x.json2
|
||||
|
||||
// describes a root object
|
||||
pub struct RootObject {
|
||||
pub mut:
|
||||
id string
|
||||
name string // Story
|
||||
fields []FieldDescription
|
||||
}
|
||||
|
||||
pub struct FieldDescription {
|
||||
pub mut:
|
||||
name string // name of field
|
||||
typ FieldType
|
||||
value string // value of field
|
||||
is_secret bool // whether field should be encrypted upon storage
|
||||
is_index bool // whether object is searchable by field
|
||||
fts_enabled bool // whether full text search on field is enabled
|
||||
}
|
||||
|
||||
// returns the sql type name of the field
|
||||
pub fn (field FieldDescription) sql_type() string {
|
||||
return match field.typ {
|
||||
.text { 'TEXT' }
|
||||
.number { 'INTEGER' }
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FieldType {
|
||||
number
|
||||
text
|
||||
}
|
||||
|
||||
pub fn (obj RootObject) to_json() string {
|
||||
mut obj_map := map[string]json2.Any{}
|
||||
for field in obj.fields {
|
||||
obj_map[field.name] = field.value
|
||||
}
|
||||
|
||||
return obj_map.str()
|
||||
}
|
||||
|
||||
// returns the lists of the indices of a root objects db table, and corresponding values
|
||||
pub fn (obj RootObject) sql_indices_values() ([]string, []string) {
|
||||
obj_encoded := obj.to_json()
|
||||
obj_val := "'${obj_encoded.replace("'", "''")}'"
|
||||
|
||||
// insert root object into its table
|
||||
mut indices := ['data']
|
||||
mut values := [obj_val]
|
||||
|
||||
for field in obj.fields {
|
||||
if field.name == 'id' {
|
||||
indices << '${field.name}'
|
||||
values << '${field.value}'
|
||||
}
|
||||
|
||||
if field.typ == .text {
|
||||
if field.is_index {
|
||||
indices << '${field.name}'
|
||||
values << "'${field.value}'"
|
||||
}
|
||||
} else if field.typ == .number {
|
||||
if field.is_index {
|
||||
indices << '${field.name}'
|
||||
values << '${field.value}'
|
||||
}
|
||||
}
|
||||
}
|
||||
println('debugzoni ${indices} ${values}')
|
||||
return indices, values
|
||||
}
|
||||
|
||||
// return the description of a given generic
|
||||
pub fn root_object[T](object T) RootObject {
|
||||
mut fields := []FieldDescription{}
|
||||
|
||||
$for field in T.fields {
|
||||
mut typ := FieldType{}
|
||||
$if field.typ is string {
|
||||
typ = .text
|
||||
} $else $if field.typ is int {
|
||||
typ = .number
|
||||
}
|
||||
|
||||
fields << FieldDescription{
|
||||
name: field.name
|
||||
typ: typ
|
||||
value: object.$(field.name).str()
|
||||
is_index: field.attrs.contains('index')
|
||||
is_secret: field.attrs.contains('secret')
|
||||
fts_enabled: field.attrs.contains('fts_enabled')
|
||||
}
|
||||
}
|
||||
return RootObject{
|
||||
name: typeof[T]()
|
||||
fields: fields
|
||||
}
|
||||
}
|
||||
|
||||
// decodes root object into generic struct T
|
||||
pub fn (object RootObject) to_generic[T]() T {
|
||||
mut t := T{}
|
||||
|
||||
$for field in T.fields {
|
||||
field_descrs := object.fields.filter(it.name == field.name)
|
||||
if field_descrs.len == 1 {
|
||||
$if field.typ is int {
|
||||
t.$(field.name) = field_descrs[0].value.int()
|
||||
} $else $if field.is_enum {
|
||||
t.$(field.name) = field_descrs[0].value.int()
|
||||
} $else {
|
||||
t.$(field.name) = field_descrs[0].value
|
||||
}
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
pub fn root_object_from_json(json string) !RootObject {
|
||||
raw_decode := json2.raw_decode(json)!
|
||||
obj_map := raw_decode.as_map()
|
||||
|
||||
mut obj := RootObject{}
|
||||
for key, val in obj_map {
|
||||
obj.fields << FieldDescription{
|
||||
name: key
|
||||
value: val.str()
|
||||
}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
15
libarchive/baobab/osis/storer.v
Normal file
15
libarchive/baobab/osis/storer.v
Normal file
@@ -0,0 +1,15 @@
|
||||
module osis
|
||||
|
||||
import freeflowuniverse.herolib.data.ourdb { OurDB }
|
||||
import os
|
||||
|
||||
pub struct Storer {
|
||||
pub mut:
|
||||
db OurDB
|
||||
}
|
||||
|
||||
pub fn new_storer() !Storer {
|
||||
return Storer{
|
||||
db: ourdb.new()!
|
||||
}
|
||||
}
|
||||
23
libarchive/baobab/osis/storer_generic.v
Normal file
23
libarchive/baobab/osis/storer_generic.v
Normal file
@@ -0,0 +1,23 @@
|
||||
module osis
|
||||
|
||||
import json
|
||||
|
||||
// new creates a new root object entry in the root_objects table,
|
||||
// and the table belonging to the type of root object with columns for index fields
|
||||
pub fn (mut storer Storer) new_generic[T](obj T) !u32 {
|
||||
data := json.encode(obj).bytes()
|
||||
return storer.db.set(data: data)
|
||||
}
|
||||
|
||||
pub fn (mut storer Storer) generic_get[T](id u32) !T {
|
||||
return json.decode(T, storer.db.get(id)!.bytestr())
|
||||
}
|
||||
|
||||
pub fn (mut storer Storer) generic_set[T](obj T) ! {
|
||||
data := json.encode(obj).bytes()
|
||||
return storer.db.set(data: data)
|
||||
}
|
||||
|
||||
pub fn (mut storer Storer) delete(id u32) ! {
|
||||
storer.db.delete(id)!
|
||||
}
|
||||
0
libarchive/baobab/specification/README.md
Normal file
0
libarchive/baobab/specification/README.md
Normal file
180
libarchive/baobab/specification/from_openapi.v
Normal file
180
libarchive/baobab/specification/from_openapi.v
Normal file
@@ -0,0 +1,180 @@
|
||||
module specification
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.code { Struct }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
|
||||
import freeflowuniverse.herolib.schemas.openapi { MediaType, OpenAPI, OperationInfo, Parameter }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec, Example, ExamplePairing, ExampleRef }
|
||||
|
||||
// Helper function: Convert OpenAPI parameter to ContentDescriptor
|
||||
fn openapi_param_to_content_descriptor(param Parameter) ContentDescriptor {
|
||||
return ContentDescriptor{
|
||||
name: param.name
|
||||
summary: param.description
|
||||
description: param.description
|
||||
required: param.required
|
||||
schema: param.schema
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function: Convert OpenAPI parameter to ContentDescriptor
|
||||
fn openapi_param_to_example(param Parameter) ?Example {
|
||||
if param.schema is Schema {
|
||||
if param.schema.example.str() != '' {
|
||||
return Example{
|
||||
name: 'Example ${param.name}'
|
||||
description: 'Example ${param.description}'
|
||||
value: param.schema.example
|
||||
}
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// Helper function: Convert OpenAPI operation to ActorMethod
|
||||
fn openapi_operation_to_actor_method(info OperationInfo) ActorMethod {
|
||||
mut parameters := []ContentDescriptor{}
|
||||
mut example_parameters := []Example{}
|
||||
|
||||
for param in info.operation.parameters {
|
||||
parameters << openapi_param_to_content_descriptor(param)
|
||||
example_parameters << openapi_param_to_example(param) or { continue }
|
||||
}
|
||||
|
||||
if schema_ := info.operation.payload_schema() {
|
||||
// TODO: document assumption
|
||||
schema := Schema{
|
||||
...schema_
|
||||
title: texttools.pascal_case(info.operation.operation_id)
|
||||
}
|
||||
parameters << ContentDescriptor{
|
||||
name: 'data'
|
||||
schema: SchemaRef(schema)
|
||||
}
|
||||
}
|
||||
|
||||
mut success_responses := map[string]MediaType{}
|
||||
|
||||
for code, response in info.operation.responses {
|
||||
if code.starts_with('2') { // Matches all 2xx responses
|
||||
success_responses[code] = response.content['application/json']
|
||||
}
|
||||
}
|
||||
|
||||
if success_responses.len > 1 || success_responses.len == 0 {
|
||||
panic('Actor specification must specify one successful response.')
|
||||
}
|
||||
response_success := success_responses.values()[0]
|
||||
|
||||
mut result := ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
schema: response_success.schema
|
||||
}
|
||||
|
||||
example_result := if response_success.example.str() != '' {
|
||||
Example{
|
||||
name: 'Example response'
|
||||
value: response_success.example
|
||||
}
|
||||
} else {
|
||||
Example{}
|
||||
}
|
||||
|
||||
pairing := if example_result != Example{} || example_parameters.len > 0 {
|
||||
ExamplePairing{
|
||||
params: example_parameters.map(ExampleRef(it))
|
||||
result: ExampleRef(example_result)
|
||||
}
|
||||
} else {
|
||||
ExamplePairing{}
|
||||
}
|
||||
|
||||
mut errors := []ErrorSpec{}
|
||||
for status, response in info.operation.responses {
|
||||
if status.int() >= 400 {
|
||||
error_schema := if response.content.len > 0 {
|
||||
response.content.values()[0].schema
|
||||
} else {
|
||||
Schema{}
|
||||
}
|
||||
errors << ErrorSpec{
|
||||
code: status.int()
|
||||
message: response.description
|
||||
data: error_schema // Extend if error schema is defined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ActorMethod{
|
||||
name: info.operation.operation_id
|
||||
description: info.operation.description
|
||||
summary: info.operation.summary
|
||||
parameters: parameters
|
||||
example: pairing
|
||||
result: result
|
||||
errors: errors
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function: Convert OpenAPI schema to Struct
|
||||
fn openapi_schema_to_struct(name string, schema SchemaRef) Struct {
|
||||
// Assuming schema properties can be mapped to Struct fields
|
||||
return Struct{
|
||||
name: name
|
||||
}
|
||||
}
|
||||
|
||||
// Converts OpenAPI to ActorSpecification
|
||||
pub fn from_openapi(spec_raw OpenAPI) !ActorSpecification {
|
||||
spec := openapi.process(spec_raw)!
|
||||
mut objects := []BaseObject{}
|
||||
|
||||
// get all operations for path as list of tuple [](path_string, http.Method, openapi.Operation)
|
||||
|
||||
// Extract methods from OpenAPI paths
|
||||
// for path, item in spec.paths {
|
||||
// if item.get.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.get, item.get.operation_id, path)
|
||||
// }
|
||||
// if item.post.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.post, item.post.operation_id, path)
|
||||
// }
|
||||
// if item.put.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.put, item.put.operation_id, path)
|
||||
// }
|
||||
// if item.delete.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.delete, item.delete.operation_id, path)
|
||||
// }
|
||||
// if item.patch.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.patch, item.patch.operation_id, path)
|
||||
// }
|
||||
// if item.head.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.head, item.head.operation_id, path)
|
||||
// }
|
||||
// if item.options.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.options, item.options.operation_id, path)
|
||||
// }
|
||||
// if item.trace.operation_id != '' {
|
||||
// methods << openapi_operation_to_actor_method(item.trace, item.trace.operation_id, path)
|
||||
// }
|
||||
// }
|
||||
|
||||
// Extract objects from OpenAPI components.schemas
|
||||
for name, schema in spec.components.schemas {
|
||||
objects << BaseObject{
|
||||
schema: schema as Schema
|
||||
}
|
||||
}
|
||||
|
||||
return ActorSpecification{
|
||||
openapi: spec_raw
|
||||
name: spec.info.title
|
||||
description: spec.info.description
|
||||
structure: Struct{} // Assuming no top-level structure for this use case
|
||||
interfaces: [.openapi] // Default to OpenAPI for input
|
||||
methods: spec.get_operations().map(openapi_operation_to_actor_method(it))
|
||||
objects: objects
|
||||
}
|
||||
}
|
||||
400
libarchive/baobab/specification/from_openapi_test.v
Normal file
400
libarchive/baobab/specification/from_openapi_test.v
Normal file
@@ -0,0 +1,400 @@
|
||||
module specification
|
||||
|
||||
import x.json2 as json
|
||||
import freeflowuniverse.herolib.core.code { Struct }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec }
|
||||
import freeflowuniverse.herolib.schemas.openapi { Components, Info, OpenAPI, Operation, PathItem, ServerSpec }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
|
||||
|
||||
const openapi_spec = OpenAPI{
|
||||
openapi: '3.0.3'
|
||||
info: Info{
|
||||
title: 'Pet Store API'
|
||||
description: 'A sample API for a pet store'
|
||||
version: '1.0.0'
|
||||
}
|
||||
servers: [
|
||||
ServerSpec{
|
||||
url: 'https://api.petstore.example.com/v1'
|
||||
description: 'Production server'
|
||||
},
|
||||
ServerSpec{
|
||||
url: 'https://staging.petstore.example.com/v1'
|
||||
description: 'Staging server'
|
||||
},
|
||||
]
|
||||
paths: {
|
||||
'/pets': PathItem{
|
||||
get: Operation{
|
||||
summary: 'List all pets'
|
||||
operation_id: 'listPets'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
name: 'limit'
|
||||
in_: 'query'
|
||||
description: 'Maximum number of pets to return'
|
||||
required: false
|
||||
schema: Schema{
|
||||
typ: 'integer'
|
||||
format: 'int32'
|
||||
example: 10
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
'200': openapi.ResponseSpec{
|
||||
description: 'A paginated list of pets'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/Pets'
|
||||
}
|
||||
example: json.raw_decode('[
|
||||
{ "id": 1, "name": "Fluffy", "tag": "dog" },
|
||||
{ "id": 2, "name": "Whiskers", "tag": "cat" }
|
||||
]')!
|
||||
}
|
||||
}
|
||||
}
|
||||
'400': openapi.ResponseSpec{
|
||||
description: 'Invalid request'
|
||||
}
|
||||
}
|
||||
}
|
||||
post: Operation{
|
||||
summary: 'Create a new pet'
|
||||
operation_id: 'createPet'
|
||||
request_body: openapi.RequestBody{
|
||||
required: true
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/NewPet'
|
||||
}
|
||||
example: json.raw_decode('{ "name": "Bella", "tag": "dog" }')!
|
||||
}
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
'201': openapi.ResponseSpec{
|
||||
description: 'Pet created'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
}
|
||||
example: json.raw_decode('{ "id": 3, "name": "Bella", "tag": "dog" }')!
|
||||
}
|
||||
}
|
||||
}
|
||||
'400': openapi.ResponseSpec{
|
||||
description: 'Invalid input'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'/pets/{petId}': PathItem{
|
||||
get: Operation{
|
||||
summary: 'Get a pet by ID'
|
||||
operation_id: 'getPet'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
name: 'petId'
|
||||
in_: 'path'
|
||||
description: 'ID of the pet to retrieve'
|
||||
required: true
|
||||
schema: Schema{
|
||||
typ: 'integer'
|
||||
format: 'int64'
|
||||
example: 1
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
'200': openapi.ResponseSpec{
|
||||
description: 'A pet'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
}
|
||||
example: json.raw_decode('{ "id": 1, "name": "Fluffy", "tag": "dog" }')!
|
||||
}
|
||||
}
|
||||
}
|
||||
'404': openapi.ResponseSpec{
|
||||
description: 'Pet not found'
|
||||
}
|
||||
}
|
||||
}
|
||||
delete: Operation{
|
||||
summary: 'Delete a pet by ID'
|
||||
operation_id: 'deletePet'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
name: 'petId'
|
||||
in_: 'path'
|
||||
description: 'ID of the pet to delete'
|
||||
required: true
|
||||
schema: Schema{
|
||||
typ: 'integer'
|
||||
format: 'int64'
|
||||
example: 1
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
'204': openapi.ResponseSpec{
|
||||
description: 'Pet deleted'
|
||||
}
|
||||
'404': openapi.ResponseSpec{
|
||||
description: 'Pet not found'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
components: Components{
|
||||
schemas: {
|
||||
'Pet': SchemaRef(Schema{
|
||||
typ: 'object'
|
||||
required: ['id', 'name']
|
||||
properties: {
|
||||
'id': SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
format: 'int64'
|
||||
})
|
||||
'name': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
})
|
||||
'NewPet': SchemaRef(Schema{
|
||||
typ: 'object'
|
||||
required: ['name']
|
||||
properties: {
|
||||
'name': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
})
|
||||
'Pets': SchemaRef(Schema{
|
||||
typ: 'array'
|
||||
items: SchemaRef(Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actor_spec = ActorSpecification{
|
||||
name: 'Pet Store API'
|
||||
description: 'A sample API for a pet store'
|
||||
structure: Struct{}
|
||||
interfaces: [.openapi]
|
||||
methods: [
|
||||
ActorMethod{
|
||||
name: 'listPets'
|
||||
summary: 'List all pets'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example limit'
|
||||
description: 'Example Maximum number of pets to return'
|
||||
value: 10
|
||||
}),
|
||||
]
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: json.raw_decode('[
|
||||
{"id": 1, "name": "Fluffy", "tag": "dog"},
|
||||
{"id": 2, "name": "Whiskers", "tag": "cat"}
|
||||
]')!
|
||||
})
|
||||
}
|
||||
parameters: [
|
||||
ContentDescriptor{
|
||||
name: 'limit'
|
||||
summary: 'Maximum number of pets to return'
|
||||
description: 'Maximum number of pets to return'
|
||||
required: false
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
format: 'int32'
|
||||
example: 10
|
||||
})
|
||||
},
|
||||
]
|
||||
result: ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
schema: SchemaRef(Reference{
|
||||
ref: '#/components/schemas/Pets'
|
||||
})
|
||||
}
|
||||
errors: [
|
||||
ErrorSpec{
|
||||
code: 400
|
||||
message: 'Invalid request'
|
||||
},
|
||||
]
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'createPet'
|
||||
summary: 'Create a new pet'
|
||||
example: openrpc.ExamplePairing{
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: '[]'
|
||||
})
|
||||
}
|
||||
result: ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
}
|
||||
errors: [
|
||||
ErrorSpec{
|
||||
code: 400
|
||||
message: 'Invalid input'
|
||||
},
|
||||
]
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'getPet'
|
||||
summary: 'Get a pet by ID'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example petId'
|
||||
description: 'Example ID of the pet to retrieve'
|
||||
value: 1
|
||||
}),
|
||||
]
|
||||
result: openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example response'
|
||||
value: json.raw_decode('{"id": 1, "name": "Fluffy", "tag": "dog"}')!
|
||||
})
|
||||
}
|
||||
parameters: [
|
||||
ContentDescriptor{
|
||||
name: 'petId'
|
||||
summary: 'ID of the pet to retrieve'
|
||||
description: 'ID of the pet to retrieve'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
format: 'int64'
|
||||
example: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
schema: SchemaRef(Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
}
|
||||
errors: [
|
||||
ErrorSpec{
|
||||
code: 404
|
||||
message: 'Pet not found'
|
||||
},
|
||||
]
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'deletePet'
|
||||
summary: 'Delete a pet by ID'
|
||||
example: openrpc.ExamplePairing{
|
||||
params: [
|
||||
openrpc.ExampleRef(openrpc.Example{
|
||||
name: 'Example petId'
|
||||
description: 'Example ID of the pet to delete'
|
||||
value: 1
|
||||
}),
|
||||
]
|
||||
}
|
||||
parameters: [
|
||||
ContentDescriptor{
|
||||
name: 'petId'
|
||||
summary: 'ID of the pet to delete'
|
||||
description: 'ID of the pet to delete'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
format: 'int64'
|
||||
example: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: ContentDescriptor{
|
||||
name: 'result'
|
||||
description: 'The response of the operation.'
|
||||
required: true
|
||||
}
|
||||
errors: [
|
||||
ErrorSpec{
|
||||
code: 404
|
||||
message: 'Pet not found'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
objects: [
|
||||
BaseObject{
|
||||
schema: Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
format: 'int64'
|
||||
})
|
||||
'name': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['id', 'name']
|
||||
}
|
||||
},
|
||||
BaseObject{
|
||||
schema: Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['name']
|
||||
}
|
||||
},
|
||||
BaseObject{
|
||||
schema: Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(SchemaRef(Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
}))
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn test_from_openapi() ! {
|
||||
// panic(from_openapi(openapi_spec)!)
|
||||
assert from_openapi(openapi_spec)! == actor_spec
|
||||
}
|
||||
106
libarchive/baobab/specification/from_openrpc.v
Normal file
106
libarchive/baobab/specification/from_openrpc.v
Normal file
@@ -0,0 +1,106 @@
|
||||
module specification
|
||||
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec, Method, OpenRPC }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// Helper function: Convert OpenRPC Method to ActorMethod
|
||||
fn openrpc_method_to_actor_method(method Method) ActorMethod {
|
||||
mut parameters := []ContentDescriptor{}
|
||||
mut errors := []ErrorSpec{}
|
||||
|
||||
// Process parameters
|
||||
for param in method.params {
|
||||
if param is ContentDescriptor {
|
||||
parameters << param
|
||||
} else {
|
||||
panic('Method param should be inflated')
|
||||
}
|
||||
}
|
||||
|
||||
// Process errors
|
||||
for err in method.errors {
|
||||
if err is ErrorSpec {
|
||||
errors << err
|
||||
} else {
|
||||
panic('Method error should be inflated')
|
||||
}
|
||||
}
|
||||
|
||||
if method.result is Reference {
|
||||
panic('Method result should be inflated')
|
||||
}
|
||||
|
||||
return ActorMethod{
|
||||
name: method.name
|
||||
description: method.description
|
||||
summary: method.summary
|
||||
parameters: parameters
|
||||
result: method.result as ContentDescriptor
|
||||
errors: errors
|
||||
}
|
||||
}
|
||||
|
||||
// // Helper function: Extract Structs from OpenRPC Components
|
||||
// fn extract_structs_from_openrpc(openrpc OpenRPC) []Struct {
|
||||
// mut structs := []Struct{}
|
||||
|
||||
// for schema_name, schema in openrpc.components.schemas {
|
||||
// if schema is Schema {
|
||||
// mut fields := []Struct.Field{}
|
||||
// for field_name, field_schema in schema.properties {
|
||||
// if field_schema is Schema {
|
||||
// fields << Struct.Field{
|
||||
// name: field_name
|
||||
// typ: field_schema.to_code() or { panic(err) }
|
||||
// description: field_schema.description
|
||||
// required: field_name in schema.required
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// structs << Struct{
|
||||
// name: schema_name
|
||||
// description: schema.description
|
||||
// fields: fields
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return structs
|
||||
// }
|
||||
|
||||
// Converts OpenRPC to ActorSpecification
|
||||
pub fn from_openrpc(spec OpenRPC) !ActorSpecification {
|
||||
mut methods := []ActorMethod{}
|
||||
mut objects := []BaseObject{}
|
||||
|
||||
// Process methods
|
||||
for method in spec.methods {
|
||||
methods << openrpc_method_to_actor_method(spec.inflate_method(method))
|
||||
}
|
||||
|
||||
// Process objects (schemas)
|
||||
// structs := extract_structs_from_openrpc(spec)
|
||||
for key, schema in spec.components.schemas {
|
||||
if schema is Schema {
|
||||
if schema.typ == 'object' {
|
||||
objects << BaseObject{
|
||||
schema: Schema{
|
||||
...schema
|
||||
title: texttools.pascal_case(key)
|
||||
id: texttools.snake_case(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ActorSpecification{
|
||||
name: spec.info.title
|
||||
description: spec.info.description
|
||||
interfaces: [.openrpc]
|
||||
methods: methods
|
||||
objects: objects
|
||||
}
|
||||
}
|
||||
434
libarchive/baobab/specification/from_openrpc_test.v
Normal file
434
libarchive/baobab/specification/from_openrpc_test.v
Normal file
@@ -0,0 +1,434 @@
|
||||
module specification
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Struct }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor }
|
||||
import freeflowuniverse.herolib.schemas.openapi { Components, Info }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
|
||||
|
||||
const openrpc_spec = openrpc.OpenRPC{
|
||||
openrpc: '1.0.0-rc1'
|
||||
info: openrpc.Info{
|
||||
title: 'Petstore'
|
||||
license: openrpc.License{
|
||||
name: 'MIT'
|
||||
}
|
||||
version: '1.0.0'
|
||||
}
|
||||
servers: [
|
||||
openrpc.Server{
|
||||
name: 'localhost'
|
||||
url: openrpc.RuntimeExpression('http://localhost:8080')
|
||||
},
|
||||
]
|
||||
methods: [
|
||||
openrpc.Method{
|
||||
name: 'list_pets'
|
||||
summary: 'List all pets'
|
||||
params: [
|
||||
openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'limit'
|
||||
description: 'How many items to return at one time (max 100)'
|
||||
required: false
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
minimum: 1
|
||||
})
|
||||
}),
|
||||
]
|
||||
result: openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'pets'
|
||||
description: 'A paged array of pets'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(SchemaRef(Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
}))
|
||||
})
|
||||
})
|
||||
examples: [
|
||||
openrpc.ExamplePairing{
|
||||
name: 'listPetExample'
|
||||
description: 'List pet example'
|
||||
},
|
||||
]
|
||||
},
|
||||
openrpc.Method{
|
||||
name: 'create_pet'
|
||||
summary: 'Create a pet'
|
||||
params: [
|
||||
openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'newPetName'
|
||||
description: 'Name of pet to create'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}),
|
||||
openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'newPetTag'
|
||||
description: 'Pet tag to create'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}),
|
||||
]
|
||||
result: openrpc.ContentDescriptorRef(Reference{
|
||||
ref: '#/components/contentDescriptors/PetId'
|
||||
})
|
||||
examples: [
|
||||
openrpc.ExamplePairing{
|
||||
name: 'createPetExample'
|
||||
description: 'Create pet example'
|
||||
},
|
||||
]
|
||||
},
|
||||
openrpc.Method{
|
||||
name: 'get_pet'
|
||||
summary: 'Info for a specific pet'
|
||||
params: [
|
||||
openrpc.ContentDescriptorRef(Reference{
|
||||
ref: '#/components/contentDescriptors/PetId'
|
||||
}),
|
||||
]
|
||||
result: openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'Expected response to a valid request'
|
||||
schema: SchemaRef(Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
})
|
||||
examples: [
|
||||
openrpc.ExamplePairing{
|
||||
name: 'getPetExample'
|
||||
description: 'Get pet example'
|
||||
},
|
||||
]
|
||||
},
|
||||
openrpc.Method{
|
||||
name: 'update_pet'
|
||||
summary: 'Update a pet'
|
||||
params: [
|
||||
openrpc.ContentDescriptorRef(Reference{
|
||||
ref: '#/components/contentDescriptors/PetId'
|
||||
}),
|
||||
openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'updatedPetName'
|
||||
description: 'New name for the pet'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}),
|
||||
openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'updatedPetTag'
|
||||
description: 'New tag for the pet'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}),
|
||||
]
|
||||
result: openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'Updated pet object'
|
||||
schema: SchemaRef(Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
})
|
||||
examples: [
|
||||
openrpc.ExamplePairing{
|
||||
name: 'updatePetExample'
|
||||
description: 'Update pet example'
|
||||
},
|
||||
]
|
||||
},
|
||||
openrpc.Method{
|
||||
name: 'delete_pet'
|
||||
summary: 'Delete a pet'
|
||||
params: [
|
||||
openrpc.ContentDescriptorRef(Reference{
|
||||
ref: '#/components/contentDescriptors/PetId'
|
||||
}),
|
||||
]
|
||||
result: openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'success'
|
||||
description: 'Boolean indicating success'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'boolean'
|
||||
})
|
||||
})
|
||||
examples: [
|
||||
openrpc.ExamplePairing{
|
||||
name: 'deletePetExample'
|
||||
description: 'Delete pet example'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
components: openrpc.Components{
|
||||
content_descriptors: {
|
||||
'PetId': openrpc.ContentDescriptorRef(ContentDescriptor{
|
||||
name: 'petId'
|
||||
description: 'The ID of the pet'
|
||||
required: true
|
||||
schema: SchemaRef(Reference{
|
||||
ref: '#/components/schemas/PetId'
|
||||
})
|
||||
})
|
||||
}
|
||||
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{
|
||||
name: 'Petstore'
|
||||
structure: Struct{
|
||||
is_pub: false
|
||||
}
|
||||
interfaces: [.openrpc]
|
||||
methods: [
|
||||
ActorMethod{
|
||||
name: 'list_pets'
|
||||
summary: 'List all pets'
|
||||
parameters: [
|
||||
ContentDescriptor{
|
||||
name: 'limit'
|
||||
description: 'How many items to return at one time (max 100)'
|
||||
required: false
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
minimum: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: ContentDescriptor{
|
||||
name: 'pets'
|
||||
description: 'A paged array of pets'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(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',
|
||||
]
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'create_pet'
|
||||
summary: 'Create a pet'
|
||||
parameters: [
|
||||
ContentDescriptor{
|
||||
name: 'newPetName'
|
||||
description: 'Name of pet to create'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
ContentDescriptor{
|
||||
name: 'newPetTag'
|
||||
description: 'Pet tag to create'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
]
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'get_pet'
|
||||
summary: 'Info for a specific pet'
|
||||
result: ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'Expected response to a valid request'
|
||||
schema: 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',
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'update_pet'
|
||||
summary: 'Update a pet'
|
||||
parameters: [
|
||||
ContentDescriptor{
|
||||
name: 'updatedPetName'
|
||||
description: 'New name for the pet'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
ContentDescriptor{
|
||||
name: 'updatedPetTag'
|
||||
description: 'New tag for the pet'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
]
|
||||
result: ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'Updated pet object'
|
||||
schema: 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',
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'delete_pet'
|
||||
summary: 'Delete a pet'
|
||||
result: ContentDescriptor{
|
||||
name: 'success'
|
||||
description: 'Boolean indicating success'
|
||||
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() ! {
|
||||
actor_spec_ := from_openrpc(openrpc_spec)!
|
||||
assert actor_spec_.methods.len == actor_spec.methods.len
|
||||
assert_methods_match(actor_spec_.methods[0], actor_spec.methods[0])
|
||||
|
||||
// assert from_openrpc(openrpc_spec)! == actor_spec
|
||||
}
|
||||
|
||||
fn assert_methods_match(a ActorMethod, b ActorMethod) {
|
||||
// Compare method names
|
||||
assert a.name == b.name, 'Method names do not match: ${a.name} != ${b.name}'
|
||||
|
||||
// Compare summaries
|
||||
assert a.summary == b.summary, 'Method summaries do not match for method ${a.name}.'
|
||||
|
||||
// Compare descriptions
|
||||
assert a.description == b.description, 'Method descriptions do not match for method ${a.name}.'
|
||||
|
||||
// Compare parameters count
|
||||
assert a.parameters.len == b.parameters.len, 'Parameter counts do not match for method ${a.name}.'
|
||||
|
||||
// Compare each parameter
|
||||
for i, param_a in a.parameters {
|
||||
assert_params_match(param_a, b.parameters[i], a.name)
|
||||
}
|
||||
|
||||
// Compare result
|
||||
assert_params_match(a.result, b.result, a.name)
|
||||
}
|
||||
|
||||
fn assert_params_match(a ContentDescriptor, b ContentDescriptor, method_name string) {
|
||||
// Compare parameter names
|
||||
assert a.name == b.name, 'Parameter names do not match in method ${method_name}: ${a.name} != ${b.name}'
|
||||
|
||||
// Compare summaries
|
||||
assert a.summary == b.summary, 'Parameter summaries do not match in method ${method_name}: ${a.name}'
|
||||
|
||||
// Compare descriptions
|
||||
assert a.description == b.description, 'Parameter descriptions do not match in method ${method_name}: ${a.name}'
|
||||
|
||||
// Compare required flags
|
||||
assert a.required == b.required, 'Required flags do not match in method ${method_name}: ${a.name}'
|
||||
|
||||
// Compare schemas
|
||||
// assert_schemas_match(a.schema, b.schema, method_name, a.name)
|
||||
}
|
||||
|
||||
// fn assert_schemas_match(a jsonschema.SchemaRef, b jsonschema.SchemaRef, method_name string, param_name string) {
|
||||
// if a is Schema &&
|
||||
// // Compare schema types
|
||||
// assert a.typ == b.typ, 'Schema types do not match for parameter ${param_name} in method ${method_name}: ${a.typ} != ${b.typ}'
|
||||
|
||||
// // Compare schema titles
|
||||
// assert a.title == b.title, 'Schema titles do not match for parameter ${param_name} in method ${method_name}.'
|
||||
|
||||
// // Compare schema descriptions
|
||||
// assert a.description == b.description, 'Schema descriptions do not match for parameter ${param_name} in method ${method_name}.'
|
||||
|
||||
// // Compare other schema fields as needed (e.g., properties, additional properties, items, etc.)
|
||||
// // Add more checks here if needed for deeper schema comparisons
|
||||
// }
|
||||
205
libarchive/baobab/specification/model.v
Normal file
205
libarchive/baobab/specification/model.v
Normal file
@@ -0,0 +1,205 @@
|
||||
module specification
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Struct }
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec, ExamplePairing }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema }
|
||||
|
||||
pub struct ActorSpecification {
|
||||
pub mut:
|
||||
version string = '1.0.0'
|
||||
openapi ?openapi.OpenAPI
|
||||
openrpc ?openrpc.OpenRPC
|
||||
name string @[omitempty]
|
||||
description string @[omitempty]
|
||||
structure Struct @[omitempty]
|
||||
interfaces []ActorInterface @[omitempty]
|
||||
methods []ActorMethod @[omitempty]
|
||||
objects []BaseObject @[omitempty]
|
||||
}
|
||||
|
||||
pub enum ActorInterface {
|
||||
openrpc
|
||||
openapi
|
||||
webui
|
||||
command
|
||||
http
|
||||
}
|
||||
|
||||
pub struct ActorMethod {
|
||||
pub:
|
||||
name string @[omitempty]
|
||||
description string @[omitempty]
|
||||
summary string
|
||||
example ExamplePairing
|
||||
parameters []ContentDescriptor
|
||||
result ContentDescriptor
|
||||
errors []ErrorSpec
|
||||
category MethodCategory
|
||||
}
|
||||
|
||||
pub struct BaseObject {
|
||||
pub mut:
|
||||
schema Schema
|
||||
new_method ?ActorMethod
|
||||
get_method ?ActorMethod
|
||||
set_method ?ActorMethod
|
||||
delete_method ?ActorMethod
|
||||
list_method ?ActorMethod
|
||||
filter_method ?ActorMethod
|
||||
other_methods []ActorMethod
|
||||
}
|
||||
|
||||
pub enum MethodCategory {
|
||||
base_object_new
|
||||
base_object_get
|
||||
base_object_set
|
||||
base_object_delete
|
||||
base_object_list
|
||||
other
|
||||
}
|
||||
|
||||
// returns whether method belongs to a given base object
|
||||
// TODO: link to more info about base object methods
|
||||
fn (m ActorMethod) belongs_to_object(obj BaseObject) bool {
|
||||
base_obj_is_param := m.parameters
|
||||
.filter(it.schema is Schema)
|
||||
.map(it.schema as Schema)
|
||||
.any(it.id == obj.schema.id)
|
||||
|
||||
base_obj_is_result := if m.result.schema is Schema {
|
||||
m.result.schema.id == obj.schema.id
|
||||
} else {
|
||||
ref := m.result.schema as Reference
|
||||
ref.ref.all_after_last('/') == obj.name()
|
||||
}
|
||||
|
||||
return base_obj_is_param || base_obj_is_result
|
||||
}
|
||||
|
||||
pub fn (s ActorSpecification) validate() ActorSpecification {
|
||||
mut validated_objects := []BaseObject{}
|
||||
for obj_ in s.objects {
|
||||
mut obj := obj_
|
||||
if obj.schema.id == '' {
|
||||
obj.schema.id = obj.schema.title
|
||||
}
|
||||
methods := s.methods.filter(it.belongs_to_object(obj))
|
||||
|
||||
if m := methods.filter(it.is_new_method())[0] {
|
||||
obj.new_method = m
|
||||
}
|
||||
if m := methods.filter(it.is_set_method())[0] {
|
||||
obj.set_method = m
|
||||
}
|
||||
if m := methods.filter(it.is_get_method())[0] {
|
||||
obj.get_method = m
|
||||
}
|
||||
if m := methods.filter(it.is_delete_method())[0] {
|
||||
obj.delete_method = m
|
||||
}
|
||||
if m := methods.filter(it.is_list_method())[0] {
|
||||
obj.list_method = m
|
||||
}
|
||||
validated_objects << BaseObject{
|
||||
...obj
|
||||
other_methods: methods.filter(!it.is_crudlf_method())
|
||||
}
|
||||
}
|
||||
return ActorSpecification{
|
||||
...s
|
||||
objects: validated_objects
|
||||
}
|
||||
}
|
||||
|
||||
// method category returns what category a method falls under
|
||||
pub fn (s ActorSpecification) method_type(method ActorMethod) MethodCategory {
|
||||
return if s.is_base_object_new_method(method) {
|
||||
.base_object_new
|
||||
} else if s.is_base_object_get_method(method) {
|
||||
.base_object_get
|
||||
} else if s.is_base_object_set_method(method) {
|
||||
.base_object_set
|
||||
} else if s.is_base_object_delete_method(method) {
|
||||
.base_object_delete
|
||||
} else if s.is_base_object_list_method(method) {
|
||||
.base_object_list
|
||||
} else {
|
||||
.other
|
||||
}
|
||||
}
|
||||
|
||||
// a base object method is a method that is a
|
||||
// CRUD+list+filter method of a base object
|
||||
fn (s ActorSpecification) is_base_object_method(method ActorMethod) bool {
|
||||
base_obj_is_param := method.parameters
|
||||
.filter(it.schema is Schema)
|
||||
.map(it.schema as Schema)
|
||||
.any(it.id in s.objects.map(it.schema.id))
|
||||
|
||||
base_obj_is_result := if method.result.schema is Schema {
|
||||
method.result.schema.id in s.objects.map(it.name())
|
||||
} else {
|
||||
ref := method.result.schema as Reference
|
||||
ref.ref.all_after_last('/') in s.objects.map(it.name())
|
||||
}
|
||||
|
||||
return base_obj_is_param || base_obj_is_result
|
||||
}
|
||||
|
||||
fn (m ActorMethod) is_new_method() bool {
|
||||
return m.name.starts_with('new')
|
||||
}
|
||||
|
||||
fn (m ActorMethod) is_get_method() bool {
|
||||
return m.name.starts_with('get')
|
||||
}
|
||||
|
||||
fn (m ActorMethod) is_set_method() bool {
|
||||
return m.name.starts_with('set')
|
||||
}
|
||||
|
||||
fn (m ActorMethod) is_delete_method() bool {
|
||||
return m.name.starts_with('delete')
|
||||
}
|
||||
|
||||
fn (m ActorMethod) is_list_method() bool {
|
||||
return m.name.starts_with('list')
|
||||
}
|
||||
|
||||
fn (m ActorMethod) is_filter_method() bool {
|
||||
return m.name.starts_with('filter')
|
||||
}
|
||||
|
||||
fn (m ActorMethod) is_crudlf_method() bool {
|
||||
return m.is_new_method() || m.is_get_method() || m.is_set_method() || m.is_delete_method()
|
||||
|| m.is_list_method() || m.is_filter_method()
|
||||
}
|
||||
|
||||
pub fn (o BaseObject) name() string {
|
||||
return if o.schema.id.trim_space() != '' {
|
||||
o.schema.id.trim_space()
|
||||
} else {
|
||||
o.schema.title.trim_space()
|
||||
}
|
||||
}
|
||||
|
||||
fn (s ActorSpecification) is_base_object_new_method(method ActorMethod) bool {
|
||||
return s.is_base_object_method(method) && method.name.starts_with('new')
|
||||
}
|
||||
|
||||
fn (s ActorSpecification) is_base_object_get_method(method ActorMethod) bool {
|
||||
return s.is_base_object_method(method) && method.name.starts_with('get')
|
||||
}
|
||||
|
||||
fn (s ActorSpecification) is_base_object_set_method(method ActorMethod) bool {
|
||||
return s.is_base_object_method(method) && method.name.starts_with('set')
|
||||
}
|
||||
|
||||
fn (s ActorSpecification) is_base_object_delete_method(method ActorMethod) bool {
|
||||
return s.is_base_object_method(method) && method.name.starts_with('delete')
|
||||
}
|
||||
|
||||
fn (s ActorSpecification) is_base_object_list_method(method ActorMethod) bool {
|
||||
return s.is_base_object_method(method) && method.name.starts_with('list')
|
||||
}
|
||||
97
libarchive/baobab/specification/to_openapi.v
Normal file
97
libarchive/baobab/specification/to_openapi.v
Normal file
@@ -0,0 +1,97 @@
|
||||
module specification
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
|
||||
import freeflowuniverse.herolib.schemas.openapi { Components, Info, MediaType, OpenAPI, Operation, Parameter, PathItem, ResponseSpec, ServerSpec }
|
||||
import net.http
|
||||
|
||||
// Converts ActorSpecification to OpenAPI
|
||||
pub fn (s ActorSpecification) to_openapi() OpenAPI {
|
||||
if openapi_spec := s.openapi {
|
||||
return openapi_spec
|
||||
}
|
||||
mut paths := map[string]PathItem{}
|
||||
|
||||
// Map ActorMethods to paths
|
||||
for method in s.methods {
|
||||
op := method.to_openapi_operation()
|
||||
paths['${method.http_path()}'] = match method.http_method() {
|
||||
.get {
|
||||
PathItem{
|
||||
get: op
|
||||
}
|
||||
}
|
||||
else {
|
||||
panic('unsupported http method')
|
||||
}
|
||||
}
|
||||
// Assign operation to corresponding HTTP method
|
||||
// TODO: what about other verbs
|
||||
}
|
||||
|
||||
mut schemas := map[string]SchemaRef{}
|
||||
for object in s.objects {
|
||||
schemas[object.schema.id] = object.to_schema()
|
||||
}
|
||||
|
||||
return OpenAPI{
|
||||
openapi: '3.0.0'
|
||||
info: Info{
|
||||
title: s.name
|
||||
summary: s.description
|
||||
description: s.description
|
||||
version: '1.0.0'
|
||||
}
|
||||
servers: [
|
||||
ServerSpec{
|
||||
url: 'http://localhost:8080'
|
||||
description: 'Default server'
|
||||
},
|
||||
]
|
||||
paths: paths
|
||||
components: Components{
|
||||
schemas: schemas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (bo BaseObject) to_schema() Schema {
|
||||
return Schema{}
|
||||
}
|
||||
|
||||
fn (m ActorMethod) http_path() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
fn (m ActorMethod) http_method() http.Method {
|
||||
return .get
|
||||
}
|
||||
|
||||
fn (method ActorMethod) to_openapi_operation() Operation {
|
||||
mut op := Operation{
|
||||
summary: method.summary
|
||||
description: method.description
|
||||
operation_id: method.name
|
||||
}
|
||||
|
||||
// Convert parameters to OpenAPI format
|
||||
for param in method.parameters {
|
||||
op.parameters << Parameter{
|
||||
name: param.name
|
||||
in_: 'query' // Default to query parameters; adjust based on function context
|
||||
description: param.description
|
||||
required: param.required
|
||||
schema: param.schema
|
||||
}
|
||||
}
|
||||
|
||||
// if method.is_void()
|
||||
op.responses['200'] = ResponseSpec{
|
||||
description: method.description
|
||||
content: {
|
||||
'application/json': MediaType{
|
||||
schema: method.result.schema
|
||||
}
|
||||
}
|
||||
}
|
||||
return op
|
||||
}
|
||||
183
libarchive/baobab/specification/to_openapi_test.v
Normal file
183
libarchive/baobab/specification/to_openapi_test.v
Normal file
@@ -0,0 +1,183 @@
|
||||
module specification
|
||||
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
|
||||
const actor_spec = ActorSpecification{
|
||||
name: 'Petstore'
|
||||
structure: code.Struct{
|
||||
is_pub: false
|
||||
}
|
||||
interfaces: [.openrpc]
|
||||
methods: [
|
||||
ActorMethod{
|
||||
name: 'list_pets'
|
||||
summary: 'List all pets'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'limit'
|
||||
description: 'How many items to return at one time (max 100)'
|
||||
required: false
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
minimum: 1
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pets'
|
||||
description: 'A paged array of pets'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(SchemaRef(Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/PetId'
|
||||
})
|
||||
'name': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: [
|
||||
'id',
|
||||
'name',
|
||||
]
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'create_pet'
|
||||
summary: 'Create a pet'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'newPetName'
|
||||
description: 'Name of pet to create'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'newPetTag'
|
||||
description: 'Pet tag to create'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
]
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'get_pet'
|
||||
summary: 'Info for a specific pet'
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'Expected response to a valid request'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/PetId'
|
||||
})
|
||||
'name': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: [
|
||||
'id',
|
||||
'name',
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'update_pet'
|
||||
summary: 'Update a pet'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'updatedPetName'
|
||||
description: 'New name for the pet'
|
||||
required: true
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'updatedPetTag'
|
||||
description: 'New tag for the pet'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'Updated pet object'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'id': SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/PetId'
|
||||
})
|
||||
'name': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'tag': SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: [
|
||||
'id',
|
||||
'name',
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
ActorMethod{
|
||||
name: 'delete_pet'
|
||||
summary: 'Delete a pet'
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'success'
|
||||
description: 'Boolean indicating success'
|
||||
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
|
||||
pub fn test_specification_to_openapi() {
|
||||
panic(actor_spec.to_openapi())
|
||||
}
|
||||
71
libarchive/baobab/specification/to_openrpc.v
Normal file
71
libarchive/baobab/specification/to_openrpc.v
Normal file
@@ -0,0 +1,71 @@
|
||||
module specification
|
||||
|
||||
import freeflowuniverse.herolib.schemas.openrpc { Components, OpenRPC }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { SchemaRef }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen
|
||||
|
||||
// pub fn from_openrpc(spec openrpc.OpenRPC) !ActorSpecification {
|
||||
// // Extract Actor metadata from OpenRPC info
|
||||
// // actor_name := openrpc_doc.info.title
|
||||
// // actor_description := openrpc_doc.info.description
|
||||
|
||||
// // // Generate methods
|
||||
// // mut methods := []ActorMethod{}
|
||||
// // for method in openrpc_doc.methods {
|
||||
// // method_code := method.to_code()! // Using provided to_code function
|
||||
// // methods << ActorMethod{
|
||||
// // name: method.name
|
||||
// // func: method_code
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // // Generate BaseObject structs from schemas
|
||||
// // mut objects := []BaseObject{}
|
||||
// // for key, schema_ref in openrpc_doc.components.schemas {
|
||||
// // struct_obj := schema_ref.to_code()! // Assuming schema_ref.to_code() converts schema to Struct
|
||||
// // // objects << BaseObject{
|
||||
// // // structure: code.Struct{
|
||||
// // // name: struct_obj.name
|
||||
// // // }
|
||||
// // // }
|
||||
// // }
|
||||
|
||||
// // Build the Actor struct
|
||||
// return ActorSpecification{
|
||||
// // name: actor_name
|
||||
// // description: actor_description
|
||||
// // methods: methods
|
||||
// // objects: objects
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn (specification ActorSpecification) to_openrpc() OpenRPC {
|
||||
mut schemas := map[string]SchemaRef{}
|
||||
for obj in specification.objects {
|
||||
schemas[obj.schema.id] = obj.schema
|
||||
// for child in obj.children {
|
||||
// schemas[child.name] = struct_to_schema(child)
|
||||
// }
|
||||
}
|
||||
return OpenRPC{
|
||||
info: openrpc.Info{
|
||||
title: specification.name.title()
|
||||
version: '1.0.0'
|
||||
}
|
||||
methods: specification.methods.map(method_to_openrpc_method(it))
|
||||
components: Components{
|
||||
schemas: schemas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn method_to_openrpc_method(method ActorMethod) openrpc.Method {
|
||||
return openrpc.Method{
|
||||
name: method.name
|
||||
summary: method.summary
|
||||
description: method.description
|
||||
params: method.parameters.map(openrpc.ContentDescriptorRef(it))
|
||||
result: openrpc.ContentDescriptorRef(method.result)
|
||||
errors: method.errors.map(openrpc.ErrorRef(it))
|
||||
}
|
||||
}
|
||||
140
libarchive/baobab/stage/README.md
Normal file
140
libarchive/baobab/stage/README.md
Normal file
@@ -0,0 +1,140 @@
|
||||
|
||||
# Stage Module
|
||||
|
||||
The **Stage** module is a core component of the **Baobab** (Base Object and Actor Backend) library. It provides the infrastructure for handling RPC-based communication and managing the lifecycle of **Actors** and **Actions**. This module facilitates processing incoming requests, converting them to actions, and ensuring their correct execution.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The **Stage** module operates based on the following architecture:
|
||||
|
||||
1. **RPC Request Handling**:
|
||||
- An **Interface Handler** receives an RPC request. Supported interfaces include:
|
||||
- **OpenRPC**
|
||||
- **JSON-RPC**
|
||||
- **OpenAPI**
|
||||
|
||||
2. **Action Creation**:
|
||||
- The **Interface Handler** converts the incoming request into an **Action**, which represents the task to be executed.
|
||||
|
||||
3. **Action Execution**:
|
||||
- The **Interface Handler** passes the **Action** to the **Director** for coordinated execution.
|
||||
- (Note: Currently, the **Director** is not fully implemented. Actions are passed directly to the **Actor** for execution.)
|
||||
|
||||
4. **Actor Processing**:
|
||||
- The **Actor** uses its `act` method to execute the **Action**.
|
||||
- The result of the **Action** is stored in its `result` field, and the **Action** is returned.
|
||||
|
||||
5. **RPC Response Generation**:
|
||||
- The **Interface Handler** converts the resulting **Action** back into the appropriate RPC response format and returns it.
|
||||
|
||||
---
|
||||
|
||||
## Key Components
|
||||
|
||||
### **Interface Handlers**
|
||||
- **Responsibilities**:
|
||||
- Receive and parse incoming RPC requests.
|
||||
- Convert requests into **Actions**.
|
||||
- Convert resulting **Actions** into appropriate RPC responses.
|
||||
- Files:
|
||||
- `interfaces/jsonrpc_interface.v`
|
||||
- `interfaces/openapi_interface.v`
|
||||
|
||||
### **Director**
|
||||
- **Responsibilities**:
|
||||
- (Planned) Coordinate the execution of **Actions**.
|
||||
- Handle retries, timeouts, and error recovery.
|
||||
- File:
|
||||
- `director.v`
|
||||
|
||||
### **Actors**
|
||||
- **Responsibilities**:
|
||||
- Execute **Actions** using their `act` method.
|
||||
- Populate the `result` field of **Actions** with the execution result.
|
||||
- File:
|
||||
- `actor.v`
|
||||
|
||||
### **Actions**
|
||||
- **Responsibilities**:
|
||||
- Represent tasks to be executed by **Actors**.
|
||||
- Carry results back after execution.
|
||||
- File:
|
||||
- `action.v`
|
||||
|
||||
### **Executor**
|
||||
- **Responsibilities**:
|
||||
- Manage the assignment of **Actions** to **Actors**.
|
||||
- File:
|
||||
- `executor.v`
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
stage/
|
||||
interfaces/
|
||||
jsonrpc_interface.v # Converts JSON-RPC requests to Actions
|
||||
openapi_interface.v # Converts OpenAPI requests to Actions
|
||||
actor.v # Defines the Actor and its behavior
|
||||
action.v # Defines the Action structure and utilities
|
||||
executor.v # Executes Actions on Actors
|
||||
director.v # (Planned) Coordinates actors, actions, and retries
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow Example
|
||||
|
||||
### 1. Receiving an RPC Request
|
||||
An RPC request is received by an interface handler:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "doSomething",
|
||||
"params": { "key": "value" },
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Converting the Request to an Action
|
||||
The interface handler converts the request into an **Action**:
|
||||
|
||||
```v
|
||||
action := jsonrpc_interface.jsonrpc_to_action(request)
|
||||
```
|
||||
|
||||
### 3. Executing the Action
|
||||
The action is passed directly to an **Actor** for execution:
|
||||
|
||||
```v
|
||||
actor := MyActor{id: "actor-1"}
|
||||
resulting_action := actor.act(action)
|
||||
```
|
||||
|
||||
### 4. Returning the RPC Response
|
||||
The interface handler converts the resulting **Action** back into a JSON-RPC response:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"result": { "status": "success", "data": "..." },
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Improvements
|
||||
|
||||
- **Director Implementation**:
|
||||
- Add retries and timeout handling for actions.
|
||||
- Provide better coordination for complex workflows.
|
||||
|
||||
- **Enhanced Interfaces**:
|
||||
- Add support for more RPC protocols.
|
||||
|
||||
---
|
||||
|
||||
This module is a crucial building block of the **Baobab** library, designed to streamline RPC-based communication and task execution with flexibility and scalability.
|
||||
15
libarchive/baobab/stage/action.v
Normal file
15
libarchive/baobab/stage/action.v
Normal file
@@ -0,0 +1,15 @@
|
||||
module stage
|
||||
|
||||
// import freeflowuniverse.herolib.core.smartid
|
||||
|
||||
pub struct Action {
|
||||
pub mut:
|
||||
id string
|
||||
name string
|
||||
priority int = 10 // 0 is highest, do 10 as default
|
||||
params string // json encoded params
|
||||
result string // can be used to remember outputs
|
||||
// run bool = true // certain actions can be defined but meant to be executed directly
|
||||
comments string
|
||||
done bool // if done then no longer need to process
|
||||
}
|
||||
47
libarchive/baobab/stage/action_client.v
Normal file
47
libarchive/baobab/stage/action_client.v
Normal file
@@ -0,0 +1,47 @@
|
||||
module stage
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
// Processor struct for managing procedure calls
|
||||
pub struct Client {
|
||||
pub mut:
|
||||
rpc redisclient.RedisRpc // Redis RPC mechanism
|
||||
}
|
||||
|
||||
// Parameters for processing a procedure call
|
||||
@[params]
|
||||
pub struct Params {
|
||||
pub:
|
||||
timeout int // Timeout in seconds
|
||||
}
|
||||
|
||||
pub struct ClientConfig {
|
||||
ActorConfig
|
||||
pub:
|
||||
redis_url string = 'localhost:6379' // url to redis server running
|
||||
}
|
||||
|
||||
pub fn new_client(config ActorConfig) !Client {
|
||||
mut redis := redisclient.new(config.redis_url)!
|
||||
mut rpc_q := redis.rpc_get(config.redis_queue_name())
|
||||
|
||||
return Client{
|
||||
rpc: rpc_q
|
||||
}
|
||||
}
|
||||
|
||||
// Process the procedure call
|
||||
pub fn (mut p Client) call_to_action(action Action, params Params) !Action {
|
||||
// Use RedisRpc's `call` to send the call and wait for the response
|
||||
response_data := p.rpc.call(redisclient.RPCArgs{
|
||||
cmd: action.name
|
||||
data: action.params
|
||||
timeout: u64(params.timeout * 1000) // Convert seconds to milliseconds
|
||||
wait: true
|
||||
})!
|
||||
|
||||
return Action{
|
||||
...action
|
||||
result: response_data
|
||||
}
|
||||
}
|
||||
79
libarchive/baobab/stage/actor.v
Normal file
79
libarchive/baobab/stage/actor.v
Normal file
@@ -0,0 +1,79 @@
|
||||
module stage
|
||||
|
||||
import freeflowuniverse.herolib.baobab.osis { OSIS }
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
@[heap]
|
||||
pub interface IActor {
|
||||
name string
|
||||
mut:
|
||||
act(Action) !Action
|
||||
}
|
||||
|
||||
pub struct Actor {
|
||||
ActorConfig
|
||||
mut:
|
||||
osis OSIS
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ActorConfig {
|
||||
pub:
|
||||
name string
|
||||
version string
|
||||
redis_url string = 'localhost:6379'
|
||||
}
|
||||
|
||||
pub fn (config ActorConfig) redis_queue_name() string {
|
||||
mut str := 'actor_${config.name}'
|
||||
if config.version != '' {
|
||||
str += '_${config.version}'
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
pub fn new_actor(config ActorConfig) !Actor {
|
||||
return Actor{
|
||||
ActorConfig: config
|
||||
osis: osis.new()!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (a ActorConfig) get_redis_rpc() !redisclient.RedisRpc {
|
||||
mut redis := redisclient.new(a.redis_url)!
|
||||
return redis.rpc_get(a.redis_queue_name())
|
||||
}
|
||||
|
||||
pub fn (a ActorConfig) version(v string) ActorConfig {
|
||||
return ActorConfig{
|
||||
...a
|
||||
version: v
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (a ActorConfig) example() ActorConfig {
|
||||
return ActorConfig{
|
||||
...a
|
||||
version: 'example'
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut a IActor) handle(method string, data string) !string {
|
||||
action := a.act(
|
||||
name: method
|
||||
params: data
|
||||
)!
|
||||
return action.result
|
||||
}
|
||||
|
||||
// // Actor listens to the Redis queue for method invocations
|
||||
// pub fn (mut a IActor) run() ! {
|
||||
// mut redis := redisclient.new('localhost:6379') or { panic(err) }
|
||||
// mut rpc := redis.rpc_get(a.name)
|
||||
|
||||
// println('Actor started and listening for tasks...')
|
||||
// for {
|
||||
// rpc.process(a.handle)!
|
||||
// time.sleep(time.millisecond * 100) // Prevent CPU spinning
|
||||
// }
|
||||
// }
|
||||
1
libarchive/baobab/stage/config.v
Normal file
1
libarchive/baobab/stage/config.v
Normal file
@@ -0,0 +1 @@
|
||||
module stage
|
||||
33
libarchive/baobab/stage/error.v
Normal file
33
libarchive/baobab/stage/error.v
Normal file
@@ -0,0 +1,33 @@
|
||||
module stage
|
||||
|
||||
// Error struct for error handling
|
||||
pub struct ActionError {
|
||||
reason ErrorReason
|
||||
}
|
||||
|
||||
// Enum for different error reasons
|
||||
pub enum ErrorReason {
|
||||
timeout
|
||||
serialization_failed
|
||||
deserialization_failed
|
||||
enqueue_failed
|
||||
}
|
||||
|
||||
pub fn (err ActionError) code() int {
|
||||
return match err.reason {
|
||||
.timeout { 408 } // HTTP 408 Request Timeout
|
||||
.serialization_failed { 500 } // HTTP 500 Internal Server Error
|
||||
.deserialization_failed { 500 } // HTTP 500 Internal Server Error
|
||||
.enqueue_failed { 503 } // HTTP 503 Service Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (err ActionError) msg() string {
|
||||
explanation := match err.reason {
|
||||
.timeout { 'The procedure call timed out.' }
|
||||
.serialization_failed { 'Failed to serialize the procedure call.' }
|
||||
.deserialization_failed { 'Failed to deserialize the procedure response.' }
|
||||
.enqueue_failed { 'Failed to enqueue the procedure response.' }
|
||||
}
|
||||
return 'Procedure failed: ${explanation}'
|
||||
}
|
||||
16
libarchive/baobab/stage/interfaces/jsonrpc.v
Normal file
16
libarchive/baobab/stage/interfaces/jsonrpc.v
Normal file
@@ -0,0 +1,16 @@
|
||||
module interfaces
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.baobab.stage { Action }
|
||||
|
||||
pub fn action_from_jsonrpc_request(request jsonrpc.Request) Action {
|
||||
return Action{
|
||||
id: request.id
|
||||
name: request.method
|
||||
params: request.params
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action_to_jsonrpc_response(action Action) jsonrpc.Response {
|
||||
return jsonrpc.new_response(action.id, action.result)
|
||||
}
|
||||
48
libarchive/baobab/stage/interfaces/openapi.v
Normal file
48
libarchive/baobab/stage/interfaces/openapi.v
Normal file
@@ -0,0 +1,48 @@
|
||||
module interfaces
|
||||
|
||||
import rand
|
||||
import x.json2 as json { Any }
|
||||
import freeflowuniverse.herolib.baobab.stage { Action, Client }
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
|
||||
pub struct OpenAPIInterface {
|
||||
pub mut:
|
||||
client Client
|
||||
}
|
||||
|
||||
pub fn new_openapi_interface(client Client) &OpenAPIInterface {
|
||||
return &OpenAPIInterface{client}
|
||||
}
|
||||
|
||||
pub fn (mut i OpenAPIInterface) handle(request openapi.Request) !openapi.Response {
|
||||
// Convert incoming OpenAPI request to a procedure call
|
||||
action := action_from_openapi_request(request)
|
||||
response := i.client.call_to_action(action) or { return err }
|
||||
return action_to_openapi_response(response)
|
||||
}
|
||||
|
||||
pub fn action_from_openapi_request(request openapi.Request) Action {
|
||||
mut params := []Any{}
|
||||
if request.arguments.len > 0 {
|
||||
params << request.arguments.values()
|
||||
}
|
||||
if request.body != '' {
|
||||
params << request.body
|
||||
}
|
||||
if request.parameters.len > 0 {
|
||||
params << json.encode(request.parameters)
|
||||
}
|
||||
|
||||
return Action{
|
||||
id: rand.uuid_v4()
|
||||
name: request.operation.operation_id
|
||||
params: json.encode(params.str())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action_to_openapi_response(action Action) openapi.Response {
|
||||
return openapi.Response{
|
||||
body: action.result
|
||||
}
|
||||
}
|
||||
29
libarchive/baobab/stage/interfaces/openrpc.v
Normal file
29
libarchive/baobab/stage/interfaces/openrpc.v
Normal file
@@ -0,0 +1,29 @@
|
||||
module interfaces
|
||||
|
||||
import freeflowuniverse.herolib.baobab.stage { Client }
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// handler for test echoes JSONRPC Request as JSONRPC Response
|
||||
fn handler(request jsonrpc.Request) !jsonrpc.Response {
|
||||
return jsonrpc.Response{
|
||||
jsonrpc: request.jsonrpc
|
||||
id: request.id
|
||||
result: request.params
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenRPCInterface {
|
||||
pub mut:
|
||||
client Client
|
||||
}
|
||||
|
||||
pub fn new_openrpc_interface(client Client) &OpenRPCInterface {
|
||||
return &OpenRPCInterface{client}
|
||||
}
|
||||
|
||||
pub fn (mut i OpenRPCInterface) handle(request jsonrpc.Request) !jsonrpc.Response {
|
||||
// Convert incoming OpenAPI request to a procedure call
|
||||
action := action_from_jsonrpc_request(request)
|
||||
response := i.client.call_to_action(action)!
|
||||
return action_to_jsonrpc_response(response)
|
||||
}
|
||||
91
libarchive/baobab/stage/interfaces/reflection_openapi.v
Normal file
91
libarchive/baobab/stage/interfaces/reflection_openapi.v
Normal file
@@ -0,0 +1,91 @@
|
||||
module interfaces
|
||||
|
||||
// import os
|
||||
// import time
|
||||
// import veb
|
||||
// import x.json2 {Any}
|
||||
// import net.http
|
||||
import freeflowuniverse.herolib.baobab.stage { Action }
|
||||
import freeflowuniverse.herolib.schemas.openapi { Request }
|
||||
|
||||
pub fn openapi_request_to_action(request Request) Action {
|
||||
// // Convert incoming OpenAPI request to a procedure call
|
||||
// mut params := []Any{}
|
||||
|
||||
// if request.arguments.len > 0 {
|
||||
// params << request.arguments.values().map(it.str()).clone()
|
||||
// }
|
||||
|
||||
// if request.body != '' {
|
||||
// params << request.body
|
||||
// }
|
||||
|
||||
// if request.parameters != '' {
|
||||
// params << request.body
|
||||
// }
|
||||
|
||||
// if request.parameters.len != 0 {
|
||||
// mut param_map := map[string]Any{} // Store parameters with correct types
|
||||
|
||||
// for param_name, param_value in request.parameters {
|
||||
// operation_param := request.operation.parameters.filter(it.name == param_name)
|
||||
// if operation_param.len > 0 {
|
||||
// param_schema := operation_param[0].schema as Schema
|
||||
// param_type := param_schema.typ
|
||||
// param_format := param_schema.format
|
||||
|
||||
// // Convert parameter value to corresponding type
|
||||
// match param_type {
|
||||
// 'integer' {
|
||||
// match param_format {
|
||||
// 'int32' {
|
||||
// param_map[param_name] = param_value.int() // Convert to int
|
||||
// }
|
||||
// 'int64' {
|
||||
// param_map[param_name] = param_value.i64() // Convert to i64
|
||||
// }
|
||||
// else {
|
||||
// param_map[param_name] = param_value.int() // Default to int
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// 'string' {
|
||||
// param_map[param_name] = param_value // Already a string
|
||||
// }
|
||||
// 'boolean' {
|
||||
// param_map[param_name] = param_value.bool() // Convert to bool
|
||||
// }
|
||||
// 'number' {
|
||||
// match param_format {
|
||||
// 'float' {
|
||||
// param_map[param_name] = param_value.f32() // Convert to float
|
||||
// }
|
||||
// 'double' {
|
||||
// param_map[param_name] = param_value.f64() // Convert to double
|
||||
// }
|
||||
// else {
|
||||
// param_map[param_name] = param_value.f64() // Default to double
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// param_map[param_name] = param_value // Leave as string for unknown types
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // If the parameter is not defined in the OpenAPI operation, skip or log it
|
||||
// println('Unknown parameter: $param_name')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Encode the parameter map to JSON if needed
|
||||
// params << json.encode(param_map.str())
|
||||
// }
|
||||
|
||||
// call := Action{
|
||||
// name: request.operation.operation_id
|
||||
// params_json: json2.encode(params.str()) // Keep as a string since ProcedureCall expects a string
|
||||
// }
|
||||
// return call
|
||||
return Action{}
|
||||
}
|
||||
43
libarchive/baobab/stage/interfaces/server_http.v
Normal file
43
libarchive/baobab/stage/interfaces/server_http.v
Normal file
@@ -0,0 +1,43 @@
|
||||
module interfaces
|
||||
|
||||
import freeflowuniverse.herolib.schemas.openapi { OpenAPI }
|
||||
import freeflowuniverse.herolib.baobab.stage { ClientConfig }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
import veb
|
||||
|
||||
pub struct HTTPServer {
|
||||
veb.Controller
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
pub struct HTTPServerConfig {
|
||||
ClientConfig
|
||||
pub:
|
||||
openapi_specification OpenAPI
|
||||
openrpc_specification OpenRPC
|
||||
}
|
||||
|
||||
pub fn new_http_server() !&HTTPServer {
|
||||
mut s := &HTTPServer{}
|
||||
|
||||
// client := actor.new_client(cfg.ClientConfig)!
|
||||
|
||||
// openapi_proxy := new_openapi_proxy(
|
||||
// client: new_client(cfg.ClientConfig)!
|
||||
// specification: cfg.openapi_spec
|
||||
// )
|
||||
|
||||
// mut openrpc_controller := openrpc.new_http_controller(
|
||||
// specification: cfg.openrpc_specification
|
||||
// handler: new_openrpc_interface(client)
|
||||
// )
|
||||
// s.register_controller[openrpc.HTTPController, Context]('/openrpc', mut openrpc_controller)!
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn (mut server HTTPServer) run() {
|
||||
veb.run[HTTPServer, Context](mut server, 8082)
|
||||
}
|
||||
1
libarchive/examples/baobab/generator/.gitignore
vendored
Normal file
1
libarchive/examples/baobab/generator/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pet_store_actor
|
||||
3
libarchive/examples/baobab/generator/basic/.gitignore
vendored
Normal file
3
libarchive/examples/baobab/generator/basic/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
methods.v
|
||||
pet_store_actor
|
||||
docs
|
||||
9
libarchive/examples/baobab/generator/basic/README.md
Normal file
9
libarchive/examples/baobab/generator/basic/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Actor Generation Examples
|
||||
|
||||
## `generate_methods.vsh`
|
||||
|
||||
This example generates actor method prototypes from an actor specification.
|
||||
|
||||
## `generate_actor_module.vsh`
|
||||
|
||||
This example generates an entire actor module from an actor specification with the support for the specified interfaces.
|
||||
22
libarchive/examples/baobab/generator/basic/generate_actor_module.vsh
Executable file
22
libarchive/examples/baobab/generator/basic/generate_actor_module.vsh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.generator
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
const example_dir = os.dir(@FILE)
|
||||
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
|
||||
|
||||
// the actor specification obtained from the OpenRPC Specification
|
||||
openrpc_spec := openrpc.new(path: openrpc_spec_path)!
|
||||
actor_spec := specification.from_openrpc(openrpc_spec)!
|
||||
|
||||
actor_module := generator.generate_actor_module(actor_spec,
|
||||
interfaces: [.openrpc]
|
||||
)!
|
||||
|
||||
actor_module.write(example_dir,
|
||||
format: true
|
||||
overwrite: true
|
||||
)!
|
||||
19
libarchive/examples/baobab/generator/basic/generate_methods.vsh
Executable file
19
libarchive/examples/baobab/generator/basic/generate_methods.vsh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.generator
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
const example_dir = os.dir(@FILE)
|
||||
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
|
||||
|
||||
// the actor specification obtained from the OpenRPC Specification
|
||||
openrpc_spec := openrpc.new(path: openrpc_spec_path)!
|
||||
actor_spec := specification.from_openrpc(openrpc_spec)!
|
||||
|
||||
methods_file := generator.generate_methods_file(actor_spec)!
|
||||
methods_file.write(example_dir,
|
||||
format: true
|
||||
overwrite: true
|
||||
)!
|
||||
19
libarchive/examples/baobab/generator/basic/generate_openrpc_file.vsh
Executable file
19
libarchive/examples/baobab/generator/basic/generate_openrpc_file.vsh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.generator
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
const example_dir = os.dir(@FILE)
|
||||
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
|
||||
|
||||
// the actor specification obtained from the OpenRPC Specification
|
||||
openrpc_spec_ := openrpc.new(path: openrpc_spec_path)!
|
||||
actor_spec := specification.from_openrpc(openrpc_spec_)!
|
||||
openrpc_spec := actor_spec.to_openrpc()
|
||||
|
||||
openrpc_file := generator.generate_openrpc_file(openrpc_spec)!
|
||||
openrpc_file.write(os.join_path(example_dir, 'docs'),
|
||||
overwrite: true
|
||||
)!
|
||||
132
libarchive/examples/baobab/generator/basic/openrpc.json
Normal file
132
libarchive/examples/baobab/generator/basic/openrpc.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"openrpc": "1.0.0",
|
||||
"info": {
|
||||
"title": "PetStore",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "GetPets",
|
||||
"description": "finds pets in the system that the user has access to by tags and within a limit",
|
||||
"params": [
|
||||
{
|
||||
"name": "tags",
|
||||
"description": "tags to filter by",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "maximum number of results to return",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet_list",
|
||||
"description": "all pets from the system, that mathes the tags",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CreatePet",
|
||||
"description": "creates a new pet in the store. Duplicates are allowed.",
|
||||
"params": [
|
||||
{
|
||||
"name": "new_pet",
|
||||
"description": "Pet to add to the store.",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/NewPet"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet",
|
||||
"description": "the newly created pet",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GetPetById",
|
||||
"description": "gets a pet based on a single ID, if the user has access to the pet",
|
||||
"params": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of pet to fetch",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet",
|
||||
"description": "pet response",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DeletePetById",
|
||||
"description": "deletes a single pet based on the ID supplied",
|
||||
"params": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of pet to delete",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet",
|
||||
"description": "pet deleted",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"schemas": {
|
||||
"NewPet": {
|
||||
"title": "NewPet",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pet": {
|
||||
"title": "Pet",
|
||||
"description": "a pet struct that represents a pet",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "name of the pet",
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"description": "a tag of the pet, helps finding pet",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "unique indentifier",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
libarchive/examples/baobab/generator/geomind_poc/.gitignore
vendored
Normal file
3
libarchive/examples/baobab/generator/geomind_poc/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
merchant
|
||||
profiler
|
||||
farmer
|
||||
344
libarchive/examples/baobab/generator/geomind_poc/farmer.json
Normal file
344
libarchive/examples/baobab/generator/geomind_poc/farmer.json
Normal file
@@ -0,0 +1,344 @@
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "Farmer",
|
||||
"description": "API for managing farms and nodes, tracking rewards, capacity, and location.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:8080",
|
||||
"description": "Local development server"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Farm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Amsterdam Data Center"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Enterprise-grade data center with renewable energy focus"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"example": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"owner"
|
||||
]
|
||||
},
|
||||
"Node": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "n47ac10b-58cc-4372-a567-0e02b2c3d479"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "High-performance GPU compute node with 4x NVIDIA A100"
|
||||
},
|
||||
"farm_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/components/schemas/Location"
|
||||
},
|
||||
"capacity": {
|
||||
"$ref": "#/components/schemas/Capacity"
|
||||
},
|
||||
"grid_version": {
|
||||
"type": "string",
|
||||
"example": "3.16.2"
|
||||
},
|
||||
"reward": {
|
||||
"$ref": "#/components/schemas/Reward"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"description",
|
||||
"farm_id",
|
||||
"location",
|
||||
"capacity",
|
||||
"reward"
|
||||
]
|
||||
},
|
||||
"Location": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"coordinates": {
|
||||
"type": "string",
|
||||
"example": "52.3740, 4.8897"
|
||||
},
|
||||
"continent": {
|
||||
"type": "string",
|
||||
"example": "Europe"
|
||||
},
|
||||
"country": {
|
||||
"type": "string",
|
||||
"example": "Netherlands"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"coordinates",
|
||||
"continent",
|
||||
"country"
|
||||
]
|
||||
},
|
||||
"Capacity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpu": {
|
||||
"type": "integer",
|
||||
"example": 128
|
||||
},
|
||||
"memory_gb": {
|
||||
"type": "integer",
|
||||
"example": 1024
|
||||
},
|
||||
"storage_tb": {
|
||||
"type": "integer",
|
||||
"example": 100
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cpu",
|
||||
"memory_gb",
|
||||
"storage_tb"
|
||||
]
|
||||
},
|
||||
"Reward": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reward_promised": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"example": 25000.50
|
||||
},
|
||||
"reward_given": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"example": 12500.25
|
||||
},
|
||||
"duration_months": {
|
||||
"type": "integer",
|
||||
"example": 36
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reward_promised",
|
||||
"reward_given",
|
||||
"duration_months"
|
||||
]
|
||||
},
|
||||
"NodeStats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"node_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"example": "42"
|
||||
},
|
||||
"uptime_hours": {
|
||||
"type": "integer",
|
||||
"example": 8760
|
||||
},
|
||||
"bandwidth_gb": {
|
||||
"type": "integer",
|
||||
"example": 25000
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node_id",
|
||||
"uptime_hours",
|
||||
"bandwidth_gb"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/farms": {
|
||||
"get": {
|
||||
"summary": "List all farms",
|
||||
"operationId": "getFarms",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of farms",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Farm"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||||
"name": "Amsterdam Data Center",
|
||||
"description": "Enterprise-grade data center with renewable energy focus",
|
||||
"owner": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
|
||||
},
|
||||
{
|
||||
"id": "d47ac10b-58cc-4372-a567-0e02b2c3d480",
|
||||
"name": "Dubai Compute Hub",
|
||||
"description": "High-density compute farm with advanced cooling",
|
||||
"owner": "0x842d35Cc6634C0532925a3b844Bc454e4438f55f"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/farms/{farmId}/nodes": {
|
||||
"get": {
|
||||
"summary": "List nodes in a farm",
|
||||
"operationId": "getNodesByFarm",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "farmId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of nodes in the farm",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Node"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"id": "n47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||||
"description": "High-performance GPU compute node with 4x NVIDIA A100",
|
||||
"farm_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||||
"location": {
|
||||
"coordinates": "52.3740, 4.8897",
|
||||
"continent": "Europe",
|
||||
"country": "Netherlands"
|
||||
},
|
||||
"capacity": {
|
||||
"cpu": 128,
|
||||
"memory_gb": 1024,
|
||||
"storage_tb": 100
|
||||
},
|
||||
"grid_version": "3.16.2",
|
||||
"reward": {
|
||||
"reward_promised": 25000.50,
|
||||
"reward_given": 12500.25,
|
||||
"duration_months": 36
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Farm not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 404
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Farm with ID f47ac10b-58cc-4372-a567-0e02b2c3d479 not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/nodes/{nodeId}/stats": {
|
||||
"get": {
|
||||
"summary": "Get node statistics",
|
||||
"operationId": "getNodeStats",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "nodeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint32"
|
||||
},
|
||||
"example": "42"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Node statistics",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NodeStats"
|
||||
},
|
||||
"example": {
|
||||
"node_id": "42",
|
||||
"uptime_hours": 8760,
|
||||
"bandwidth_gb": 25000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Node not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 404
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Node with ID n47ac10b-58cc-4372-a567-0e02b2c3d479 not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
libarchive/examples/baobab/generator/geomind_poc/generate.vsh
Executable file
23
libarchive/examples/baobab/generator/geomind_poc/generate.vsh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.generator
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import os
|
||||
|
||||
const example_dir = os.dir(@FILE)
|
||||
const specs = ['merchant', 'profiler', 'farmer']
|
||||
|
||||
for spec in specs {
|
||||
openapi_spec_path := os.join_path(example_dir, '${spec}.json')
|
||||
openapi_spec := openapi.new(path: openapi_spec_path, process: true)!
|
||||
actor_spec := specification.from_openapi(openapi_spec)!
|
||||
actor_module := generator.generate_actor_folder(actor_spec,
|
||||
interfaces: [.openapi, .http]
|
||||
)!
|
||||
actor_module.write(example_dir,
|
||||
format: true
|
||||
overwrite: true
|
||||
compile: false
|
||||
)!
|
||||
}
|
||||
997
libarchive/examples/baobab/generator/geomind_poc/merchant.json
Normal file
997
libarchive/examples/baobab/generator/geomind_poc/merchant.json
Normal file
@@ -0,0 +1,997 @@
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "Merchant",
|
||||
"description": "API for e-commerce operations including stores, products, and orders",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [{
|
||||
"url": "http://localhost:8080",
|
||||
"description": "Local development server"
|
||||
},{
|
||||
"url": "http://localhost:8080/openapi/example",
|
||||
"description": "Local example server"
|
||||
}],
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Store": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174000"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Tech Gadgets Store"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Premium electronics and gadgets retailer"
|
||||
},
|
||||
"contact": {
|
||||
"type": "string",
|
||||
"example": "contact@techgadgets.com"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"contact",
|
||||
"active"
|
||||
]
|
||||
},
|
||||
"ProductComponentTemplate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174001"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "4K Display Panel"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "55-inch 4K UHD Display Panel"
|
||||
},
|
||||
"specs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"resolution": "3840x2160",
|
||||
"refreshRate": "120Hz",
|
||||
"panel_type": "OLED"
|
||||
}
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"example": 599.99
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Z]{3}$",
|
||||
"example": "USD"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"price",
|
||||
"currency"
|
||||
]
|
||||
},
|
||||
"ProductTemplate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174002"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Smart TV 55-inch"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "55-inch Smart TV with 4K Display"
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ProductComponentTemplate"
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"id": "123e4567-e89b-12d3-a456-426614174001",
|
||||
"name": "4K Display Panel",
|
||||
"description": "55-inch 4K UHD Display Panel",
|
||||
"specs": {
|
||||
"resolution": "3840x2160",
|
||||
"refreshRate": "120Hz"
|
||||
},
|
||||
"price": 599.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
]
|
||||
},
|
||||
"store_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174000"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"example": "Electronics"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"components",
|
||||
"store_id",
|
||||
"active"
|
||||
]
|
||||
},
|
||||
"Product": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174003"
|
||||
},
|
||||
"template_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174002"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Smart TV 55-inch"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "55-inch Smart TV with 4K Display"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"example": 899.99
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Z]{3}$",
|
||||
"example": "USD"
|
||||
},
|
||||
"store_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174000"
|
||||
},
|
||||
"stock_quantity": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"example": 50
|
||||
},
|
||||
"available": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"template_id",
|
||||
"name",
|
||||
"price",
|
||||
"currency",
|
||||
"store_id",
|
||||
"stock_quantity",
|
||||
"available"
|
||||
]
|
||||
},
|
||||
"OrderItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"product_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174003"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"example": 2
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"example": 899.99
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Z]{3}$",
|
||||
"example": "USD"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"product_id",
|
||||
"quantity",
|
||||
"price",
|
||||
"currency"
|
||||
]
|
||||
},
|
||||
"Order": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174004"
|
||||
},
|
||||
"customer_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174005"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrderItem"
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"product_id": "123e4567-e89b-12d3-a456-426614174003",
|
||||
"quantity": 2,
|
||||
"price": 899.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
]
|
||||
},
|
||||
"total_amount": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"example": 1799.98
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Z]{3}$",
|
||||
"example": "USD"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pending",
|
||||
"confirmed",
|
||||
"shipped",
|
||||
"delivered"
|
||||
],
|
||||
"example": "pending"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2024-02-10T10:30:00Z"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2024-02-10T10:30:00Z"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"customer_id",
|
||||
"items",
|
||||
"total_amount",
|
||||
"currency",
|
||||
"status",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
]
|
||||
},
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 404
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Resource not found"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"code",
|
||||
"message"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/stores": {
|
||||
"post": {
|
||||
"summary": "Create a new store",
|
||||
"operationId": "createStore",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Tech Gadgets Store"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Premium electronics and gadgets retailer"
|
||||
},
|
||||
"contact": {
|
||||
"type": "string",
|
||||
"example": "contact@techgadgets.com"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"contact"
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"newStore": {
|
||||
"summary": "Create a new electronics store",
|
||||
"value": {
|
||||
"name": "Tech Gadgets Store",
|
||||
"description": "Premium electronics and gadgets retailer",
|
||||
"contact": "contact@techgadgets.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Store created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Store"
|
||||
},
|
||||
"example": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"name": "Tech Gadgets Store",
|
||||
"description": "Premium electronics and gadgets retailer",
|
||||
"contact": "contact@techgadgets.com",
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 400,
|
||||
"message": "Invalid store data provided"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/products/templates/components": {
|
||||
"post": {
|
||||
"summary": "Create a new product component template",
|
||||
"operationId": "createProductComponentTemplate",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"specs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"price": {
|
||||
"type": "number"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"price",
|
||||
"currency"
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"displayPanel": {
|
||||
"summary": "Create a display panel component",
|
||||
"value": {
|
||||
"name": "4K Display Panel",
|
||||
"description": "55-inch 4K UHD Display Panel",
|
||||
"specs": {
|
||||
"resolution": "3840x2160",
|
||||
"refreshRate": "120Hz",
|
||||
"panel_type": "OLED"
|
||||
},
|
||||
"price": 599.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Component template created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProductComponentTemplate"
|
||||
},
|
||||
"example": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174001",
|
||||
"name": "4K Display Panel",
|
||||
"description": "55-inch 4K UHD Display Panel",
|
||||
"specs": {
|
||||
"resolution": "3840x2160",
|
||||
"refreshRate": "120Hz",
|
||||
"panel_type": "OLED"
|
||||
},
|
||||
"price": 599.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 400,
|
||||
"message": "Invalid component template data"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/products/templates": {
|
||||
"post": {
|
||||
"summary": "Create a new product template",
|
||||
"operationId": "createProductTemplate",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"store_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"components",
|
||||
"store_id"
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"smartTV": {
|
||||
"summary": "Create a Smart TV template",
|
||||
"value": {
|
||||
"name": "Smart TV 55-inch",
|
||||
"description": "55-inch Smart TV with 4K Display",
|
||||
"components": [
|
||||
"123e4567-e89b-12d3-a456-426614174001"
|
||||
],
|
||||
"store_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"category": "Electronics"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Product template created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProductTemplate"
|
||||
},
|
||||
"example": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174002",
|
||||
"name": "Smart TV 55-inch",
|
||||
"description": "55-inch Smart TV with 4K Display",
|
||||
"components": [
|
||||
{
|
||||
"id": "123e4567-e89b-12d3-a456-426614174001",
|
||||
"name": "4K Display Panel",
|
||||
"description": "55-inch 4K UHD Display Panel",
|
||||
"specs": {
|
||||
"resolution": "3840x2160",
|
||||
"refreshRate": "120Hz"
|
||||
},
|
||||
"price": 599.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
],
|
||||
"store_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"category": "Electronics",
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Store not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 404,
|
||||
"message": "Store not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/products": {
|
||||
"post": {
|
||||
"summary": "Create a new product from template",
|
||||
"operationId": "createProduct",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"template_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"store_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"stock_quantity": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"template_id",
|
||||
"store_id",
|
||||
"stock_quantity"
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"newProduct": {
|
||||
"summary": "Create a new Smart TV product",
|
||||
"value": {
|
||||
"template_id": "123e4567-e89b-12d3-a456-426614174002",
|
||||
"store_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"stock_quantity": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Product created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Product"
|
||||
},
|
||||
"example": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174003",
|
||||
"template_id": "123e4567-e89b-12d3-a456-426614174002",
|
||||
"name": "Smart TV 55-inch",
|
||||
"description": "55-inch Smart TV with 4K Display",
|
||||
"price": 899.99,
|
||||
"currency": "USD",
|
||||
"store_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"stock_quantity": 50,
|
||||
"available": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Template or store not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 404,
|
||||
"message": "Product template not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders": {
|
||||
"post": {
|
||||
"summary": "Create a new order",
|
||||
"operationId": "createOrder",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"customer_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrderItem"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"customer_id",
|
||||
"items"
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"newOrder": {
|
||||
"summary": "Create an order for two Smart TVs",
|
||||
"value": {
|
||||
"customer_id": "123e4567-e89b-12d3-a456-426614174005",
|
||||
"items": [
|
||||
{
|
||||
"product_id": "123e4567-e89b-12d3-a456-426614174003",
|
||||
"quantity": 2,
|
||||
"price": 899.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Order created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
},
|
||||
"example": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174004",
|
||||
"customer_id": "123e4567-e89b-12d3-a456-426614174005",
|
||||
"items": [
|
||||
{
|
||||
"product_id": "123e4567-e89b-12d3-a456-426614174003",
|
||||
"quantity": 2,
|
||||
"price": 899.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
],
|
||||
"total_amount": 1799.98,
|
||||
"currency": "USD",
|
||||
"status": "pending",
|
||||
"created_at": "2024-02-10T10:30:00Z",
|
||||
"updated_at": "2024-02-10T10:30:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input or insufficient stock",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 400,
|
||||
"message": "Insufficient stock for product"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders/{orderId}/status": {
|
||||
"put": {
|
||||
"summary": "Update order status",
|
||||
"operationId": "updateOrderStatus",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"example": "123e4567-e89b-12d3-a456-426614174004"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pending",
|
||||
"confirmed",
|
||||
"shipped",
|
||||
"delivered"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"updateStatus": {
|
||||
"summary": "Update order to shipped status",
|
||||
"value": {
|
||||
"status": "shipped"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Order status updated successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
},
|
||||
"example": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174004",
|
||||
"customer_id": "123e4567-e89b-12d3-a456-426614174005",
|
||||
"items": [
|
||||
{
|
||||
"product_id": "123e4567-e89b-12d3-a456-426614174003",
|
||||
"quantity": 2,
|
||||
"price": 899.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
],
|
||||
"total_amount": 1799.98,
|
||||
"currency": "USD",
|
||||
"status": "shipped",
|
||||
"created_at": "2024-02-10T10:30:00Z",
|
||||
"updated_at": "2024-02-10T10:35:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 404,
|
||||
"message": "Order not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/stores/{storeId}/products": {
|
||||
"get": {
|
||||
"summary": "Get all products for a store",
|
||||
"operationId": "getStoreProducts",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"example": "123e4567-e89b-12d3-a456-426614174000"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of store's products",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Product"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"id": "123e4567-e89b-12d3-a456-426614174003",
|
||||
"template_id": "123e4567-e89b-12d3-a456-426614174002",
|
||||
"name": "Smart TV 55-inch",
|
||||
"description": "55-inch Smart TV with 4K Display",
|
||||
"price": 899.99,
|
||||
"currency": "USD",
|
||||
"store_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"stock_quantity": 48,
|
||||
"available": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Store not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 404,
|
||||
"message": "Store not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/stores/{storeId}/orders": {
|
||||
"get": {
|
||||
"summary": "Get all orders for a store's products",
|
||||
"operationId": "getStoreOrders",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"example": "123e4567-e89b-12d3-a456-426614174000"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of orders containing store's products",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"id": "123e4567-e89b-12d3-a456-426614174004",
|
||||
"customer_id": "123e4567-e89b-12d3-a456-426614174005",
|
||||
"items": [
|
||||
{
|
||||
"product_id": "123e4567-e89b-12d3-a456-426614174003",
|
||||
"quantity": 2,
|
||||
"price": 899.99,
|
||||
"currency": "USD"
|
||||
}
|
||||
],
|
||||
"total_amount": 1799.98,
|
||||
"currency": "USD",
|
||||
"status": "shipped",
|
||||
"created_at": "2024-02-10T10:30:00Z",
|
||||
"updated_at": "2024-02-10T10:35:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Store not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"example": {
|
||||
"code": 404,
|
||||
"message": "Store not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
libarchive/examples/baobab/generator/geomind_poc/model.v
Normal file
81
libarchive/examples/baobab/generator/geomind_poc/model.v
Normal file
@@ -0,0 +1,81 @@
|
||||
module geomind_poc
|
||||
|
||||
pub struct Merchant {
|
||||
pub:
|
||||
id string
|
||||
name string
|
||||
description string
|
||||
contact string
|
||||
active bool
|
||||
}
|
||||
|
||||
pub struct ProductComponentTemplate {
|
||||
pub:
|
||||
id string
|
||||
name string
|
||||
description string
|
||||
// technical specifications
|
||||
specs map[string]string
|
||||
// price per unit
|
||||
price f64
|
||||
// currency code (e.g., 'USD', 'EUR')
|
||||
currency string
|
||||
}
|
||||
|
||||
pub struct ProductTemplate {
|
||||
pub:
|
||||
id string
|
||||
name string
|
||||
description string
|
||||
// components that make up this product template
|
||||
components []ProductComponentTemplate
|
||||
// merchant who created this template
|
||||
merchant_id string
|
||||
// category of the product (e.g., 'electronics', 'clothing')
|
||||
category string
|
||||
// whether this template is available for use
|
||||
active bool
|
||||
}
|
||||
|
||||
pub struct Product {
|
||||
pub:
|
||||
id string
|
||||
template_id string
|
||||
// specific instance details that may differ from template
|
||||
name string
|
||||
description string
|
||||
// actual price of this product instance
|
||||
price f64
|
||||
currency string
|
||||
// merchant selling this product
|
||||
merchant_id string
|
||||
// current stock level
|
||||
stock_quantity int
|
||||
// whether this product is available for purchase
|
||||
available bool
|
||||
}
|
||||
|
||||
pub struct OrderItem {
|
||||
pub:
|
||||
product_id string
|
||||
quantity int
|
||||
price f64
|
||||
currency string
|
||||
}
|
||||
|
||||
pub struct Order {
|
||||
pub:
|
||||
id string
|
||||
// customer identifier
|
||||
customer_id string
|
||||
// items in the order
|
||||
items []OrderItem
|
||||
// total order amount
|
||||
total_amount f64
|
||||
currency string
|
||||
// order status (e.g., 'pending', 'confirmed', 'shipped', 'delivered')
|
||||
status string
|
||||
// timestamps
|
||||
created_at string
|
||||
updated_at string
|
||||
}
|
||||
148
libarchive/examples/baobab/generator/geomind_poc/play.v
Normal file
148
libarchive/examples/baobab/generator/geomind_poc/play.v
Normal file
@@ -0,0 +1,148 @@
|
||||
module geomind_poc
|
||||
|
||||
import freeflowuniverse.crystallib.core.playbook { PlayBook }
|
||||
|
||||
// play_commerce processes heroscript actions for the commerce system
|
||||
pub fn play_commerce(mut plbook PlayBook) ! {
|
||||
commerce_actions := plbook.find(filter: 'commerce.')!
|
||||
mut c := Commerce{}
|
||||
|
||||
for action in commerce_actions {
|
||||
match action.name {
|
||||
'merchant' {
|
||||
mut p := action.params
|
||||
merchant := c.create_merchant(
|
||||
name: p.get('name')!
|
||||
description: p.get_default('description', '')!
|
||||
contact: p.get('contact')!
|
||||
)!
|
||||
println('Created merchant: ${merchant.name}')
|
||||
}
|
||||
'component' {
|
||||
mut p := action.params
|
||||
component := c.create_product_component_template(
|
||||
name: p.get('name')!
|
||||
description: p.get_default('description', '')!
|
||||
specs: p.get_map()
|
||||
price: p.get_float('price')!
|
||||
currency: p.get('currency')!
|
||||
)!
|
||||
println('Created component: ${component.name}')
|
||||
}
|
||||
'template' {
|
||||
mut p := action.params
|
||||
// Get component IDs as a list
|
||||
component_ids := p.get_list('components')!
|
||||
// Convert component IDs to actual components
|
||||
mut components := []ProductComponentTemplate{}
|
||||
for id in component_ids {
|
||||
// In a real implementation, you would fetch the component from storage
|
||||
// For this example, we create a dummy component
|
||||
component := ProductComponentTemplate{
|
||||
id: id
|
||||
name: 'Component'
|
||||
description: ''
|
||||
specs: map[string]string{}
|
||||
price: 0
|
||||
currency: 'USD'
|
||||
}
|
||||
components << component
|
||||
}
|
||||
|
||||
template := c.create_product_template(
|
||||
name: p.get('name')!
|
||||
description: p.get_default('description', '')!
|
||||
components: components
|
||||
merchant_id: p.get('merchant_id')!
|
||||
category: p.get_default('category', 'General')!
|
||||
)!
|
||||
println('Created template: ${template.name}')
|
||||
}
|
||||
'product' {
|
||||
mut p := action.params
|
||||
product := c.create_product(
|
||||
template_id: p.get('template_id')!
|
||||
merchant_id: p.get('merchant_id')!
|
||||
stock_quantity: p.get_int('stock_quantity')!
|
||||
)!
|
||||
println('Created product: ${product.name} with stock: ${product.stock_quantity}')
|
||||
}
|
||||
'order' {
|
||||
mut p := action.params
|
||||
// Get order items as a list of maps
|
||||
items_data := p.get_list('items')!
|
||||
mut items := []OrderItem{}
|
||||
for item_data in items_data {
|
||||
// Parse item data (format: "product_id:quantity:price:currency")
|
||||
parts := item_data.split(':')
|
||||
if parts.len != 4 {
|
||||
return error('Invalid order item format: ${item_data}')
|
||||
}
|
||||
item := OrderItem{
|
||||
product_id: parts[0]
|
||||
quantity: parts[1].int()
|
||||
price: parts[2].f64()
|
||||
currency: parts[3]
|
||||
}
|
||||
items << item
|
||||
}
|
||||
|
||||
order := c.create_order(
|
||||
customer_id: p.get('customer_id')!
|
||||
items: items
|
||||
)!
|
||||
println('Created order: ${order.id} with ${order.items.len} items')
|
||||
}
|
||||
'update_order' {
|
||||
mut p := action.params
|
||||
order := c.update_order_status(
|
||||
order_id: p.get('order_id')!
|
||||
new_status: p.get('status')!
|
||||
)!
|
||||
println('Updated order ${order.id} status to: ${order.status}')
|
||||
}
|
||||
else {
|
||||
return error('Unknown commerce action: ${action.name}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example heroscript usage:
|
||||
/*
|
||||
!!commerce.merchant
|
||||
name: "Tech Gadgets Store"
|
||||
description: "Premium electronics and gadgets retailer"
|
||||
contact: "contact@techgadgets.com"
|
||||
|
||||
!!commerce.component
|
||||
name: "4K Display Panel"
|
||||
description: "55-inch 4K UHD Display Panel"
|
||||
specs:
|
||||
resolution: "3840x2160"
|
||||
refreshRate: "120Hz"
|
||||
panel_type: "OLED"
|
||||
price: 599.99
|
||||
currency: "USD"
|
||||
|
||||
!!commerce.template
|
||||
name: "Smart TV 55-inch"
|
||||
description: "55-inch Smart TV with 4K Display"
|
||||
components: "123e4567-e89b-12d3-a456-426614174001"
|
||||
merchant_id: "123e4567-e89b-12d3-a456-426614174000"
|
||||
category: "Electronics"
|
||||
|
||||
!!commerce.product
|
||||
template_id: "123e4567-e89b-12d3-a456-426614174002"
|
||||
merchant_id: "123e4567-e89b-12d3-a456-426614174000"
|
||||
stock_quantity: 50
|
||||
|
||||
!!commerce.order
|
||||
customer_id: "123e4567-e89b-12d3-a456-426614174005"
|
||||
items:
|
||||
- "123e4567-e89b-12d3-a456-426614174003:2:899.99:USD"
|
||||
|
||||
!!commerce.update_order
|
||||
order_id: "123e4567-e89b-12d3-a456-426614174004"
|
||||
status: "shipped"
|
||||
*/
|
||||
286
libarchive/examples/baobab/generator/geomind_poc/profiler.json
Normal file
286
libarchive/examples/baobab/generator/geomind_poc/profiler.json
Normal file
@@ -0,0 +1,286 @@
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "Profiler",
|
||||
"description": "API for managing user profiles with name, public key, and KYC verification",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:8080",
|
||||
"description": "Local development server"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Profile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "123e4567-e89b-12d3-a456-426614174000"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Alice Doe"
|
||||
},
|
||||
"public_key": {
|
||||
"type": "string",
|
||||
"example": "028a8f8b59f7283a47f9f6d4bc8176e847ad2b6c6d8bdfd041e5e7f3b4ac28c9fc"
|
||||
},
|
||||
"kyc_verified": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "public_key", "kyc_verified"]
|
||||
},
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 400
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Invalid request"
|
||||
}
|
||||
},
|
||||
"required": ["code", "message"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/profiles": {
|
||||
"post": {
|
||||
"summary": "Create a new profile",
|
||||
"operationId": "createProfile",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Bob Smith"
|
||||
},
|
||||
"public_key": {
|
||||
"type": "string",
|
||||
"example": "03a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
|
||||
}
|
||||
},
|
||||
"required": ["name", "public_key"]
|
||||
},
|
||||
"examples": {
|
||||
"newProfile": {
|
||||
"summary": "Example of creating a new profile",
|
||||
"value": {
|
||||
"name": "Bob Smith",
|
||||
"public_key": "03a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Profile created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Profile"
|
||||
},
|
||||
"examples": {
|
||||
"successResponse": {
|
||||
"summary": "Example of successful profile creation",
|
||||
"value": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Bob Smith",
|
||||
"public_key": "03a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
|
||||
"kyc_verified": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"examples": {
|
||||
"invalidInput": {
|
||||
"summary": "Example of invalid input error",
|
||||
"value": {
|
||||
"code": 400,
|
||||
"message": "Invalid public key format"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/profiles/{profileId}": {
|
||||
"get": {
|
||||
"summary": "Get profile details",
|
||||
"operationId": "getProfile",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "profileId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint32"
|
||||
},
|
||||
"example": "42"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Profile retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Profile"
|
||||
},
|
||||
"examples": {
|
||||
"existingProfile": {
|
||||
"summary": "Example of retrieved profile",
|
||||
"value": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Bob Smith",
|
||||
"public_key": "03a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
|
||||
"kyc_verified": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Profile not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"examples": {
|
||||
"notFound": {
|
||||
"summary": "Example of profile not found error",
|
||||
"value": {
|
||||
"code": 404,
|
||||
"message": "Profile with ID '550e8400-e29b-41d4-a716-446655440000' not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/profiles/{profileId}/kyc": {
|
||||
"put": {
|
||||
"summary": "Update KYC verification status",
|
||||
"operationId": "updateKYCStatus",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "profileId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint32"
|
||||
},
|
||||
"example": "42"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kyc_verified": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"required": ["kyc_verified"]
|
||||
},
|
||||
"examples": {
|
||||
"verifyKYC": {
|
||||
"summary": "Example of verifying KYC",
|
||||
"value": {
|
||||
"kyc_verified": true
|
||||
}
|
||||
},
|
||||
"unverifyKYC": {
|
||||
"summary": "Example of unverifying KYC",
|
||||
"value": {
|
||||
"kyc_verified": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "KYC status updated successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Profile"
|
||||
},
|
||||
"examples": {
|
||||
"updatedProfile": {
|
||||
"summary": "Example of profile with updated KYC status",
|
||||
"value": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Bob Smith",
|
||||
"public_key": "03a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
|
||||
"kyc_verified": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Profile not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
},
|
||||
"examples": {
|
||||
"notFound": {
|
||||
"summary": "Example of profile not found error",
|
||||
"value": {
|
||||
"code": 404,
|
||||
"message": "Profile with ID '550e8400-e29b-41d4-a716-446655440000' not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
191
libarchive/examples/baobab/generator/geomind_poc/server.v
Normal file
191
libarchive/examples/baobab/generator/geomind_poc/server.v
Normal file
@@ -0,0 +1,191 @@
|
||||
module geomind_poc
|
||||
|
||||
import crypto.rand
|
||||
import time
|
||||
|
||||
// Commerce represents the main e-commerce server handling all operations
|
||||
pub struct Commerce {
|
||||
mut:
|
||||
merchants map[string]Merchant
|
||||
templates map[string]ProductTemplate
|
||||
products map[string]Product
|
||||
orders map[string]Order
|
||||
}
|
||||
|
||||
// generate_id creates a unique identifier
|
||||
fn generate_id() string {
|
||||
return rand.uuid_v4()
|
||||
}
|
||||
|
||||
// create_merchant adds a new merchant to the system
|
||||
pub fn (mut c Commerce) create_merchant(name string, description string, contact string) !Merchant {
|
||||
merchant_id := generate_id()
|
||||
merchant := Merchant{
|
||||
id: merchant_id
|
||||
name: name
|
||||
description: description
|
||||
contact: contact
|
||||
active: true
|
||||
}
|
||||
c.merchants[merchant_id] = merchant
|
||||
return merchant
|
||||
}
|
||||
|
||||
// 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 {
|
||||
component := ProductComponentTemplate{
|
||||
id: generate_id()
|
||||
name: name
|
||||
description: description
|
||||
specs: specs
|
||||
price: price
|
||||
currency: currency
|
||||
}
|
||||
return component
|
||||
}
|
||||
|
||||
// create_product_template creates a new product template
|
||||
pub fn (mut c Commerce) create_product_template(name string, description string, components []ProductComponentTemplate, merchant_id string, category string) !ProductTemplate {
|
||||
if merchant_id !in c.merchants {
|
||||
return error('Merchant not found')
|
||||
}
|
||||
|
||||
template := ProductTemplate{
|
||||
id: generate_id()
|
||||
name: name
|
||||
description: description
|
||||
components: components
|
||||
merchant_id: merchant_id
|
||||
category: category
|
||||
active: true
|
||||
}
|
||||
c.templates[template.id] = template
|
||||
return template
|
||||
}
|
||||
|
||||
// create_product creates a new product instance from a template
|
||||
pub fn (mut c Commerce) create_product(template_id string, merchant_id string, stock_quantity int) !Product {
|
||||
if template_id !in c.templates {
|
||||
return error('Template not found')
|
||||
}
|
||||
if merchant_id !in c.merchants {
|
||||
return error('Merchant not found')
|
||||
}
|
||||
|
||||
template := c.templates[template_id]
|
||||
mut total_price := 0.0
|
||||
for component in template.components {
|
||||
total_price += component.price
|
||||
}
|
||||
|
||||
product := Product{
|
||||
id: generate_id()
|
||||
template_id: template_id
|
||||
name: template.name
|
||||
description: template.description
|
||||
price: total_price
|
||||
currency: template.components[0].currency // assuming all components use same currency
|
||||
merchant_id: merchant_id
|
||||
stock_quantity: stock_quantity
|
||||
available: true
|
||||
}
|
||||
c.products[product.id] = product
|
||||
return product
|
||||
}
|
||||
|
||||
// create_order creates a new order
|
||||
pub fn (mut c Commerce) create_order(customer_id string, items []OrderItem) !Order {
|
||||
mut total_amount := 0.0
|
||||
mut currency := ''
|
||||
|
||||
for item in items {
|
||||
if item.product_id !in c.products {
|
||||
return error('Product not found: ${item.product_id}')
|
||||
}
|
||||
product := c.products[item.product_id]
|
||||
if !product.available || product.stock_quantity < item.quantity {
|
||||
return error('Product ${product.name} is not available in requested quantity')
|
||||
}
|
||||
total_amount += item.price * item.quantity
|
||||
if currency == '' {
|
||||
currency = item.currency
|
||||
} else if currency != item.currency {
|
||||
return error('Mixed currencies are not supported')
|
||||
}
|
||||
}
|
||||
|
||||
order := Order{
|
||||
id: generate_id()
|
||||
customer_id: customer_id
|
||||
items: items
|
||||
total_amount: total_amount
|
||||
currency: currency
|
||||
status: 'pending'
|
||||
created_at: time.now().str()
|
||||
updated_at: time.now().str()
|
||||
}
|
||||
c.orders[order.id] = order
|
||||
|
||||
// Update stock quantities
|
||||
for item in items {
|
||||
mut product := c.products[item.product_id]
|
||||
product.stock_quantity -= item.quantity
|
||||
if product.stock_quantity == 0 {
|
||||
product.available = false
|
||||
}
|
||||
c.products[item.product_id] = product
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
// update_order_status updates the status of an order
|
||||
pub fn (mut c Commerce) update_order_status(order_id string, new_status string) !Order {
|
||||
if order_id !in c.orders {
|
||||
return error('Order not found')
|
||||
}
|
||||
|
||||
mut order := c.orders[order_id]
|
||||
order.status = new_status
|
||||
order.updated_at = time.now().str()
|
||||
c.orders[order_id] = order
|
||||
return order
|
||||
}
|
||||
|
||||
// get_merchant_products returns all products for a given merchant
|
||||
pub fn (c Commerce) get_merchant_products(merchant_id string) ![]Product {
|
||||
if merchant_id !in c.merchants {
|
||||
return error('Merchant not found')
|
||||
}
|
||||
|
||||
mut products := []Product{}
|
||||
for product in c.products.values() {
|
||||
if product.merchant_id == merchant_id {
|
||||
products << product
|
||||
}
|
||||
}
|
||||
return products
|
||||
}
|
||||
|
||||
// get_merchant_orders returns all orders for products sold by a merchant
|
||||
pub fn (c Commerce) get_merchant_orders(merchant_id string) ![]Order {
|
||||
if merchant_id !in c.merchants {
|
||||
return error('Merchant not found')
|
||||
}
|
||||
|
||||
mut orders := []Order{}
|
||||
for order in c.orders.values() {
|
||||
mut includes_merchant := false
|
||||
for item in order.items {
|
||||
product := c.products[item.product_id]
|
||||
if product.merchant_id == merchant_id {
|
||||
includes_merchant = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if includes_merchant {
|
||||
orders << order
|
||||
}
|
||||
}
|
||||
return orders
|
||||
}
|
||||
57
libarchive/examples/baobab/generator/geomind_poc/specs.md
Normal file
57
libarchive/examples/baobab/generator/geomind_poc/specs.md
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
|
||||
- profile management
|
||||
- my name
|
||||
- my pub key
|
||||
- kyc
|
||||
- ...
|
||||
- product has components
|
||||
- admin items
|
||||
- supported_currencies
|
||||
- countries
|
||||
- continents
|
||||
- farming
|
||||
- farms
|
||||
- default farm exists, users don't have to chose
|
||||
- name
|
||||
- description
|
||||
- owner (pubkey)
|
||||
- nodes
|
||||
- reward (nr of INCA per month and time e.g. 24 months)
|
||||
- reward_promised
|
||||
- reward_given
|
||||
- location
|
||||
- coordinates
|
||||
- continent
|
||||
- country
|
||||
- description
|
||||
- farmid
|
||||
- capacity (disks, mem, ...)
|
||||
- gridversion (eg. 3.16)
|
||||
- nodestats
|
||||
- ...
|
||||
- uptime
|
||||
- bandwidth
|
||||
- referral system
|
||||
- coupons for discounts (one product can have multiple coupons and discounts)
|
||||
- data gets imported with heroscript for what we sell
|
||||
- minimal wallet function (BTC, CHF, MGLD, TFT, INCA)
|
||||
- transactions, so they can see what they spend money on
|
||||
- transfer/exchange
|
||||
- basic communication (messages in/out)
|
||||
- to allow us to communicate with user
|
||||
- news
|
||||
- basic news feed with topics, which we can set
|
||||
- vdc
|
||||
- name
|
||||
- description (optional)
|
||||
- spendinglimit
|
||||
- currency per month, week or day e.g. 0.1 BTC/month
|
||||
- each spending limit has name
|
||||
- admins, list of pubkeys who have access to this and can add capacity to it, or delete, ...
|
||||
- deployment
|
||||
- deploymentid
|
||||
- vdcid
|
||||
- heroscript
|
||||
- status
|
||||
- links (name, link, description, category)
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env -S v
|
||||
|
||||
import freeflowuniverse.crystallib.core.playbook
|
||||
import geomind_poc
|
||||
|
||||
fn main() {
|
||||
test_script := "
|
||||
!!commerce.merchant
|
||||
name: 'Tech Gadgets Store'
|
||||
description: 'Premium electronics and gadgets retailer'
|
||||
contact: 'contact@techgadgets.com'
|
||||
|
||||
!!commerce.component
|
||||
name: '4K Display Panel'
|
||||
description: '55-inch 4K UHD Display Panel'
|
||||
specs:
|
||||
resolution: '3840x2160'
|
||||
refreshRate: '120Hz'
|
||||
panel_type: 'OLED'
|
||||
price: 599.99
|
||||
currency: 'USD'
|
||||
|
||||
!!commerce.template
|
||||
name: 'Smart TV 55-inch'
|
||||
description: '55-inch Smart TV with 4K Display'
|
||||
components: '123e4567-e89b-12d3-a456-426614174001'
|
||||
merchant_id: '123e4567-e89b-12d3-a456-426614174000'
|
||||
category: 'Electronics'
|
||||
|
||||
!!commerce.product
|
||||
template_id: '123e4567-e89b-12d3-a456-426614174002'
|
||||
merchant_id: '123e4567-e89b-12d3-a456-426614174000'
|
||||
stock_quantity: 50
|
||||
|
||||
!!commerce.order
|
||||
customer_id: '123e4567-e89b-12d3-a456-426614174005'
|
||||
items:
|
||||
- '123e4567-e89b-12d3-a456-426614174003:2:899.99:USD'
|
||||
|
||||
!!commerce.update_order
|
||||
order_id: '123e4567-e89b-12d3-a456-426614174004'
|
||||
status: 'shipped'
|
||||
"
|
||||
|
||||
mut plbook := playbook.new(text: test_script)!
|
||||
geomind_poc.play_commerce(mut plbook)!
|
||||
}
|
||||
25
libarchive/examples/baobab/generator/mcc_example.vsh
Executable file
25
libarchive/examples/baobab/generator/mcc_example.vsh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.generator
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import os
|
||||
|
||||
const example_dir = os.join_path('${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/circles/mcc',
|
||||
'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
|
||||
openapi_spec := openapi.new(path: openapi_spec_path)!
|
||||
actor_spec := specification.from_openapi(openapi_spec)!
|
||||
|
||||
actor_module := generator.generate_actor_module(actor_spec,
|
||||
interfaces: [.openapi, .http]
|
||||
)!
|
||||
|
||||
actor_module.write(example_dir,
|
||||
format: true
|
||||
overwrite: true
|
||||
compile: false
|
||||
)!
|
||||
4
libarchive/examples/baobab/generator/openapi_e2e/.gitignore
vendored
Normal file
4
libarchive/examples/baobab/generator/openapi_e2e/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
methods.v
|
||||
meeting_scheduler_actor
|
||||
generate_actor_module
|
||||
src
|
||||
31
libarchive/examples/baobab/generator/openapi_e2e/generate_actor_module.vsh
Executable file
31
libarchive/examples/baobab/generator/openapi_e2e/generate_actor_module.vsh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.generator
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import os
|
||||
|
||||
const example_dir = os.dir(@FILE)
|
||||
const openapi_spec_path = os.join_path(example_dir, 'openapi.json')
|
||||
|
||||
// the actor specification obtained from the OpenRPC Specification
|
||||
openapi_spec := openapi.new(path: openapi_spec_path)!
|
||||
actor_spec := specification.from_openapi(openapi_spec)!
|
||||
|
||||
println(actor_spec)
|
||||
|
||||
// actor_module := generator.generate_actor_module(actor_spec,
|
||||
// interfaces: [.openapi, .http]
|
||||
// )!
|
||||
|
||||
actor_module := generator.generate_actor_module(actor_spec,
|
||||
interfaces: [.http]
|
||||
)!
|
||||
|
||||
actor_module.write(example_dir,
|
||||
format: true
|
||||
overwrite: true
|
||||
compile: false
|
||||
)!
|
||||
|
||||
// os.execvp('bash', ['${example_dir}/meeting_scheduler_actor/scripts/run.sh'])!
|
||||
311
libarchive/examples/baobab/generator/openapi_e2e/openapi.json
Normal file
311
libarchive/examples/baobab/generator/openapi_e2e/openapi.json
Normal file
@@ -0,0 +1,311 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "Meeting Scheduler",
|
||||
"version": "1.0.0",
|
||||
"description": "An API for managing meetings, availability, and scheduling."
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:8080/openapi/v1",
|
||||
"description": "Production server"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8081/openapi/v1",
|
||||
"description": "Example server"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {
|
||||
"summary": "List all users",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A list of users",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Bob",
|
||||
"email": "bob@example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{userId}": {
|
||||
"get": {
|
||||
"operationId": "get_user",
|
||||
"summary": "Get user by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint32"
|
||||
},
|
||||
"description": "The ID of the user",
|
||||
"example": 1
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User details",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
},
|
||||
"example": {
|
||||
"id": "1",
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/events": {
|
||||
"post": {
|
||||
"summary": "Create an event",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Event"
|
||||
},
|
||||
"example": {
|
||||
"title": "Team Meeting",
|
||||
"description": "Weekly sync",
|
||||
"startTime": "2023-10-10T10:00:00Z",
|
||||
"endTime": "2023-10-10T11:00:00Z",
|
||||
"userId": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Event created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Event"
|
||||
},
|
||||
"example": {
|
||||
"id": "101",
|
||||
"title": "Team Meeting",
|
||||
"description": "Weekly sync",
|
||||
"startTime": "2023-10-10T10:00:00Z",
|
||||
"endTime": "2023-10-10T11:00:00Z",
|
||||
"userId": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/availability": {
|
||||
"get": {
|
||||
"summary": "Get availability for a user",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The ID of the user",
|
||||
"example": "1"
|
||||
},
|
||||
{
|
||||
"name": "date",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"description": "The date to check availability (YYYY-MM-DD)",
|
||||
"example": "2023-10-10"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Availability details",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TimeSlot"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"startTime": "10:00:00",
|
||||
"endTime": "11:00:00",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"startTime": "11:00:00",
|
||||
"endTime": "12:00:00",
|
||||
"available": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bookings": {
|
||||
"post": {
|
||||
"summary": "Book a meeting",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Booking"
|
||||
},
|
||||
"example": {
|
||||
"userId": "1",
|
||||
"eventId": "101",
|
||||
"timeSlot": {
|
||||
"startTime": "10:00:00",
|
||||
"endTime": "11:00:00",
|
||||
"available": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Booking created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Booking"
|
||||
},
|
||||
"example": {
|
||||
"id": "5001",
|
||||
"userId": "1",
|
||||
"eventId": "101",
|
||||
"timeSlot": {
|
||||
"startTime": "10:00:00",
|
||||
"endTime": "11:00:00",
|
||||
"available": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Event": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"startTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"endTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"TimeSlot": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"startTime": {
|
||||
"type": "string",
|
||||
"format": "time"
|
||||
},
|
||||
"endTime": {
|
||||
"type": "string",
|
||||
"format": "time"
|
||||
},
|
||||
"available": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Booking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string"
|
||||
},
|
||||
"eventId": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeSlot": {
|
||||
"$ref": "#/components/schemas/TimeSlot"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
libarchive/examples/baobab/specification/README.md
Normal file
3
libarchive/examples/baobab/specification/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Actor Specification Examples
|
||||
|
||||
These examples show how `OpenRPC` and `OpenAPI` specifications can be translated back and forth into an `ActorSpecification`. This is an important step of actor generation as actor code is generated from actor specification.
|
||||
346
libarchive/examples/baobab/specification/openapi.json
Normal file
346
libarchive/examples/baobab/specification/openapi.json
Normal file
@@ -0,0 +1,346 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Pet Store API",
|
||||
"description": "A sample API for a pet store",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://api.petstore.example.com/v1",
|
||||
"description": "Production server"
|
||||
},
|
||||
{
|
||||
"url": "https://staging.petstore.example.com/v1",
|
||||
"description": "Staging server"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/pets": {
|
||||
"get": {
|
||||
"summary": "List all pets",
|
||||
"operationId": "listPets",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Maximum number of pets to return",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A paginated list of pets",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create a new pet",
|
||||
"operationId": "createPet",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NewPet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Pet created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pets/{petId}": {
|
||||
"get": {
|
||||
"summary": "Get a pet by ID",
|
||||
"operationId": "getPet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of the pet to retrieve",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A pet",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete a pet by ID",
|
||||
"operationId": "deletePet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "petId",
|
||||
"in": "path",
|
||||
"description": "ID of the pet to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Pet deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Pet not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders": {
|
||||
"get": {
|
||||
"summary": "List all orders",
|
||||
"operationId": "listOrders",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A list of orders",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders/{orderId}": {
|
||||
"get": {
|
||||
"summary": "Get an order by ID",
|
||||
"operationId": "getOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of the order to retrieve",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An order",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Order"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete an order by ID",
|
||||
"operationId": "deleteOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orderId",
|
||||
"in": "path",
|
||||
"description": "ID of the order to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Order deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Order not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"post": {
|
||||
"summary": "Create a user",
|
||||
"operationId": "createUser",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NewUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "User created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Pet": {
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NewPet": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
},
|
||||
"Order": {
|
||||
"type": "object",
|
||||
"required": ["id", "petId", "quantity", "shipDate"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"petId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"shipDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["placed", "approved", "delivered"]
|
||||
},
|
||||
"complete": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"required": ["id", "username"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NewUser": {
|
||||
"type": "object",
|
||||
"required": ["username"],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
libarchive/examples/baobab/specification/openapi_to_specification.vsh
Executable file
13
libarchive/examples/baobab/specification/openapi_to_specification.vsh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import os
|
||||
|
||||
const example_dir = os.dir(@FILE)
|
||||
const openapi_spec_path = os.join_path(example_dir, 'openapi.json')
|
||||
|
||||
// the actor specification obtained from the OpenRPC Specification
|
||||
openapi_spec := openapi.new(path: openapi_spec_path)!
|
||||
actor_specification := specification.from_openapi(openapi_spec)!
|
||||
println(actor_specification)
|
||||
857
libarchive/examples/baobab/specification/openrpc.json
Normal file
857
libarchive/examples/baobab/specification/openrpc.json
Normal file
@@ -0,0 +1,857 @@
|
||||
{
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Zinit JSON-RPC API",
|
||||
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
|
||||
"license": {
|
||||
"name": "MIT"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"name": "Unix Socket",
|
||||
"url": "unix:///tmp/zinit.sock"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "rpc.discover",
|
||||
"description": "Returns the OpenRPC specification for the API",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "OpenRPCSpec",
|
||||
"description": "The OpenRPC specification",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "service_list",
|
||||
"description": "Lists all services managed by Zinit",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceList",
|
||||
"description": "A map of service names to their current states",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "Service state (Running, Success, Error, etc.)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "List all services",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ServiceListResult",
|
||||
"value": {
|
||||
"service1": "Running",
|
||||
"service2": "Success",
|
||||
"service3": "Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_status",
|
||||
"description": "Shows detailed status information for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatus",
|
||||
"description": "Detailed status information for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the running service (if running)"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Current state of the service (Running, Success, Error, etc.)"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target state of the service (Up, Down)"
|
||||
},
|
||||
"after": {
|
||||
"type": "object",
|
||||
"description": "Dependencies of the service and their states",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "State of the dependency"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get status of redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatusResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"state": "Running",
|
||||
"target": "Up",
|
||||
"after": {
|
||||
"dependency1": "Success",
|
||||
"dependency2": "Running"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_start",
|
||||
"description": "Starts a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to start",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"description": "Result of the start operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stop",
|
||||
"description": "Stops a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to stop",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"description": "Result of the stop operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StopResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_monitor",
|
||||
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to monitor",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"description": "Result of the monitor operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Monitor redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "MonitorResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32001,
|
||||
"message": "Service already monitored",
|
||||
"data": "service \"redis\" already monitored"
|
||||
},
|
||||
{
|
||||
"code": -32005,
|
||||
"message": "Config error",
|
||||
"data": "failed to load service configuration"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_forget",
|
||||
"description": "Stops monitoring a service. You can only forget a stopped service.",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to forget",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"description": "Result of the forget operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Forget redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ForgetResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32002,
|
||||
"message": "Service is up",
|
||||
"data": "service \"redis\" is up"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_kill",
|
||||
"description": "Sends a signal to a running service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to send the signal to",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"description": "Result of the kill operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Send SIGTERM to redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
},
|
||||
{
|
||||
"name": "signal",
|
||||
"value": "SIGTERM"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "KillResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
},
|
||||
{
|
||||
"code": -32004,
|
||||
"message": "Invalid signal",
|
||||
"data": "invalid signal: INVALID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_shutdown",
|
||||
"description": "Stops all services and powers off the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"description": "Result of the shutdown operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Shutdown the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "ShutdownResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_reboot",
|
||||
"description": "Stops all services and reboots the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"description": "Result of the reboot operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Reboot the system",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "RebootResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32006,
|
||||
"message": "Shutting down",
|
||||
"data": "system is already shutting down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_create",
|
||||
"description": "Creates a new service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to create",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"description": "The service configuration content",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exec": {
|
||||
"type": "string",
|
||||
"description": "Command to run"
|
||||
},
|
||||
"oneshot": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the service should be restarted"
|
||||
},
|
||||
"after": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Services that must be running before this one starts"
|
||||
},
|
||||
"log": {
|
||||
"type": "string",
|
||||
"enum": ["null", "ring", "stdout"],
|
||||
"description": "How to handle service output"
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Environment variables for the service"
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"type": "integer",
|
||||
"description": "Maximum time to wait for service to stop during shutdown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "CreateServiceResult",
|
||||
"description": "Result of the create operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32007,
|
||||
"message": "Service already exists",
|
||||
"data": "Service 'name' already exists"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to create service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_delete",
|
||||
"description": "Deletes a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "DeleteServiceResult",
|
||||
"description": "Result of the delete operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to delete service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_get",
|
||||
"description": "Gets a service configuration file",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "GetServiceResult",
|
||||
"description": "The service configuration",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "Service 'name' not found"
|
||||
},
|
||||
{
|
||||
"code": -32008,
|
||||
"message": "Service file error",
|
||||
"data": "Failed to read service file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "service_stats",
|
||||
"description": "Get memory and CPU usage statistics for a service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the service to get stats for",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStats",
|
||||
"description": "Memory and CPU usage statistics for the service",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Service name"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the service"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"description": "Stats for child processes",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "Process ID of the child process"
|
||||
},
|
||||
"memory_usage": {
|
||||
"type": "integer",
|
||||
"description": "Memory usage in bytes"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"type": "number",
|
||||
"description": "CPU usage as a percentage (0-100)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get stats for redis service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "ServiceStatsResult",
|
||||
"value": {
|
||||
"name": "redis",
|
||||
"pid": 1234,
|
||||
"memory_usage": 10485760,
|
||||
"cpu_usage": 2.5,
|
||||
"children": [
|
||||
{
|
||||
"pid": 1235,
|
||||
"memory_usage": 5242880,
|
||||
"cpu_usage": 1.2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32000,
|
||||
"message": "Service not found",
|
||||
"data": "service name \"unknown\" unknown"
|
||||
},
|
||||
{
|
||||
"code": -32003,
|
||||
"message": "Service is down",
|
||||
"data": "service \"redis\" is down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_start_http_server",
|
||||
"description": "Start an HTTP/RPC server at the specified address",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"description": "Result of the start HTTP server operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Start HTTP server on localhost:8080",
|
||||
"params": [
|
||||
{
|
||||
"name": "address",
|
||||
"value": "127.0.0.1:8080"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "StartHttpServerResult",
|
||||
"value": "HTTP server started at 127.0.0.1:8080"
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Invalid address",
|
||||
"data": "Invalid network address format"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "system_stop_http_server",
|
||||
"description": "Stop the HTTP/RPC server if running",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"description": "Result of the stop HTTP server operation",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Stop the HTTP server",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "StopHttpServerResult",
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "Server not running",
|
||||
"data": "No HTTP server is currently running"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_currentLogs",
|
||||
"description": "Get current logs from zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"description": "Array of log strings",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Get all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:01 nginx: Starting service"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get logs for a specific service",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogsResult",
|
||||
"value": [
|
||||
"2023-01-01T12:00:00 redis: Starting service",
|
||||
"2023-01-01T12:00:02 redis: Service started"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_subscribeLogs",
|
||||
"description": "Subscribe to log messages generated by zinit and monitored services",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"description": "A subscription to log messages",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "Subscribe to all logs",
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Subscribe to filtered logs",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "redis"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "LogSubscription",
|
||||
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
132
libarchive/examples/baobab/specification/openrpc0.json
Normal file
132
libarchive/examples/baobab/specification/openrpc0.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"openrpc": "1.0.0",
|
||||
"info": {
|
||||
"title": "PetStore",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "GetPets",
|
||||
"description": "finds pets in the system that the user has access to by tags and within a limit",
|
||||
"params": [
|
||||
{
|
||||
"name": "tags",
|
||||
"description": "tags to filter by",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "maximum number of results to return",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet_list",
|
||||
"description": "all pets from the system, that mathes the tags",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CreatePet",
|
||||
"description": "creates a new pet in the store. Duplicates are allowed.",
|
||||
"params": [
|
||||
{
|
||||
"name": "new_pet",
|
||||
"description": "Pet to add to the store.",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/NewPet"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet",
|
||||
"description": "the newly created pet",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GetPetById",
|
||||
"description": "gets a pet based on a single ID, if the user has access to the pet",
|
||||
"params": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of pet to fetch",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet",
|
||||
"description": "pet response",
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/Pet"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DeletePetById",
|
||||
"description": "deletes a single pet based on the ID supplied",
|
||||
"params": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of pet to delete",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "pet",
|
||||
"description": "pet deleted",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"schemas": {
|
||||
"NewPet": {
|
||||
"title": "NewPet",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pet": {
|
||||
"title": "Pet",
|
||||
"description": "a pet struct that represents a pet",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "name of the pet",
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"description": "a tag of the pet, helps finding pet",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "unique indentifier",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
libarchive/examples/baobab/specification/openrpc_to_specification.vsh
Executable file
13
libarchive/examples/baobab/specification/openrpc_to_specification.vsh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
const example_dir = os.dir(@FILE)
|
||||
const openrpc_spec_path = os.join_path(example_dir, 'openrpc.json')
|
||||
|
||||
// the actor specification obtained from the OpenRPC Specification
|
||||
openrpc_spec := openrpc.new(path: openrpc_spec_path)!
|
||||
actor_specification := specification.from_openrpc(openrpc_spec)!
|
||||
println(actor_specification)
|
||||
107
libarchive/examples/baobab/specification/specification_to_openapi.vsh
Executable file
107
libarchive/examples/baobab/specification/specification_to_openapi.vsh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import json
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
const actor_specification = specification.ActorSpecification{
|
||||
name: 'PetStore'
|
||||
interfaces: [.openrpc]
|
||||
methods: [
|
||||
specification.ActorMethod{
|
||||
name: 'GetPets'
|
||||
description: 'finds pets in the system that the user has access to by tags and within a limit'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'tags'
|
||||
description: 'tags to filter by'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
}))
|
||||
})
|
||||
},
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'limit'
|
||||
description: 'maximum number of results to return'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'integer'
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pet_list'
|
||||
description: 'all pets from the system, that matches the tags'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
}
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'CreatePet'
|
||||
description: 'creates a new pet in the store. Duplicates are allowed.'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'new_pet'
|
||||
description: 'Pet to add to the store.'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/NewPet'
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'the newly created pet'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
}
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'GetPetById'
|
||||
description: 'gets a pet based on a single ID, if the user has access to the pet'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'id'
|
||||
description: 'ID of pet to fetch'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'integer'
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'pet response'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
}
|
||||
},
|
||||
specification.ActorMethod{
|
||||
name: 'DeletePetById'
|
||||
description: 'deletes a single pet based on the ID supplied'
|
||||
parameters: [
|
||||
openrpc.ContentDescriptor{
|
||||
name: 'id'
|
||||
description: 'ID of pet to delete'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'integer'
|
||||
})
|
||||
},
|
||||
]
|
||||
result: openrpc.ContentDescriptor{
|
||||
name: 'pet'
|
||||
description: 'pet deleted'
|
||||
schema: jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'null'
|
||||
})
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
openapi_specification := actor_specification.to_openapi()
|
||||
println(json.encode_pretty(openapi_specification))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user