feat: generate dynamic API docs from OpenRPC spec

- Implement dynamic doc generation from OpenRPC methods
- Generate example calls and responses from schemas
- Improve OpenRPC and JSON Schema decoders for full parsing
- Add example value generation based on schema type
- Add tests for schema decoding with examples
This commit is contained in:
Mahmoud-Emad
2025-09-17 17:50:43 +03:00
parent 844e3d5214
commit e4101351aa
8 changed files with 299 additions and 57 deletions

View File

@@ -4,6 +4,30 @@ pub fn (schema Schema) type_() string {
return schema.typ.str()
}
// example_value generates a basic example value based on the schema type.
// Returns a JSON-formatted string appropriate for the schema type.
pub fn (schema Schema) example_value() string {
return ''
}
// Check if schema has an explicit example value (ignore empty arrays which indicate no example)
example_str := schema.example.str()
if example_str != '' && example_str != '[]' {
// For object examples, return the JSON string as-is
if schema.typ == 'object' || example_str.starts_with('{') {
return example_str
}
// For string types, ensure proper JSON formatting with quotes
if schema.typ == 'string' && !example_str.starts_with('"') {
return '"${example_str}"'
}
return example_str
}
// Generate type-based example when no explicit example is provided
match schema.typ {
'string' { return '"example_value"' }
'integer', 'number' { return '42' }
'boolean' { return 'true' }
'array' { return '[]' }
'object' { return '{}' }
else { return '"example_value"' }
}
}

View File

@@ -3,9 +3,14 @@ module jsonschema
import x.json2 { Any }
import json
// decode parses a JSON Schema string into a Schema struct.
// Handles complex fields like properties, additionalProperties, items, and examples
// that require custom parsing beyond standard JSON decoding.
pub fn decode(data string) !Schema {
schema_map := json2.raw_decode(data)!.as_map()
mut schema := json.decode(Schema, data)!
// Process fields that require custom decoding
for key, value in schema_map {
if key == 'properties' {
schema.properties = decode_schemaref_map(value.as_map())!
@@ -13,6 +18,9 @@ pub fn decode(data string) !Schema {
schema.additional_properties = decode_schemaref(value.as_map())!
} else if key == 'items' {
schema.items = decode_items(value)!
} else if key == 'example' {
// Manually handle example field since it's marked with @[json: '-'] in the Schema struct
schema.example = value
}
}
return schema
@@ -41,11 +49,15 @@ pub fn decode_schemaref_map(data_map map[string]Any) !map[string]SchemaRef {
return schemaref_map
}
// decode_schemaref parses a map into either a Schema or Reference.
// Handles both direct schema definitions and $ref references to external schemas.
pub fn decode_schemaref(data_map map[string]Any) !SchemaRef {
if ref := data_map['\$ref'] {
return Reference{
ref: ref.str()
}
}
return decode(data_map.str())!
// Convert map back to JSON string for proper schema decoding with custom field handling
json_str := json2.encode(data_map)
return decode(json_str)!
}

View File

@@ -42,3 +42,49 @@ fn test_decode_schemaref() ! {
required: ['name']
}
}
fn test_decode_with_example() ! {
// Test schema with example field
schema_with_example := '{
"type": "string",
"description": "A test string",
"example": "test_value"
}'
schema := decode(schema_with_example)!
assert schema.typ == 'string'
assert schema.description == 'A test string'
assert schema.example.str() == 'test_value'
}
fn test_decode_with_object_example() ! {
// Test schema with object example
schema_with_object_example := '{
"type": "object",
"description": "A test object",
"example": {
"name": "test",
"value": 123
}
}'
schema := decode(schema_with_object_example)!
assert schema.typ == 'object'
assert schema.description == 'A test object'
// Object examples are stored as json2.Any and need special handling
assert schema.example.str().contains('test')
}
fn test_decode_without_example() ! {
// Test schema without example field
schema_without_example := '{
"type": "integer",
"description": "A test integer"
}'
schema := decode(schema_without_example)!
assert schema.typ == 'integer'
assert schema.description == 'A test integer'
// Should have empty example
assert schema.example.str() == '[]'
}