From 715f4813640416a1b85506ebd2ca5ed99300e1b4 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Thu, 13 Feb 2025 02:33:24 +0300 Subject: [PATCH] fix generated code compilation for example --- lib/baobab/generator/generate_act.v | 2 +- lib/baobab/generator/generate_clients.v | 2 +- lib/baobab/generator/generate_methods.v | 54 +++++++++++++------ .../generator/generate_methods_example.v | 41 ++++++++++---- .../generator/generate_methods_interface.v | 4 +- lib/baobab/specification/from_openapi.v | 28 ++++++++-- lib/baobab/specification/model.v | 1 + 7 files changed, 96 insertions(+), 36 deletions(-) diff --git a/lib/baobab/generator/generate_act.v b/lib/baobab/generator/generate_act.v index 36ff5630..1952d603 100644 --- a/lib/baobab/generator/generate_act.v +++ b/lib/baobab/generator/generate_act.v @@ -67,7 +67,7 @@ pub fn generate_method_handle(actor_name string, method ActorMethod) !Function { 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}]', param)! + decode_stmt := generate_decode_stmt('params_arr[${i}].str()', param)! body += '${param_name} := ${decode_stmt}' } } diff --git a/lib/baobab/generator/generate_clients.v b/lib/baobab/generator/generate_clients.v index 809559a6..4ac6073e 100644 --- a/lib/baobab/generator/generate_clients.v +++ b/lib/baobab/generator/generate_clients.v @@ -92,7 +92,7 @@ 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})').join(', ') + method.parameters.map(texttools.snake_case(it.name)).map('Any(${it}.str())').join(', ') } else {''} params_stmt := if method.parameters.len == 0 { diff --git a/lib/baobab/generator/generate_methods.v b/lib/baobab/generator/generate_methods.v index 22176928..5f204032 100644 --- a/lib/baobab/generator/generate_methods.v +++ b/lib/baobab/generator/generate_methods.v @@ -2,7 +2,10 @@ module generator import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result } import freeflowuniverse.herolib.core.texttools -import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter} +import freeflowuniverse.herolib.schemas.openrpc {ContentDescriptor} +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 {schema_to_struct} import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification} const crud_prefixes = ['new', 'get', 'set', 'delete', 'list'] @@ -14,24 +17,13 @@ pub fn generate_methods_file(spec ActorSpecification) !VFile { receiver := generate_methods_receiver(spec.name) receiver_param := Param { mutable: true - name: name_snake + name: name_snake[0].ascii_str() // receiver is first letter of domain typ: code.Result{code.Object{receiver.name}} } mut items := [CodeItem(receiver), CodeItem(generate_core_factory(receiver_param))] for method in spec.methods { - method_fn := generate_method_function(receiver_param, method)! - // check if method is a Base Object CRUD Method and - // if so generate the method's body - body := match spec.method_type(method) { - .base_object_new { base_object_new_body(method)! } - .base_object_get { base_object_get_body(method)! } - .base_object_set { base_object_set_body(method)! } - .base_object_delete { base_object_delete_body(method)! } - .base_object_list { base_object_list_body(method)! } - else {"panic('implement')"} - } - items << Function{...method_fn, body: body} + items << generate_method_code(receiver_param, ActorMethod{...method, category: spec.method_type(method)})! } return VFile { @@ -52,14 +44,44 @@ fn generate_methods_receiver(name string) code.Struct { fn generate_core_factory(receiver code.Param) code.Function { return code.Function { is_pub: true - name: 'new_${receiver.name}' + 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_function(receiver code.Param, method ActorMethod) !Function { +pub fn generate_method_code(receiver code.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 + body := match method.category { + .base_object_new { base_object_new_body(method)! } + .base_object_get { base_object_get_body(method)! } + .base_object_set { base_object_set_body(method)! } + .base_object_delete { base_object_delete_body(method)! } + .base_object_list { base_object_list_body(method)! } + else {"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 code.Param, method ActorMethod) !Function { result_param := content_descriptor_to_parameter(method.result)! return Function{ name: texttools.snake_case(method.name) diff --git a/lib/baobab/generator/generate_methods_example.v b/lib/baobab/generator/generate_methods_example.v index 33b6a5e6..51998d54 100644 --- a/lib/baobab/generator/generate_methods_example.v +++ b/lib/baobab/generator/generate_methods_example.v @@ -3,6 +3,8 @@ module generator import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result } import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.schemas.openrpc {Example} +import freeflowuniverse.herolib.schemas.jsonschema {Schema} +import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schema_to_struct} import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter} import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification} @@ -13,15 +15,12 @@ pub fn generate_methods_example_file(spec ActorSpecification) !VFile { receiver := generate_example_methods_receiver(spec.name) receiver_param := Param { mutable: true - name: name_snake + name: name_snake[0].ascii_str() typ: code.Result{code.Object{receiver.name}} } mut items := [CodeItem(receiver), CodeItem(generate_core_example_factory(receiver_param))] for method in spec.methods { - method_fn := generate_method_function(receiver_param, method)! - items << Function{...method_fn, - body: generate_method_example_body(method_fn, method)! - } + items << generate_method_example_code(receiver_param, ActorMethod{...method, category: spec.method_type(method)})! } return VFile { @@ -37,7 +36,7 @@ pub fn generate_methods_example_file(spec ActorSpecification) !VFile { fn generate_core_example_factory(receiver code.Param) code.Function { return code.Function { is_pub: true - name: 'new_${receiver.name}_example' + name: 'new_${texttools.snake_case(receiver.typ.symbol())}' body: "return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}" result: receiver } @@ -51,15 +50,35 @@ fn generate_example_methods_receiver(name string) code.Struct { } } -fn generate_method_example_body(func Function, method ActorMethod) !string { - return if !method_is_void(method)! { + +// returns bodyless method prototype +pub fn generate_method_example_code(receiver code.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 { - "data := '${method.example.result.value}' - return ${generate_decode_stmt('data', method.result)!}" + "json_str := '${method.example.result.value}' + return ${generate_decode_stmt('json_str', method.result)!}" } else { - "return ${func.result.typ.empty_value()}" + "return ${result_param.typ.empty_value()}" } } else { "" } + + fn_prototype := generate_method_prototype(receiver, method)! + method_code << Function{ + ...fn_prototype + body: body + } + return method_code } \ No newline at end of file diff --git a/lib/baobab/generator/generate_methods_interface.v b/lib/baobab/generator/generate_methods_interface.v index 94941679..ad6b4bbd 100644 --- a/lib/baobab/generator/generate_methods_interface.v +++ b/lib/baobab/generator/generate_methods_interface.v @@ -20,12 +20,12 @@ pub fn generate_methods_interface_declaration(spec ActorSpecification) !code.Int receiver := generate_methods_receiver(spec.name) receiver_param := Param { mutable: true - name: name_snake + 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_function(receiver_param, it)!) + methods: spec.methods.map(generate_method_prototype(receiver_param, it)!) } } diff --git a/lib/baobab/specification/from_openapi.v b/lib/baobab/specification/from_openapi.v index 3312acbd..88baa318 100644 --- a/lib/baobab/specification/from_openapi.v +++ b/lib/baobab/specification/from_openapi.v @@ -1,8 +1,9 @@ module specification +import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.code { Struct, Function } import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef } -import freeflowuniverse.herolib.schemas.openapi { Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec } +import freeflowuniverse.herolib.schemas.openapi { Operation, Parameter, OpenAPI, Components, Info, PathItem, ServerSpec, MediaType } import freeflowuniverse.herolib.schemas.openrpc { ExamplePairing, Example, ExampleRef, ContentDescriptor, ErrorSpec } // Helper function: Convert OpenAPI parameter to ContentDescriptor @@ -42,19 +43,36 @@ fn openapi_operation_to_actor_method(info openapi.OperationInfo) ActorMethod { } } - response_200 := info.operation.responses['200'].content['application/json'] + 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_200.schema + schema: response_success.schema } - example_result := if response_200.example.str() != '' { + example_result := if response_success.example.str() != '' { Example{ name: 'Example response', - value: response_200.example + value: response_success.example } } else {Example{}} diff --git a/lib/baobab/specification/model.v b/lib/baobab/specification/model.v index 3eda7824..931a6ed5 100644 --- a/lib/baobab/specification/model.v +++ b/lib/baobab/specification/model.v @@ -35,6 +35,7 @@ pub: parameters []ContentDescriptor result ContentDescriptor errors []ErrorSpec + category MethodCategory } pub struct BaseObject {