- Integrate heromodels RPC as a handler within heroserver - Update API endpoint to use standard JSON-RPC format - Add generated curl examples with copy button to docs - Improve error handling to return JSON-RPC errors - Simplify heromodels server example script
282 lines
8.1 KiB
V
282 lines
8.1 KiB
V
module heroserver
|
|
|
|
import freeflowuniverse.herolib.schemas.openrpc
|
|
import freeflowuniverse.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
|
|
}
|
|
|
|
// 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_call string
|
|
example_response string
|
|
endpoint_url string
|
|
curl_example string // New field for curl command
|
|
}
|
|
|
|
// 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:
|
|
steps []AuthStep
|
|
}
|
|
|
|
pub struct AuthStep {
|
|
pub mut:
|
|
number int
|
|
title string
|
|
method string
|
|
endpoint string
|
|
description string
|
|
example string
|
|
}
|
|
|
|
// 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 {
|
|
if handler_type.trim_space() == '' {
|
|
return error('handler_type cannot be empty')
|
|
}
|
|
mut doc_spec := DocSpec{
|
|
info: openrpc_spec.info
|
|
auth_info: create_auth_info()
|
|
}
|
|
|
|
for method in openrpc_spec.methods {
|
|
// Convert parameters
|
|
mut doc_params := []DocParam{}
|
|
for param in method.params {
|
|
if param is openrpc.ContentDescriptor {
|
|
type_info := extract_type_from_schema(param.schema)
|
|
example := generate_example_from_schema(param.schema, param.name)
|
|
|
|
doc_param := DocParam{
|
|
name: param.name
|
|
description: param.description
|
|
required: param.required
|
|
type_info: type_info
|
|
example: example
|
|
}
|
|
doc_params << doc_param
|
|
}
|
|
}
|
|
|
|
// Convert result
|
|
mut doc_result := DocParam{}
|
|
if method.result is openrpc.ContentDescriptor {
|
|
result_cd := method.result as openrpc.ContentDescriptor
|
|
type_info := extract_type_from_schema(result_cd.schema)
|
|
example := generate_example_from_schema(result_cd.schema, result_cd.name)
|
|
|
|
doc_result = DocParam{
|
|
name: result_cd.name
|
|
description: result_cd.description
|
|
required: result_cd.required
|
|
type_info: type_info
|
|
example: example
|
|
}
|
|
}
|
|
|
|
// Generate example call and response
|
|
example_call := generate_example_call(doc_params)
|
|
example_response := generate_example_response(doc_result)
|
|
|
|
// Generate JSON-RPC example call
|
|
jsonrpc_call := generate_jsonrpc_example_call(method.name, doc_params)
|
|
|
|
mut doc_method := DocMethod{
|
|
name: method.name
|
|
summary: method.summary
|
|
description: method.description
|
|
params: doc_params
|
|
result: doc_result
|
|
endpoint_url: '/api/${handler_type}'
|
|
example_call: example_call
|
|
example_response: example_response
|
|
curl_example: '' // Will be set later with proper base URL
|
|
}
|
|
|
|
// Generate curl example with localhost as default using JSON-RPC format
|
|
doc_method.curl_example = generate_curl_example_jsonrpc(method.name, doc_params,
|
|
'http://localhost:8080', handler_type)
|
|
doc_spec.methods << doc_method
|
|
}
|
|
|
|
return doc_spec
|
|
}
|
|
|
|
// extract_type_from_schema extracts the JSON Schema type from a SchemaRef.
|
|
// Returns the type string (e.g., 'string', 'object', 'array') or 'reference'/'unknown' for edge cases.
|
|
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 {
|
|
return schema.typ
|
|
}
|
|
return 'unknown'
|
|
}
|
|
|
|
// generate_example_from_schema creates an example value for a parameter or result.
|
|
// Uses the jsonschema.Schema.example_value() method with parameter name customization for strings.
|
|
// Returns properly formatted JSON values based on the schema type.
|
|
fn generate_example_from_schema(schema_ref jsonschema.SchemaRef, param_name string) string {
|
|
schema := match schema_ref {
|
|
jsonschema.Schema {
|
|
schema_ref
|
|
}
|
|
jsonschema.Reference {
|
|
return '"reference_value"'
|
|
}
|
|
}
|
|
|
|
// Use the improved example_value() method from jsonschema module
|
|
example := schema.example_value()
|
|
|
|
// For string types without explicit examples, customize with parameter name
|
|
if example == '"example_value"' && schema.typ == 'string' && param_name != '' {
|
|
return '"example_${param_name}"'
|
|
}
|
|
|
|
return example
|
|
}
|
|
|
|
// generate_example_call creates a formatted JSON example for method calls.
|
|
// Combines all parameter examples into a properly formatted JSON object.
|
|
fn generate_example_call(params []DocParam) string {
|
|
if params.len == 0 {
|
|
return '{}'
|
|
}
|
|
|
|
mut call_parts := []string{}
|
|
for param in params {
|
|
call_parts << '"${param.name}": ${param.example}'
|
|
}
|
|
|
|
return '{\n ${call_parts.join(',\n ')}\n}'
|
|
}
|
|
|
|
// generate_example_response creates a formatted JSON example for method responses.
|
|
// Wraps the result example in a standard {"result": ...} format.
|
|
fn generate_example_response(result DocParam) string {
|
|
if result.name == '' {
|
|
return '{"result": "success"}'
|
|
}
|
|
|
|
return '{"result": ${result.example}}'
|
|
}
|
|
|
|
// Create authentication documentation info
|
|
fn create_auth_info() AuthDocInfo {
|
|
return AuthDocInfo{
|
|
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}'
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
// generate_jsonrpc_example_call creates a complete JSON-RPC request example
|
|
fn generate_jsonrpc_example_call(method_name string, params []DocParam) string {
|
|
params_obj := if params.len == 0 {
|
|
'{}'
|
|
} else {
|
|
mut call_parts := []string{}
|
|
for param in params {
|
|
call_parts << '"${param.name}": ${param.example}'
|
|
}
|
|
'{\n ${call_parts.join(',\n ')}\n }'
|
|
}
|
|
|
|
return '{\n "jsonrpc": "2.0",\n "method": "${method_name}",\n "params": ${params_obj},\n "id": 1\n}'
|
|
}
|
|
|
|
// generate_curl_example_jsonrpc creates a curl command with proper JSON-RPC format
|
|
fn generate_curl_example_jsonrpc(method_name string, params []DocParam, base_url string, handler_name string) string {
|
|
endpoint := '${base_url}/api/${handler_name}'
|
|
jsonrpc_request := generate_jsonrpc_example_call(method_name, params)
|
|
|
|
mut curl_cmd := 'curl -X POST ${endpoint} \\\n'
|
|
curl_cmd += ' -H "Content-Type: application/json" \\\n'
|
|
curl_cmd += ' -d \'${jsonrpc_request}\''
|
|
|
|
return curl_cmd
|
|
}
|
|
|
|
// generate_curl_example creates a curl command for the given method (legacy)
|
|
pub fn generate_curl_example(method DocMethod, base_url string, handler_name string) string {
|
|
endpoint := '${base_url}/api/${handler_name}'
|
|
|
|
mut curl_cmd := 'curl -X POST ${endpoint} \\\n'
|
|
curl_cmd += ' -H "Content-Type: application/json" \\\n'
|
|
curl_cmd += ' -d \'${method.example_call}\''
|
|
|
|
return curl_cmd
|
|
}
|