1use forge_core::schema::{FunctionDef, FunctionKind, RustType, SchemaRegistry, TableDef};
2
3use crate::Error;
4
5#[derive(Default)]
6pub struct ApiGenerator;
7
8impl ApiGenerator {
9 pub fn new() -> Self {
10 Self
11 }
12
13 pub fn generate(&self, registry: &SchemaRegistry) -> Result<String, Error> {
14 let mut output = String::from(
15 "// Auto-generated by FORGE - DO NOT EDIT\n\n#![allow(dead_code, unused_imports)]\n\n",
16 );
17 output.push_str("use forge_dioxus::{\n");
18 output.push_str(" ForgeClient, ForgeClientError, JobExecutionState, QueryState,\n");
19 output.push_str(" SubscriptionState, WorkflowExecutionState,\n");
20 output.push_str("};\n");
21 output.push_str("use serde_json::json;\n\n");
22 output.push_str("use super::types::*;\n");
23 output.push_str("use super::{use_forge_job, use_forge_query, use_forge_subscription, use_forge_workflow};\n\n");
24
25 let dto_tables: Vec<TableDef> = registry.all_tables();
26 for function in registry.all_functions() {
27 output.push_str(&render_function(&function, &dto_tables));
28 output.push('\n');
29 }
30
31 Ok(output)
32 }
33}
34
35fn render_function(function: &FunctionDef, tables: &[TableDef]) -> String {
36 let fn_name = function.name.as_str();
37 let return_type = rust_type_to_rust(&function.return_type);
38 let wrapped_args = wrapped_args_expression(function, tables);
39 let params = function_params(function, tables);
40
41 match function.kind {
42 FunctionKind::Query => {
43 let hook_params = hook_params(function, tables);
44 let hook_args = hook_args_expression(function, tables);
45 format!(
46 "pub async fn {fn_name}(client: &ForgeClient{params}) -> Result<{return_type}, ForgeClientError> {{\n client.call(\"{fn_name}\", {wrapped_args}).await\n}}\n\npub fn use_{fn_name}({hook_params}) -> dioxus::prelude::Signal<QueryState<{return_type}>> {{\n use_forge_query(\"{fn_name}\", {hook_args})\n}}\n\npub fn use_{fn_name}_subscription({hook_params}) -> dioxus::prelude::Signal<SubscriptionState<{return_type}>> {{\n use_forge_subscription(\"{fn_name}\", {hook_args})\n}}"
47 )
48 }
49 FunctionKind::Mutation => format!(
50 "pub async fn {fn_name}(client: &ForgeClient{params}) -> Result<{return_type}, ForgeClientError> {{\n client.call(\"{fn_name}\", {wrapped_args}).await\n}}"
51 ),
52 FunctionKind::Job => {
53 let hook_params = hook_params(function, tables);
54 let hook_args = hook_args_expression(function, tables);
55 format!(
56 "pub fn use_track_{fn_name}({hook_params}) -> dioxus::prelude::Signal<JobExecutionState<{return_type}>> {{\n use_forge_job(\"{fn_name}\", {hook_args})\n}}"
57 )
58 }
59 FunctionKind::Workflow => {
60 let hook_params = hook_params(function, tables);
61 let hook_args = hook_args_expression(function, tables);
62 format!(
63 "pub fn use_track_{fn_name}({hook_params}) -> dioxus::prelude::Signal<WorkflowExecutionState<{return_type}>> {{\n use_forge_workflow(\"{fn_name}\", {hook_args})\n}}"
64 )
65 }
66 FunctionKind::Cron => String::new(),
67 }
68}
69
70fn function_params(function: &FunctionDef, tables: &[TableDef]) -> String {
71 match function.args.as_slice() {
72 [] => String::new(),
73 [arg] if is_custom_args_type(&arg.rust_type, tables) => {
74 format!(", {}: {}", arg.name, rust_type_to_rust(&arg.rust_type))
75 }
76 args => args
77 .iter()
78 .map(|arg| format!(", {}: {}", arg.name, rust_type_to_rust(&arg.rust_type)))
79 .collect::<String>(),
80 }
81}
82
83fn hook_params(function: &FunctionDef, tables: &[TableDef]) -> String {
84 match function.args.as_slice() {
85 [] => String::new(),
86 [arg] if is_custom_args_type(&arg.rust_type, tables) => {
87 format!("{}: {}", arg.name, rust_type_to_rust(&arg.rust_type))
88 }
89 args => args
90 .iter()
91 .map(|arg| format!("{}: {}", arg.name, rust_type_to_rust(&arg.rust_type)))
92 .collect::<Vec<_>>()
93 .join(", "),
94 }
95}
96
97fn wrapped_args_expression(function: &FunctionDef, tables: &[TableDef]) -> String {
98 match function.args.as_slice() {
99 [] => "()".to_string(),
100 [arg] if is_custom_args_type(&arg.rust_type, tables) => arg.name.clone(),
101 [arg] => format!("json!({{ \"{}\": {} }})", arg.name, arg.name),
102 args => {
103 let body = args
104 .iter()
105 .map(|arg| format!("\"{}\": {}", arg.name, arg.name))
106 .collect::<Vec<_>>()
107 .join(", ");
108 format!("json!({{ {body} }})")
109 }
110 }
111}
112
113fn hook_args_expression(function: &FunctionDef, tables: &[TableDef]) -> String {
114 wrapped_args_expression(function, tables)
115}
116
117fn is_custom_args_type(rust_type: &RustType, tables: &[TableDef]) -> bool {
118 match rust_type {
119 RustType::Custom(name) => {
120 (name.ends_with("Args") || name.contains("Args") || name.ends_with("Input"))
121 && tables.iter().any(|table| table.struct_name == *name)
122 }
123 _ => false,
124 }
125}
126
127fn rust_type_to_rust(rust_type: &RustType) -> String {
128 match rust_type {
129 RustType::String | RustType::Uuid => "String".to_string(),
130 RustType::I32 => "i32".to_string(),
131 RustType::I64 => "i64".to_string(),
132 RustType::F32 => "f32".to_string(),
133 RustType::F64 => "f64".to_string(),
134 RustType::Bool => "bool".to_string(),
135 RustType::DateTime
136 | RustType::Date
137 | RustType::Instant
138 | RustType::LocalDate
139 | RustType::LocalTime => "String".to_string(),
140 RustType::Upload => "forge_dioxus::ForgeUpload".to_string(),
141 RustType::Json => "serde_json::Value".to_string(),
142 RustType::Bytes => "Vec<u8>".to_string(),
143 RustType::Option(inner) => format!("Option<{}>", rust_type_to_rust(inner)),
144 RustType::Vec(inner) => format!("Vec<{}>", rust_type_to_rust(inner)),
145 RustType::Custom(name) => match name.as_str() {
146 "Uuid" | "uuid::Uuid" => "String".to_string(),
147 "DateTime<Utc>" | "NaiveDate" | "NaiveDateTime" | "Instant" | "LocalDate"
148 | "LocalTime" | "Timestamp" => "String".to_string(),
149 "Value" | "serde_json::Value" => "serde_json::Value".to_string(),
150 "Bytes" => "Vec<u8>".to_string(),
151 "Upload" => "forge_dioxus::ForgeUpload".to_string(),
152 other => other.to_string(),
153 },
154 }
155}