This commit is contained in:
2025-09-25 05:52:02 +04:00
parent 62ccf42a4b
commit 7d49f552e4
32 changed files with 1062 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
import json
import os
def to_snake_case(name):
import re
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
def ts_type(v_type):
if v_type in ['string']:
return 'string'
if v_type in ['int', 'integer', 'u32', 'u64', 'i64', 'f32', 'f64']:
return 'number'
if v_type in ['bool', 'boolean']:
return 'boolean'
if v_type.startswith('[]'):
return ts_type(v_type[2:]) + '[]'
if v_type == 'array':
return 'any[]'
return v_type
def generate_interface(schema_name, schema):
content = f'export interface {schema_name} {{\n'
if 'properties' in schema:
for prop_name, prop_schema in schema.get('properties', {}).items():
prop_type = ts_type(prop_schema.get('type', 'any'))
if '$ref' in prop_schema:
prop_type = prop_schema['$ref'].split('/')[-1]
required = '?' if prop_name not in schema.get('required', []) else ''
content += f' {prop_name}{required}: {prop_type};\n'
if schema.get('allOf'):
for item in schema['allOf']:
if '$ref' in item:
ref_name = item['$ref'].split('/')[-1]
content += f' // Properties from {ref_name} are inherited\n'
content += '}\n'
return content
def generate_client(spec):
methods_str = ''
for method in spec['methods']:
params = []
if 'params' in method:
for param in method['params']:
param_type = 'any'
if 'schema' in param:
if '$ref' in param['schema']:
param_type = param['schema']['$ref'].split('/')[-1]
else:
param_type = ts_type(param['schema'].get('type', 'any'))
params.append(f"{param['name']}: {param_type}")
params_str = ', '.join(params)
result_type = 'any'
if 'result' in method and 'schema' in method['result']:
if '$ref' in method['result']['schema']:
result_type = method['result']['schema']['$ref'].split('/')[-1]
else:
result_type = ts_type(method['result']['schema'].get('type', 'any'))
method_name_snake = to_snake_case(method['name'])
methods_str += f"""
async {method_name_snake}(params: {{ {params_str} }}): Promise<{result_type}> {{
return this.send('{method['name']}', params);
}}
"""
schemas = spec.get('components', {}).get('schemas', {})
imports_str = '\n'.join([f"import {{ {name} }} from './{name}';" for name in schemas.keys()])
base_url = 'http://localhost:8086/api/heromodels'
client_class = f"""
import fetch from 'node-fetch';
{imports_str}
export class HeroModelsClient {{
private baseUrl: string;
constructor(baseUrl: string = '{base_url}') {{
this.baseUrl = baseUrl;
}}
private async send<T>(method: string, params: any): Promise<T> {{
const response = await fetch(this.baseUrl, {{
method: 'POST',
headers: {{
'Content-Type': 'application/json',
}},
body: JSON.stringify({{
jsonrpc: '2.0',
method: method,
params: params,
id: 1,
}}),
}});
if (!response.ok) {{
throw new Error(`HTTP error! status: ${{response.status}}`);
}}
const jsonResponse:any = await response.json();
if (jsonResponse.error) {{
throw new Error(`RPC error: ${{jsonResponse.error.message}}`);
}}
return jsonResponse.result;
}}
{methods_str}
}}
"""
return client_class
def main():
script_dir = os.path.dirname(__file__)
openrpc_path = os.path.abspath(os.path.join(script_dir, '..', '..', 'hero', 'heromodels', 'openrpc.json'))
output_dir = os.path.join(script_dir, 'generated_ts_client')
if not os.path.exists(output_dir):
os.makedirs(output_dir)
with open(openrpc_path, 'r') as f:
spec = json.load(f)
schemas = spec.get('components', {}).get('schemas', {})
for name, schema in schemas.items():
interface_content = generate_interface(name, schema)
with open(os.path.join(output_dir, f'{name}.ts'), 'w') as f:
f.write(interface_content)
client_content = generate_client(spec)
with open(os.path.join(output_dir, 'client.ts'), 'w') as f:
f.write(client_content)
print(f"TypeScript client generated successfully in {output_dir}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.hero.typescriptgenerator
import freeflowuniverse.herolib.schemas.openrpc
import os
const openrpc_path = os.dir(@FILE) + '/../../heromodels/openrpc.json'
const output_dir = os.dir(@FILE) + '/generated_ts_client'
fn main() {
spec_text := os.read_file(openrpc_path) or {
eprintln('Failed to read openrpc.json: ${err}')
return
}
openrpc_spec := openrpc.decode(spec_text) or {
eprintln('Failed to decode openrpc spec: ${err}')
return
}
config := typescriptgenerator.IntermediateConfig{
base_url: 'http://localhost:8086/api/heromodels'
}
intermediate_spec := typescriptgenerator.from_openrpc(openrpc_spec, config) or {
eprintln('Failed to create intermediate spec: ${err}')
return
}
typescriptgenerator.generate_typescript_client(intermediate_spec, output_dir) or {
eprintln('Failed to generate typescript client: ${err}')
return
}
println("TypeScript client generated successfully in ${output_dir}")
}

View File

@@ -0,0 +1,10 @@
export interface Base {
id?: number;
name?: string;
description?: string;
created_at?: number;
updated_at?: number;
securitypolicy?: number;
tags?: number;
messages?: any[];
}

View File

@@ -0,0 +1,7 @@
export interface Calendar {
events?: any[];
color?: string;
timezone?: string;
is_public?: boolean;
// Properties from Base are inherited
}

View File

@@ -0,0 +1,17 @@
export interface CalendarEvent {
title?: string;
start_time?: number;
end_time?: number;
location?: string;
attendees?: any[];
fs_items?: any[];
calendar_id?: number;
status?: string;
is_all_day?: boolean;
is_recurring?: boolean;
recurrence?: any[];
reminder_mins?: any[];
color?: string;
timezone?: string;
// Properties from Base are inherited
}

View File

@@ -0,0 +1,7 @@
export interface ChatGroup {
chat_type?: string;
last_activity?: number;
is_archived?: boolean;
group_id?: number;
// Properties from Base are inherited
}

View File

@@ -0,0 +1,12 @@
export interface ChatMessage {
content?: string;
chat_group_id?: number;
sender_id?: number;
parent_messages?: any[];
fs_files?: any[];
message_type?: string;
status?: string;
reactions?: any[];
mentions?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,12 @@
export interface Contact {
emails?: any[];
user_id?: number;
phones?: any[];
addresses?: any[];
avatar_url?: string;
bio?: string;
timezone?: string;
status?: string;
profile_ids?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,8 @@
export interface Education {
school?: string;
degree?: string;
field_of_study?: string;
start_date?: number;
end_date?: number;
description?: string;
}

View File

@@ -0,0 +1,9 @@
export interface Experience {
title?: string;
company?: string;
location?: string;
start_date?: number;
end_date?: number;
current?: boolean;
description?: string;
}

View File

@@ -0,0 +1,7 @@
export interface Group {
members?: any[];
subgroups?: any[];
parent_group?: number;
is_public?: boolean;
// Properties from Base are inherited
}

View File

@@ -0,0 +1,5 @@
export interface GroupMember {
user_id?: number;
role?: string;
joined_at?: number;
}

View File

@@ -0,0 +1,10 @@
export interface Message {
subject?: string;
message?: string;
parent?: number;
author?: number;
to?: any[];
cc?: any[];
send_log?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,4 @@
export interface MessageLink {
message_id?: number;
link_type?: string;
}

View File

@@ -0,0 +1,5 @@
export interface MessageReaction {
user_id?: number;
emoji?: string;
timestamp?: number;
}

View File

@@ -0,0 +1,7 @@
export interface Milestone {
name?: string;
description?: string;
due_date?: number;
completed?: boolean;
issues?: any[];
}

View File

@@ -0,0 +1,12 @@
export interface Planning {
color?: string;
timezone?: string;
is_public?: boolean;
calendar_template_id?: number;
registration_desk_id?: number;
autoschedule_rules?: any[];
invite_rules?: any[];
attendees_required?: any[];
attendees_optional?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,9 @@
export interface PlanningRecurrenceRule {
until?: number;
by_weekday?: any[];
by_monthday?: any[];
hour_from?: number;
hour_to?: number;
duration?: number;
priority?: number;
}

View File

@@ -0,0 +1,17 @@
export interface Profile {
user_id?: number;
summary?: string;
headline?: string;
location?: string;
industry?: string;
picture_url?: string;
background_image_url?: string;
email?: string;
phone?: string;
website?: string;
experience?: any[];
education?: any[];
skills?: any[];
languages?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,9 @@
export interface Project {
swimlanes?: any[];
milestones?: any[];
fs_files?: any[];
status?: string;
start_date?: number;
end_date?: number;
// Properties from Base are inherited
}

View File

@@ -0,0 +1,17 @@
export interface ProjectIssue {
title?: string;
project_id?: number;
issue_type?: string;
priority?: string;
status?: string;
swimlane?: string;
assignees?: any[];
reporter?: number;
milestone?: string;
deadline?: number;
estimate?: number;
fs_files?: any[];
parent_id?: number;
children?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,8 @@
export interface RecurrenceRule {
frequency?: string;
interval?: number;
until?: number;
count?: number;
by_weekday?: any[];
by_monthday?: any[];
}

View File

@@ -0,0 +1,7 @@
export interface Registration {
user_id?: number;
accepted?: boolean;
accepted_by?: number;
timestamp?: number;
timestamp_acceptation?: number;
}

View File

@@ -0,0 +1,12 @@
export interface RegistrationDesk {
fs_items?: any[];
white_list?: any[];
white_list_accepted?: any[];
required_list?: any[];
black_list?: any[];
start_time?: number;
end_time?: number;
acceptance_required?: boolean;
registrations?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,5 @@
export interface RegistrationFileAttachment {
fs_item?: number;
cat?: string;
public?: boolean;
}

View File

@@ -0,0 +1,6 @@
export interface SendLog {
to?: any[];
cc?: any[];
status?: string;
timestamp?: number;
}

View File

@@ -0,0 +1,7 @@
export interface Swimlane {
name?: string;
description?: string;
order?: number;
color?: string;
is_done?: boolean;
}

View File

@@ -0,0 +1,7 @@
export interface User {
user_id?: number;
contact_id?: number;
status?: string;
profile_ids?: any[];
// Properties from Base are inherited
}

View File

@@ -0,0 +1,324 @@
import fetch from 'node-fetch';
import { Base } from './Base';
import { Calendar } from './Calendar';
import { CalendarEvent } from './CalendarEvent';
import { RecurrenceRule } from './RecurrenceRule';
import { ChatGroup } from './ChatGroup';
import { ChatMessage } from './ChatMessage';
import { MessageLink } from './MessageLink';
import { MessageReaction } from './MessageReaction';
import { Contact } from './Contact';
import { Group } from './Group';
import { GroupMember } from './GroupMember';
import { Message } from './Message';
import { SendLog } from './SendLog';
import { Planning } from './Planning';
import { PlanningRecurrenceRule } from './PlanningRecurrenceRule';
import { Profile } from './Profile';
import { Experience } from './Experience';
import { Education } from './Education';
import { Project } from './Project';
import { Swimlane } from './Swimlane';
import { Milestone } from './Milestone';
import { ProjectIssue } from './ProjectIssue';
import { RegistrationDesk } from './RegistrationDesk';
import { RegistrationFileAttachment } from './RegistrationFileAttachment';
import { Registration } from './Registration';
import { User } from './User';
export class HeroModelsClient {
private baseUrl: string;
constructor(baseUrl: string = 'http://localhost:8086/api/heromodels') {
this.baseUrl = baseUrl;
}
private async send<T>(method: string, params: any): Promise<T> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params,
id: 1,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const jsonResponse:any = await response.json();
if (jsonResponse.error) {
throw new Error(`RPC error: ${jsonResponse.error.message}`);
}
return jsonResponse.result;
}
async calendar_get(params: { id: number }): Promise<Calendar> {
return this.send('calendar_get', params);
}
async calendar_set(params: { calendar: Calendar }): Promise<number> {
return this.send('calendar_set', params);
}
async calendar_delete(params: { id: number }): Promise<boolean> {
return this.send('calendar_delete', params);
}
async calendar_exist(params: { id: number }): Promise<boolean> {
return this.send('calendar_exist', params);
}
async calendar_list(params: { }): Promise<any[]> {
return this.send('calendar_list', params);
}
async calendar_event_get(params: { id: number }): Promise<CalendarEvent> {
return this.send('calendar_event_get', params);
}
async calendar_event_set(params: { calendar_event: CalendarEvent }): Promise<number> {
return this.send('calendar_event_set', params);
}
async calendar_event_delete(params: { id: number }): Promise<boolean> {
return this.send('calendar_event_delete', params);
}
async calendar_event_exist(params: { id: number }): Promise<boolean> {
return this.send('calendar_event_exist', params);
}
async calendar_event_list(params: { }): Promise<any[]> {
return this.send('calendar_event_list', params);
}
async chat_group_get(params: { id: number }): Promise<ChatGroup> {
return this.send('chat_group_get', params);
}
async chat_group_set(params: { chat_group: ChatGroup }): Promise<number> {
return this.send('chat_group_set', params);
}
async chat_group_delete(params: { id: number }): Promise<boolean> {
return this.send('chat_group_delete', params);
}
async chat_group_exist(params: { id: number }): Promise<boolean> {
return this.send('chat_group_exist', params);
}
async chat_group_list(params: { }): Promise<any[]> {
return this.send('chat_group_list', params);
}
async chat_message_get(params: { id: number }): Promise<ChatMessage> {
return this.send('chat_message_get', params);
}
async chat_message_set(params: { chat_message: ChatMessage }): Promise<number> {
return this.send('chat_message_set', params);
}
async chat_message_delete(params: { id: number }): Promise<boolean> {
return this.send('chat_message_delete', params);
}
async chat_message_exist(params: { id: number }): Promise<boolean> {
return this.send('chat_message_exist', params);
}
async chat_message_list(params: { }): Promise<any[]> {
return this.send('chat_message_list', params);
}
async contact_get(params: { id: number }): Promise<Contact> {
return this.send('contact_get', params);
}
async contact_set(params: { contact: Contact }): Promise<number> {
return this.send('contact_set', params);
}
async contact_delete(params: { id: number }): Promise<boolean> {
return this.send('contact_delete', params);
}
async contact_exist(params: { id: number }): Promise<boolean> {
return this.send('contact_exist', params);
}
async contact_list(params: { }): Promise<any[]> {
return this.send('contact_list', params);
}
async group_get(params: { id: number }): Promise<Group> {
return this.send('group_get', params);
}
async group_set(params: { group: Group }): Promise<number> {
return this.send('group_set', params);
}
async group_delete(params: { id: number }): Promise<boolean> {
return this.send('group_delete', params);
}
async group_exist(params: { id: number }): Promise<boolean> {
return this.send('group_exist', params);
}
async group_list(params: { }): Promise<any[]> {
return this.send('group_list', params);
}
async message_get(params: { id: number }): Promise<Message> {
return this.send('message_get', params);
}
async message_set(params: { message: Message }): Promise<number> {
return this.send('message_set', params);
}
async message_delete(params: { id: number }): Promise<boolean> {
return this.send('message_delete', params);
}
async message_exist(params: { id: number }): Promise<boolean> {
return this.send('message_exist', params);
}
async message_list(params: { }): Promise<any[]> {
return this.send('message_list', params);
}
async planning_get(params: { id: number }): Promise<Planning> {
return this.send('planning_get', params);
}
async planning_set(params: { planning: Planning }): Promise<number> {
return this.send('planning_set', params);
}
async planning_delete(params: { id: number }): Promise<boolean> {
return this.send('planning_delete', params);
}
async planning_exist(params: { id: number }): Promise<boolean> {
return this.send('planning_exist', params);
}
async planning_list(params: { }): Promise<any[]> {
return this.send('planning_list', params);
}
async profile_get(params: { id: number }): Promise<Profile> {
return this.send('profile_get', params);
}
async profile_set(params: { profile: Profile }): Promise<number> {
return this.send('profile_set', params);
}
async profile_delete(params: { id: number }): Promise<boolean> {
return this.send('profile_delete', params);
}
async profile_exist(params: { id: number }): Promise<boolean> {
return this.send('profile_exist', params);
}
async profile_list(params: { }): Promise<any[]> {
return this.send('profile_list', params);
}
async project_get(params: { id: number }): Promise<Project> {
return this.send('project_get', params);
}
async project_set(params: { project: Project }): Promise<number> {
return this.send('project_set', params);
}
async project_delete(params: { id: number }): Promise<boolean> {
return this.send('project_delete', params);
}
async project_exist(params: { id: number }): Promise<boolean> {
return this.send('project_exist', params);
}
async project_list(params: { }): Promise<any[]> {
return this.send('project_list', params);
}
async project_issue_get(params: { id: number }): Promise<ProjectIssue> {
return this.send('project_issue_get', params);
}
async project_issue_set(params: { project_issue: ProjectIssue }): Promise<number> {
return this.send('project_issue_set', params);
}
async project_issue_delete(params: { id: number }): Promise<boolean> {
return this.send('project_issue_delete', params);
}
async project_issue_exist(params: { id: number }): Promise<boolean> {
return this.send('project_issue_exist', params);
}
async project_issue_list(params: { }): Promise<any[]> {
return this.send('project_issue_list', params);
}
async registration_desk_get(params: { id: number }): Promise<RegistrationDesk> {
return this.send('registration_desk_get', params);
}
async registration_desk_set(params: { registration_desk: RegistrationDesk }): Promise<number> {
return this.send('registration_desk_set', params);
}
async registration_desk_delete(params: { id: number }): Promise<boolean> {
return this.send('registration_desk_delete', params);
}
async registration_desk_exist(params: { id: number }): Promise<boolean> {
return this.send('registration_desk_exist', params);
}
async registration_desk_list(params: { }): Promise<any[]> {
return this.send('registration_desk_list', params);
}
async user_get(params: { id: number }): Promise<User> {
return this.send('user_get', params);
}
async user_set(params: { user: User }): Promise<number> {
return this.send('user_set', params);
}
async user_delete(params: { id: number }): Promise<boolean> {
return this.send('user_delete', params);
}
async user_exist(params: { id: number }): Promise<boolean> {
return this.send('user_exist', params);
}
async user_list(params: { }): Promise<any[]> {
return this.send('user_list', params);
}
}

View File

@@ -0,0 +1,101 @@
module typescriptgenerator
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.texttools
pub fn generate_typescript_client(spec IntermediateSpec, dest_path string) ! {
mut dest := pathlib.get_dir(path: dest_path, create: true)!
// Generate a file for each schema
for name, schema in spec.schemas {
mut file_content := generate_interface(schema)
mut file_path := pathlib.get_file(path: '${dest.path}/${name}.ts', create: true)!
file_path.write(file_content) or { panic(err) }
}
// Generate the main client file
mut client_content := generate_client(spec)
mut client_file_path := pathlib.get_file(path: '${dest.path}/client.ts', create: true)!
client_file_path.write(client_content) or { panic(err) }
}
fn generate_interface(schema IntermediateSchema) string {
mut content := 'export interface ${schema.name} {\n'
for prop in schema.properties {
content += ' ${prop.name}${if prop.required { '' } else { '?' }}: ${ts_type(prop.type_info)};\n'
}
content += '}\n'
return content
}
fn generate_client(spec IntermediateSpec) string {
mut methods_str := ''
for method in spec.methods {
params_str := method.params.map('${it.name}: ${ts_type(it.type_info)}').join(', ')
methods_str += '
async ${texttools.snake_case(method.name)}(params: { ${params_str} }): Promise<${ts_type(method.result.type_info)}> {
return this.send(\'${method.name}\', params);
}
'
}
mut imports_str := ''
for schema_name in spec.schemas.keys() {
imports_str += "import { ${schema_name} } from './${schema_name}';\n"
}
return "
import fetch from 'node-fetch';
${imports_str}
export class HeroModelsClient {
private baseUrl: string;
constructor(baseUrl: string = '${spec.base_url}') {
this.baseUrl = baseUrl;
}
private async send<T>(method: string, params: any): Promise<T> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params,
id: 1,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${"$"}{response.status}`);
}
const jsonResponse:any = await response.json();
if (jsonResponse.error) {
throw new Error(`RPC error: ${"$"}{jsonResponse.error.message}`);
}
return jsonResponse.result;
}
${methods_str}
}
"
}
fn ts_type(v_type string) string {
return match v_type {
'string' { 'string' }
'int', 'integer', 'u32', 'u64', 'i64' { 'number' }
'bool', 'boolean' { 'boolean' }
'f32', 'f64' { 'number' }
'[]string' { 'string[]' }
'[]int' { 'number[]' }
'array' { 'any[]' }
else { v_type }
}
}

View File

@@ -0,0 +1,36 @@
module typescriptgenerator
import freeflowuniverse.herolib.schemas.openrpc
import os
const openrpc_path = os.dir(@FILE) + '/../../heromodels/openrpc.json'
const output_dir = os.dir(@FILE) + '/test_generated_ts_client'
fn test_generate_typescript_client() {
spec_text := os.read_file(openrpc_path) or {
eprintln('Failed to read openrpc.json: ${err}')
return
}
openrpc_spec := openrpc.decode(spec_text) or {
eprintln('Failed to decode openrpc spec: ${err}')
return
}
config := IntermediateConfig{
base_url: 'http://localhost:8086/api/heromodels'
}
intermediate_spec := from_openrpc(openrpc_spec, config) or {
eprintln('Failed to create intermediate spec: ${err}')
return
}
generate_typescript_client(intermediate_spec, output_dir) or {
eprintln('Failed to generate typescript client: ${err}')
return
}
println("TypeScript client generated successfully in ${output_dir}")
}

View File

@@ -0,0 +1,186 @@
module typescriptgenerator
import freeflowuniverse.herolib.schemas.openrpc
import freeflowuniverse.herolib.schemas.jsonschema
// IntermediateSpec is the main object passed to the typescript generator.
pub struct IntermediateSpec {
pub mut:
info openrpc.Info
methods []IntermediateMethod
schemas map[string]IntermediateSchema
base_url string
}
// IntermediateSchema represents a schema in a format that's easy to consume for generation.
pub struct IntermediateSchema {
pub mut:
name string
description string
properties []IntermediateProperty
}
// IntermediateMethod holds the information for a single method to be displayed.
pub struct IntermediateMethod {
pub mut:
name string
summary string
description string
params []IntermediateParam
result IntermediateParam
endpoint_url string
}
// IntermediateParam represents a parameter or result in the documentation
pub struct IntermediateParam {
pub mut:
name string
description string
type_info string
required bool
}
// IntermediateProperty represents a property of a schema
pub struct IntermediateProperty {
pub mut:
name string
description string
type_info string
required bool
}
// IntermediateConfig holds configuration for documentation generation
pub struct IntermediateConfig {
pub mut:
base_url string = 'http://localhost:8080'
handler_type string = 'heromodels'
}
pub fn from_openrpc(openrpc_spec openrpc.OpenRPC, config IntermediateConfig) !IntermediateSpec {
if config.handler_type.trim_space() == '' {
return error('handler_type cannot be empty')
}
mut intermediate_spec := IntermediateSpec{
info: openrpc_spec.info
base_url: config.base_url
schemas: process_schemas(openrpc_spec.components.schemas)!
}
// Process all methods
for method in openrpc_spec.methods {
intermediate_method := process_method(method, config)!
intermediate_spec.methods << intermediate_method
}
return intermediate_spec
}
fn process_method(method openrpc.Method, config IntermediateConfig) !IntermediateMethod {
// Convert parameters
intermediate_params := process_parameters(method.params)!
// Convert result
intermediate_result := process_result(method.result)!
intermediate_method := IntermediateMethod{
name: method.name
summary: method.summary
description: method.description
params: intermediate_params
result: intermediate_result
endpoint_url: '${config.base_url}/api/${config.handler_type}'
}
return intermediate_method
}
fn process_parameters(params []openrpc.ContentDescriptorRef) ![]IntermediateParam {
mut intermediate_params := []IntermediateParam{}
for param in params {
if param is openrpc.ContentDescriptor {
type_info := extract_type_from_schema(param.schema)
intermediate_params << IntermediateParam{
name: param.name
description: param.description
type_info: type_info
required: param.required
}
} else if param is jsonschema.Reference {
//TODO: handle reference
}
}
return intermediate_params
}
fn process_result(result openrpc.ContentDescriptorRef) !IntermediateParam {
mut intermediate_result := IntermediateParam{}
if result is openrpc.ContentDescriptor {
type_info := extract_type_from_schema(result.schema)
intermediate_result = IntermediateParam{
name: result.name
description: result.description
type_info: type_info
required: false // Results are never required
}
} else if result is jsonschema.Reference {
// handle reference
ref := result as jsonschema.Reference
type_info := ref.ref.all_after_last('/')
intermediate_result = IntermediateParam{
name: type_info.to_lower()
type_info: type_info
}
}
return intermediate_result
}
fn extract_type_from_schema(schema_ref jsonschema.SchemaRef) string {
schema := match schema_ref {
jsonschema.Schema {
schema_ref
}
jsonschema.Reference {
ref := schema_ref as jsonschema.Reference
return ref.ref.all_after_last('/')
}
}
if schema.typ.len > 0 {
return schema.typ
}
return 'unknown'
}
fn process_schemas(schemas map[string]jsonschema.SchemaRef) !map[string]IntermediateSchema {
mut intermediate_schemas := map[string]IntermediateSchema{}
for name, schema_ref in schemas {
if schema_ref is jsonschema.Schema {
schema := schema_ref as jsonschema.Schema
mut properties := []IntermediateProperty{}
for prop_name, prop_schema_ref in schema.properties {
prop_type := extract_type_from_schema(prop_schema_ref)
properties << IntermediateProperty {
name: prop_name
description: "" // TODO
type_info: prop_type
required: prop_name in schema.required
}
}
intermediate_schemas[name] = IntermediateSchema {
name: name
description: schema.description
properties: properties
}
}
}
return intermediate_schemas
}