Files
herolib/lib/hero/heroserver/doc_model.v
Mahmoud-Emad 8ef9522676 refactor: Update method names and add curl example generation
- Rename API method names using dot notation
- Add endpoint_url and curl_example to DocMethod
- Implement generate_curl_example function
- Update DocMethod struct with new fields
2025-10-21 15:39:59 +03:00

271 lines
7.4 KiB
V

module heroserver
import incubaid.herolib.schemas.openrpc
import incubaid.herolib.schemas.jsonschema
// DocSpec is the main object passed to the documentation template.
pub struct DocSpec {
pub mut:
info openrpc.Info
methods []DocMethod
objects []DocObject
auth_info AuthDocInfo
base_url string // Dynamic base URL for examples
}
// DocObject represents a logical grouping of methods.
pub struct DocObject {
pub mut:
name string
description string
methods []DocMethod
}
// DocMethod holds the information for a single method to be displayed.
pub struct DocMethod {
pub mut:
name string
summary string
description string
params []DocParam
result DocParam
example_request string
example_response string
endpoint_url string
curl_example string
}
// DocParam represents a parameter or result in the documentation
pub struct DocParam {
pub mut:
name string
description string
type_info string
required bool
example string
}
// AuthDocInfo contains authentication flow information
pub struct AuthDocInfo {
pub mut:
enabled bool
steps []AuthStep
}
// AuthStep represents a single step in the authentication flow
pub struct AuthStep {
pub mut:
number int
title string
method string
endpoint string
description string
example string
}
// DocConfig holds configuration for documentation generation
pub struct DocConfig {
pub mut:
base_url string = 'http://localhost:8080'
handler_type string
auth_enabled bool = true
}
// doc_spec_from_openrpc converts an OpenRPC specification to a documentation-friendly DocSpec.
// Processes all methods, parameters, and results with proper type extraction and example generation.
// Returns error if handler_type is empty or if OpenRPC spec is invalid.
pub fn doc_spec_from_openrpc(openrpc_spec openrpc.OpenRPC, handler_type string) !DocSpec {
return doc_spec_from_openrpc_with_config(openrpc_spec, DocConfig{
handler_type: handler_type
})
}
// doc_spec_from_openrpc_with_config converts an OpenRPC specification with custom configuration
pub fn doc_spec_from_openrpc_with_config(openrpc_spec openrpc.OpenRPC, config DocConfig) !DocSpec {
if config.handler_type.trim_space() == '' {
return error('handler_type cannot be empty')
}
mut doc_spec := DocSpec{
info: openrpc_spec.info
base_url: config.base_url
auth_info: create_auth_info_with_config(config.auth_enabled)
}
// Process all methods - inflate each method to resolve $ref references
for method in openrpc_spec.methods {
// Inflate the method to resolve all schema references
inflated_method := openrpc_spec.inflate_method(method)
doc_method := process_method(inflated_method, config)!
doc_spec.methods << doc_method
}
return doc_spec
}
// process_method converts a single OpenRPC method to a DocMethod
fn process_method(method openrpc.Method, config DocConfig) !DocMethod {
// Convert parameters
doc_params := process_parameters(method.params)!
example_request := generate_request_example(doc_params)!
// Convert result
doc_result := process_result(method.result)!
// Example is always generated by extract_example_from_schema
example_response := doc_result.example
endpoint_url := '${config.base_url}/api/${config.handler_type}'
curl_example := generate_curl_example(method.name, example_request, endpoint_url)
doc_method := DocMethod{
name: method.name
summary: method.summary
description: method.description
params: doc_params
result: doc_result
example_response: example_response
example_request: example_request
endpoint_url: endpoint_url
curl_example: curl_example
}
return doc_method
}
// process_parameters converts OpenRPC parameters to DocParam array
fn process_parameters(params []openrpc.ContentDescriptorRef) ![]DocParam {
mut doc_params := []DocParam{}
for param in params {
if param is openrpc.ContentDescriptor {
type_info := extract_type_from_schema(param.schema)
example := extract_example_from_schema(param.schema)
doc_params << DocParam{
name: param.name
description: param.description
type_info: type_info
required: param.required
example: example
}
}
}
return doc_params
}
// process_result converts OpenRPC result to DocParam
fn process_result(result openrpc.ContentDescriptorRef) !DocParam {
mut doc_result := DocParam{}
if result is openrpc.ContentDescriptor {
type_info := extract_type_from_schema(result.schema)
example := extract_example_from_schema(result.schema)
doc_result = DocParam{
name: result.name
description: result.description
type_info: type_info
// required: false // Results are never required
example: example
}
}
return doc_result
}
// create_auth_info_with_config creates authentication documentation based on configuration
fn create_auth_info_with_config(enabled bool) AuthDocInfo {
if !enabled {
return AuthDocInfo{
enabled: false
steps: []
}
}
return create_auth_info()
}
// extract_type_from_schema extracts the JSON Schema type from a SchemaRef.
// Returns detailed type string (e.g., 'string', 'array[integer]', 'object') for better example generation.
fn extract_type_from_schema(schema_ref jsonschema.SchemaRef) string {
schema := match schema_ref {
jsonschema.Schema {
schema_ref
}
jsonschema.Reference {
return 'reference'
}
}
if schema.typ.len > 0 {
// For arrays, include the item type if available
if schema.typ == 'array' {
if items := schema.items {
// Handle single schema reference (most common case)
if items is jsonschema.SchemaRef {
item_type := extract_type_from_schema(items)
return 'array[${item_type}]'
}
// Handle array of schema references (tuple validation)
if items is []jsonschema.SchemaRef {
if items.len > 0 {
item_type := extract_type_from_schema(items[0])
return 'array[${item_type}]'
}
}
}
}
// For objects with additionalProperties, include the value type
if schema.typ == 'object' {
if additional := schema.additional_properties {
value_type := extract_type_from_schema(additional)
return 'object[${value_type}]'
}
}
return schema.typ
}
return 'unknown'
}
// Create authentication documentation info
fn create_auth_info() AuthDocInfo {
return AuthDocInfo{
enabled: true
steps: [
AuthStep{
number: 1
title: 'Register Public Key'
method: 'POST'
endpoint: '/auth/register'
description: 'Register your public key with the server'
example: '{\n "pubkey": "your_public_key_here"\n}'
},
AuthStep{
number: 2
title: 'Request Challenge'
method: 'POST'
endpoint: '/auth/authreq'
description: 'Request an authentication challenge'
example: '{\n "pubkey": "your_public_key_here"\n}'
},
AuthStep{
number: 3
title: 'Submit Signature'
method: 'POST'
endpoint: '/auth/auth'
description: 'Sign the challenge and submit for authentication'
example: '{\n "pubkey": "your_public_key_here",\n "signature": "signed_challenge"\n}'
},
AuthStep{
number: 4
title: 'Use Session Key'
method: 'ALL'
endpoint: '/api/{handler}/{method}'
description: 'Include session key in Authorization header for all API calls'
example: 'Authorization: Bearer {session_key}'
},
]
}
}