baobab fixes to better support openapi codegen

This commit is contained in:
timurgordon
2025-02-01 11:57:11 +03:00
parent 7e8a4c5c45
commit 1db2c3ee54
8 changed files with 324 additions and 56 deletions

68
lib/baobab/actor/client.v Normal file
View 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
}
}

View File

@@ -31,18 +31,18 @@ fn openapi_param_to_example(param Parameter) ?Example {
}
// Helper function: Convert OpenAPI operation to ActorMethod
fn openapi_operation_to_actor_method(op Operation, method_name string, path string) ActorMethod {
fn openapi_operation_to_actor_method(info openapi.OperationInfo) ActorMethod {
mut parameters := []ContentDescriptor{}
mut example_parameters:= []Example{}
for param in op.parameters {
for param in info.operation.parameters {
parameters << openapi_param_to_content_descriptor(param)
example_parameters << openapi_param_to_example(param) or {
continue
}
}
response_200 := op.responses['200'].content['application/json']
response_200 := info.operation.responses['200'].content['application/json']
mut result := ContentDescriptor{
name: "result",
@@ -66,7 +66,7 @@ fn openapi_operation_to_actor_method(op Operation, method_name string, path stri
} else {ExamplePairing{}}
mut errors := []ErrorSpec{}
for status, response in op.responses {
for status, response in info.operation.responses {
if status.int() >= 400 {
error_schema := if response.content.len > 0 {
response.content.values()[0].schema
@@ -80,9 +80,9 @@ fn openapi_operation_to_actor_method(op Operation, method_name string, path stri
}
return ActorMethod{
name: method_name,
description: op.description,
summary: op.summary,
name: info.operation.operation_id,
description: info.operation.description,
summary: info.operation.summary,
parameters: parameters,
example: pairing
result: result,
@@ -99,37 +99,39 @@ fn openapi_schema_to_struct(name string, schema SchemaRef) Struct {
}
// Converts OpenAPI to ActorSpecification
pub fn from_openapi(spec OpenAPI) !ActorSpecification {
mut methods := []ActorMethod{}
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)
}
}
// 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 {
@@ -137,11 +139,12 @@ pub fn from_openapi(spec OpenAPI) !ActorSpecification {
}
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: methods,
methods: spec.get_operations().map(openapi_operation_to_actor_method(it)),
objects: objects,
}
}

View File

@@ -88,7 +88,7 @@ pub fn from_openrpc(spec OpenRPC) !ActorSpecification {
objects << BaseObject{
schema: Schema {...schema,
title: texttools.name_fix_pascal(key)
id: texttools.name_fix_snake(key)
id: texttools.snake_case(key)
}
}
}

View File

@@ -1,11 +1,14 @@
module specification
import freeflowuniverse.herolib.core.code { Struct, Function }
import freeflowuniverse.herolib.schemas.openapi
import freeflowuniverse.herolib.schemas.openrpc {ExamplePairing, ContentDescriptor, ErrorSpec}
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference}
pub struct ActorSpecification {
pub mut:
openapi ?openapi.OpenAPI
openrpc ?openrpc.OpenRPC
name string @[omitempty]
description string @[omitempty]
structure Struct @[omitempty]

View File

@@ -1,34 +1,25 @@
module specification
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
import freeflowuniverse.herolib.schemas.openapi { Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec }
import freeflowuniverse.herolib.schemas.openapi { MediaType, ResponseSpec, Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec }
import net.http
// Converts ActorSpecification to OpenAPI
pub fn (s ActorSpecification) to_openapi() OpenAPI {
if openapi_spec := s.openapi {
return openapi_spec
}
mut paths := map[string]PathItem{}
// Map ActorMethods to paths
for method in s.methods {
mut op := Operation{
summary: method.summary,
description: method.description,
operation_id: method.name,
op := method.to_openapi_operation()
paths['${method.http_path()}'] = match method.http_method() {
.get { PathItem {get: op} }
else { panic('unsupported http method') }
}
// 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,
}
}
// Assign operation to corresponding HTTP method
// TODO: what about other verbs
paths['/${method.name}'] = PathItem{get: op}
}
mut schemas := map[string]SchemaRef{}
@@ -60,3 +51,41 @@ pub fn (s ActorSpecification) to_openapi() OpenAPI {
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
}

View File

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

View File

@@ -12,6 +12,7 @@ mut:
pub struct Actor {
pub:
name string
redis_url string = 'localhost:6379'
mut:
osis OSIS
}

View File

@@ -18,7 +18,12 @@ pub fn new_openapi_interface(client Client) &OpenAPIInterface {
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)!
println('debugzo3 ${action}')
response := i.client.call_to_action(action) or {
println('debugzo3.5 ${err.msg()}')
return err
}
println('debugzo4 ${response}')
return action_to_openapi_response(response)
}