openapi_model_generator/
generator.rs

1use crate::{models::{Model, RequestModel, ResponseModel}, Result};
2
3const RUST_RESERVED_KEYWORDS: &[&str] = &[
4    "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
5    "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
6    "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe",
7    "use", "where", "while",
8
9    "abstract", "become", "box", "do", "final", "macro", "override", "priv", "try",
10    "typeof", "unsized", "virtual", "yield",
11];
12
13const EMPTY_RESPONSE_NAME: &str = "UnknownResponse";
14const EMPTY_REQUEST_NAME: &str = "UnknownRequest";
15
16fn is_reserved_word(string_to_check: &str) -> bool {
17    RUST_RESERVED_KEYWORDS.contains(&string_to_check)
18}
19
20pub fn generate_models(
21    models: &[Model],
22    requests: &[RequestModel],
23    responses: &[ResponseModel],
24) -> Result<String> {
25    let mut output = String::new();
26
27    output.push_str("use serde::{Serialize, Deserialize};\n");
28    output.push_str("use uuid::Uuid;\n");
29    output.push_str("use chrono::{DateTime, NaiveDate, Utc};\n\n");
30
31    for model in models {
32        output.push_str(&generate_model(model)?);
33        output.push('\n');
34    }
35
36    for request in requests {
37        output.push_str(&generate_request_model(request)?);
38        output.push('\n');
39    }
40
41    for response in responses {
42        output.push_str(&generate_response_model(response)?);
43        output.push('\n');
44    }
45
46    Ok(output)
47}
48
49fn generate_model(model: &Model) -> Result<String> {
50    let mut output = String::new();
51
52    if !model.name.is_empty() {
53        output.push_str(&format!("/// {}\n", model.name));
54    }
55    
56    output.push_str(&format!("#[derive(Debug, Serialize, Deserialize)]\n"));
57    output.push_str(&format!("pub struct {} {{\n", model.name));
58
59    for field in &model.fields {
60        let field_type = match field.field_type.as_str() {
61            "String" => "String",
62            "f64" => "f64",
63            "i64" => "i64",
64            "bool" => "bool",
65            "DateTime" => "DateTime<Utc>",
66            "Date" => "NaiveDate",
67            "Uuid" => "Uuid",
68            _ => &field.field_type,
69        };
70
71        let mut lowercased_name = field.name.to_lowercase();
72        if is_reserved_word(&lowercased_name) {
73            lowercased_name = format!("r#{}", lowercased_name)
74        }
75
76        output.push_str(&format!(
77            "    #[serde(rename = \"{}\")]\n",
78            field.name.to_lowercase()
79        ));
80
81        if field.is_required {
82            output.push_str(&format!(
83                "    pub {}: {},\n",
84                lowercased_name,
85                field_type
86            ));
87        } else {
88            output.push_str(&format!(
89                "    pub {}: Option<{}>,\n",
90                lowercased_name,
91                field_type
92            ));
93        }
94    }
95
96    output.push_str("}\n\n");
97    Ok(output)
98}
99
100fn generate_request_model(request: &RequestModel) -> Result<String> {
101    let mut output = String::new();
102    tracing::info!("Generating request model");
103    tracing::info!("{:#?}", request);
104
105    if request.name.is_empty() || request.name == EMPTY_REQUEST_NAME {
106       return Ok(String::new());
107    }
108
109    output.push_str(&format!("/// {}\n", request.name));
110    output.push_str(&"#[derive(Debug, Serialize)]\n".to_string());
111    output.push_str(&format!("pub struct {} {{\n", request.name));
112    output.push_str(&"    pub content_type: String,\n".to_string());
113    output.push_str(&format!("    pub body: {},\n", request.schema));
114    output.push_str("}\n");
115    Ok(output)
116}
117
118fn generate_response_model(response: &ResponseModel) -> Result<String> {
119    let mut output = String::new();
120    
121    // Return if name is empty
122    if response.name.is_empty() || response.name == EMPTY_RESPONSE_NAME {
123        return Ok(String::new());
124    }
125
126    output.push_str(&format!("/// {}\n", response.name));
127    output.push_str(&"#[derive(Debug, Deserialize)]\n".to_string());
128    output.push_str(&format!("pub struct {} {{\n", response.name));
129    output.push_str(&"    pub status_code: String,\n".to_string());
130    output.push_str(&"    pub content_type: String,\n".to_string());
131    output.push_str(&format!("    pub body: {},\n", response.schema));
132    if let Some(desc) = &response.description {
133        output.push_str(&format!("    /// {}\n", desc));
134    }
135    output.push_str("}\n");
136    Ok(output)
137}
138
139
140pub fn generate_rust_code(models: &[Model]) -> Result<String> {
141    let mut code = String::new();
142
143    code.push_str("use serde::{Serialize, Deserialize};\n");
144    code.push_str("use uuid::Uuid;\n");
145    code.push_str("use chrono::{DateTime, NaiveDate, Utc};\n\n");
146
147    for model in models {
148        code.push_str(&format!("/// {}\n", model.name));
149        code.push_str(&"#[derive(Debug, Serialize, Deserialize)]\n".to_string());
150        code.push_str(&format!("pub struct {} {{\n", model.name));
151
152        for field in &model.fields {
153            let field_type = match field.field_type.as_str() {
154                "String" => "String",
155                "f64" => "f64",
156                "i64" => "i64",
157                "bool" => "bool",
158                "DateTime" => "DateTime<Utc>",
159                "Date" => "NaiveDate",
160                "Uuid" => "Uuid",
161                _ => &field.field_type,
162            };
163
164            let mut lowercased_name = field.name.to_lowercase();
165            if is_reserved_word(&lowercased_name) {
166                lowercased_name = format!("r#{}", lowercased_name)
167            }
168
169            code.push_str(&format!(
170                "    #[serde(rename = \"{}\")]\n",
171                field.name.to_lowercase()
172            ));
173            
174            if field.is_required {
175                code.push_str(&format!(
176                    "    pub {}: {},\n",
177                    lowercased_name,
178                    field_type
179                ));
180            } else {
181                code.push_str(&format!(
182                    "    pub {}: Option<{}>,\n",
183                    lowercased_name,
184                    field_type
185                ));
186            }
187        }
188
189        code.push_str("}\n\n");
190    }
191
192    Ok(code)
193}
194
195pub fn generate_lib() -> Result<String> {
196    let mut code = String::new();
197    code.push_str("pub mod models;\n");
198
199    Ok(code)
200}