better openapi schema support

This commit is contained in:
timurgordon
2025-02-11 17:10:00 +03:00
parent 5a8daa3feb
commit 80254739b0
5 changed files with 77 additions and 23 deletions

View File

@@ -18,12 +18,9 @@ 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)
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)
}

View File

@@ -50,7 +50,7 @@ pub const type_f64 = Float{
bytes: 64
}
pub type Type = Array | Object | Result | Integer | Alias | String | Boolean | Void
pub type Type = Void | Map | Array | Object | Result | Integer | Alias | String | Boolean | Function
pub struct Boolean{}
@@ -94,6 +94,8 @@ pub fn (t Type) symbol() string {
Alias {t.name}
String {'string'}
Boolean {'bool'}
Map{'map[string]${t.typ.symbol()}'}
Function{'fn ()'}
Void {''}
}
}
@@ -105,6 +107,11 @@ pub:
typ Type
}
pub struct Map {
pub:
typ Type
}
pub struct Object {
pub:
name string
@@ -117,6 +124,7 @@ pub:
pub fn (t Type) typescript() string {
return match t {
Map {'Record<string, ${t.typ.typescript()}>'}
Array { '${t.typ.typescript()}[]' }
Object { t.name }
Result { '${t.typ.typescript()}'}
@@ -124,6 +132,7 @@ pub fn (t Type) typescript() string {
Integer { 'number' }
Alias {t.name}
String {'string'}
Function {'func'}
Void {''}
}
}
@@ -131,4 +140,19 @@ pub fn (t Type) typescript() string {
// TODO: enfore that cant be both mutable and shared
pub fn (t Type) vgen() string {
return t.symbol()
}
pub fn (t Type) empty_value() string {
return match t {
Map {'{}'}
Array { '[]${t.typ.symbol()}{}' }
Object { if t.name != '' {'${t.name}{}'} else {''} }
Result { t.typ.empty_value() }
Boolean { 'false' }
Integer { '0' }
Alias {''}
String {"''"}
Function {''}
Void {''}
}
}

View File

@@ -5,6 +5,7 @@ import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef, Referenc
const vtypes = {
'integer': 'int'
'number': 'int'
'string': 'string'
'u32': 'u32'
'boolean': 'bool'
@@ -64,9 +65,14 @@ pub fn schema_to_type(schema Schema) Type {
return match schema.typ {
'object' {
if schema.title == '' {
panic('Object schemas must define a title.')
panic('Object schemas must define a title. ${schema}')
}
Object{schema.title}
if schema.properties.len == 0 {
if additional_props := schema.additional_properties {
code.Map{code.String{}}
} else {Object{schema.title}}
}
else {Object{schema.title}}
}
'array' {
// todo: handle multiple item schemas
@@ -102,7 +108,7 @@ pub fn schema_to_type(schema Schema) Type {
panic('unknown type `${schema.typ}` ')
}
}
}
}
}
pub fn schema_to_code(schema Schema) CodeItem {
@@ -170,9 +176,8 @@ pub fn ref_to_field(schema_ref SchemaRef, name string) StructField {
name: name
description: schema_ref.description
}
if schema_ref.typ == 'object' {
// then it is an anonymous struct
field.anon_struct = schema_to_struct(schema_ref as Schema)
if schema_ref.typ == 'object' || schema_ref.typ == 'array' {
field.typ = schemaref_to_type(schema_ref)
return field
} else if schema_ref.typ in vtypes {
field.typ = type_from_symbol(vtypes[schema_ref.typ])

View File

@@ -103,7 +103,6 @@ pub fn (mut c HTTPController) endpoints(mut ctx Context, path string) veb.Result
// Use OpenAPI spec to determine the response status for the error
return ctx.handle_error(operation.responses, err)
}
println('debugzo2 ${response}')
// Return the response to the client
ctx.res.set_status(response.status)

View File

@@ -3,6 +3,7 @@ module openapi
import freeflowuniverse.herolib.schemas.jsonschema {Schema, SchemaRef, Reference}
import freeflowuniverse.herolib.core.texttools
import os
import maps
@[params]
pub struct Params {
@@ -31,6 +32,27 @@ pub fn new(params Params) !OpenAPI {
} else {specification}
}
@[params]
pub struct ProcessSchema {
pub:
name string = 'Unknown'
}
pub fn process_schema(schema Schema, params ProcessSchema) Schema {
return Schema {
...schema
id: if schema.id != '' && schema.typ == 'object' { schema.id }
else if schema.title != '' { schema.title }
else { params.name }
title: if schema.title != '' && schema.typ == 'object' { schema.title }
else if schema.id != '' { schema.id }
else { params.name }
properties: maps.to_map[string, SchemaRef, string, SchemaRef](schema.properties, fn (k string, v SchemaRef) (string, SchemaRef) {
return k, if v is Schema {SchemaRef(process_schema(v))} else {v}
})
}
}
pub fn process(spec OpenAPI) !OpenAPI {
mut processed := OpenAPI{...spec
paths: spec.paths.clone()
@@ -38,16 +60,7 @@ pub fn process(spec OpenAPI) !OpenAPI {
for key, schema in spec.components.schemas {
if schema is Schema {
mut processed_schema := Schema{
...schema,
id: if schema.id != '' { schema.id }
else if schema.title != '' { schema.title }
else { key }
title: if schema.title != '' { schema.title }
else if schema.id != '' { schema.id }
else { key }
}
processed.components.schemas[key] = processed_schema
processed.components.schemas[key] = process_schema(schema, name:key)
}
}
@@ -87,7 +100,17 @@ fn (spec OpenAPI) process_operation(op Operation, method string, path string) !O
mut req_body_ := RequestBody{...op.request_body
content: op.request_body.content.clone()
}
req_body_.content['application/json'].schema = SchemaRef(spec.dereference_schema(content.schema)!)
req_body_.content['application/json'].schema = SchemaRef(
process_schema(spec.dereference_schema(content.schema)!, name: content.schema.ref.all_after_last('/'))
)
processed.request_body = RequestBodyRef(req_body_)
} else if content.schema is Schema {
mut req_body_ := RequestBody{...op.request_body
content: op.request_body.content.clone()
}
req_body_.content['application/json'].schema = SchemaRef(
process_schema(content.schema)
)
processed.request_body = RequestBodyRef(req_body_)
}
}
@@ -99,7 +122,13 @@ fn (spec OpenAPI) process_operation(op Operation, method string, path string) !O
}
if media_type := processed_rs.content['application/json'] {
if media_type.schema is Reference {
processed_rs.content['application/json'].schema = SchemaRef(spec.dereference_schema(media_type.schema)!)
processed_rs.content['application/json'].schema = SchemaRef(
process_schema(spec.dereference_schema(media_type.schema)!, name: media_type.schema.ref.all_after_last('/'))
)
} else if media_type.schema is Schema {
processed_rs.content['application/json'].schema = SchemaRef(
process_schema(media_type.schema)
)
}
}
processed.responses['200'] = processed_rs