openapi json model, processing & parsing fixes

This commit is contained in:
timurgordon
2025-01-30 04:53:08 +03:00
parent ee8fbbca09
commit 5cb30e6783
5 changed files with 101 additions and 11 deletions

View File

@@ -1,10 +1,11 @@
module jsonschema
import x.json2 { Any }
import json
pub fn decode(data string) !Schema {
schema_map := json2.raw_decode(data)!.as_map()
mut schema := json2.decode[Schema](data)!
mut schema := json.decode(Schema, data)!
for key, value in schema_map {
if key == 'properties' {
schema.properties = decode_schemaref_map(value.as_map())!

View File

@@ -8,7 +8,7 @@ pub type SchemaRef = Reference | Schema
pub struct Reference {
pub:
ref string @[json: 'ref']
ref string @[json: '\$ref']
}
pub type Number = int
@@ -21,10 +21,10 @@ pub mut:
title string @[omitempty]
description string @[omitempty]
typ string @[json: 'type'; omitempty]
properties map[string]SchemaRef @[omitempty]
properties map[string]SchemaRef @[json: '-'; omitempty]
additional_properties SchemaRef @[json: 'additionalProperties'; omitempty]
required []string @[omitempty]
items Items @[omitempty]
items Items @[json: '-'; omitempty]
defs map[string]SchemaRef @[omitempty]
one_of []SchemaRef @[json: 'oneOf'; omitempty]
format string @[omitempty]

View File

@@ -83,10 +83,8 @@ pub fn json_decode_path(path_ PathItem, path_map map[string]Any) !PathItem {
pub fn json_decode_operation(operation_ Operation, operation_map map[string]Any) !Operation {
mut operation := operation_
if request_body_any := operation_map['requestBody'] {
request_body_map := request_body_any.as_map()
if content_any := request_body_map['content'] {
mut request_body := json.decode(RequestBody, request_body_any.str())!
// mut request_body := operation.request_body as RequestBody
@@ -134,11 +132,12 @@ fn json_decode_content(content_ map[string]MediaType, content_map map[string]Any
mut content := content_.clone()
for key, item in content_map {
media_type_map := item.as_map()
mut media_type := content[key]
if schema_any := media_type_map['schema'] {
media_type.schema = jsonschema.decode_schemaref(schema_any.as_map())!
if mut media_type := content[key] {
if schema_any := media_type_map['schema'] {
media_type.schema = jsonschema.decode_schemaref(schema_any.as_map())!
}
content[key] = media_type
}
content[key] = media_type
}
return content
}

View File

@@ -1,5 +1,7 @@
module openapi
import freeflowuniverse.herolib.schemas.jsonschema {Schema, SchemaRef, Reference}
import freeflowuniverse.herolib.core.texttools
import os
@[params]
@@ -22,5 +24,74 @@ pub fn new(params Params) !OpenAPI {
os.read_file(params.path)!
} else { params.text }
return json_decode(text)!
specification := json_decode(text)!
return process(specification)!
}
fn process(spec OpenAPI) !OpenAPI {
mut processed := OpenAPI{...spec}
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
}
}
for path, item in spec.paths {
mut processed_item := PathItem{...item}
processed_item.get = processed.process_operation(item.get, 'get', path)!
processed_item.post = processed.process_operation(item.post, 'post', path)!
processed_item.put = processed.process_operation(item.put, 'put', path)!
processed_item.delete = processed.process_operation(item.delete, 'delete', path)!
processed_item.patch = processed.process_operation(item.patch, 'patch', path)!
processed_item.options = processed.process_operation(item.options, 'options', path)!
processed_item.head = processed.process_operation(item.head, 'head', path)!
processed_item.trace = processed.process_operation(item.trace, 'trace', path)!
processed.paths[path] = processed_item
}
return processed
}
fn (spec OpenAPI) process_operation(op Operation, method string, path string) !Operation {
mut processed := Operation{...op}
if processed.operation_id == '' {
processed.operation_id = generate_operation_id(method, path)
}
if op.request_body is RequestBody {
if content := op.request_body.content['application/json'] {
if content.schema is Reference {
mut req_body_ := RequestBody{...op.request_body}
req_body_.content['application/json'].schema = SchemaRef(spec.dereference_schema(content.schema)!)
processed.request_body = RequestBodyRef(req_body_)
}
}
}
return processed
}
// Helper function to generate a unique operationId
fn generate_operation_id(method string, path string) string {
// Convert HTTP method and path into a camelCase string
method_part := method.to_lower()
path_part := path.all_before('{')
.replace('/', '_') // Replace slashes with underscores
.replace('{', '') // Remove braces around path parameters
.replace('}', '') // Remove braces around path parameters
return texttools.camel_case('${method_part}_${path_part}')
}

View File

@@ -1,5 +1,6 @@
module openapi
import maps
import x.json2 as json {Any}
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference, SchemaRef}
@@ -121,6 +122,15 @@ pub mut:
path_items map[string]PathItemRef // An object to hold reusable Path Item Object.
}
pub fn (s OpenAPI) dereference_schema(ref Reference) !Schema {
schema_ref := s.components.schemas[ref.ref.all_after_last('/')] or {
return error('Reference ${ref} not found in schema')
}
if schema_ref is Schema {
return schema_ref
}
return error('Reference references another reference')
}
type Items = SchemaRef | []SchemaRef
@@ -176,6 +186,15 @@ pub mut:
servers []ServerSpec @[omitempty]// An alternative server array to service this operation. If an alternative server object is specified at the Path Item Object or Root level, it will be overridden by this value.
}
// returns errors responses amongst a map of response specs
pub fn (r map[string]ResponseSpec) errors() map[string]ResponseSpec {
return maps.filter(r, fn (k string, v ResponseSpec) bool {
return if k.is_int() {
k.int() >= 400 && k.int()< 600
} else { false }
})
}
// TODO: currently using map[string]ResponseSpec
pub struct Responses {
pub: