- Add method grouping by model/actor prefix - Introduce DocMethodGroup struct for grouped methods - Refactor TOC to display methods by groups - Add collapsible sections for method groups and methods - Improve CSS for better presentation of grouped content
342 lines
9.6 KiB
V
342 lines
9.6 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
|
|
method_groups []DocMethodGroup // Grouped methods by model/actor prefix
|
|
auth_info AuthDocInfo
|
|
base_url string // Dynamic base URL for examples
|
|
}
|
|
|
|
// DocMethodGroup represents a group of methods for the same model/actor
|
|
pub struct DocMethodGroup {
|
|
pub mut:
|
|
name string // e.g., "calendar", "calendar_event", "user"
|
|
display_name string // e.g., "Calendar", "Calendar Event", "User"
|
|
methods []DocMethod
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Group methods by their model/actor prefix
|
|
doc_spec.method_groups = group_methods_by_prefix(doc_spec.methods)
|
|
|
|
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}'
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
// group_methods_by_prefix groups methods by their model/actor prefix (the part before the dot)
|
|
// For example: "calendar.get", "calendar.set" -> group "calendar"
|
|
// "calendar_event.get" -> group "calendar_event"
|
|
fn group_methods_by_prefix(methods []DocMethod) []DocMethodGroup {
|
|
mut groups_map := map[string][]DocMethod{}
|
|
|
|
// Group methods by prefix
|
|
for method in methods {
|
|
prefix := extract_method_prefix(method.name)
|
|
if prefix !in groups_map {
|
|
groups_map[prefix] = []DocMethod{}
|
|
}
|
|
groups_map[prefix] << method
|
|
}
|
|
|
|
// Convert map to array of DocMethodGroup
|
|
mut groups := []DocMethodGroup{}
|
|
for prefix, group_methods in groups_map {
|
|
groups << DocMethodGroup{
|
|
name: prefix
|
|
display_name: format_display_name(prefix)
|
|
methods: group_methods
|
|
}
|
|
}
|
|
|
|
// Sort groups alphabetically by name
|
|
groups.sort(a.name < b.name)
|
|
|
|
return groups
|
|
}
|
|
|
|
// extract_method_prefix extracts the model/actor prefix from a method name
|
|
// Examples: "calendar.get" -> "calendar"
|
|
// "calendar_event.set" -> "calendar_event"
|
|
// "user.delete" -> "user"
|
|
fn extract_method_prefix(method_name string) string {
|
|
parts := method_name.split('.')
|
|
if parts.len > 0 {
|
|
return parts[0]
|
|
}
|
|
return method_name
|
|
}
|
|
|
|
// format_display_name converts a prefix to a human-readable display name
|
|
// Examples: "calendar" -> "Calendar"
|
|
// "calendar_event" -> "Calendar Event"
|
|
// "chat_message" -> "Chat Message"
|
|
fn format_display_name(prefix string) string {
|
|
// Split by underscore and capitalize each word
|
|
words := prefix.split('_')
|
|
mut capitalized := []string{}
|
|
for word in words {
|
|
if word.len > 0 {
|
|
capitalized << word[0].ascii_str().to_upper() + word[1..]
|
|
}
|
|
}
|
|
return capitalized.join(' ')
|
|
}
|