Skip to main content

forge_codegen/dioxus/
api.rs

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}