Skip to main content

hyperlane_cli/template/
fn.rs

1use super::*;
2
3/// Get directory name for template type
4///
5/// # Arguments
6///
7/// - `&TemplateType` - The template type
8///
9/// # Returns
10///
11/// - `String` - Directory name
12fn get_directory_name(template_type: &TemplateType) -> String {
13    match template_type {
14        TemplateType::Controller => TEMPLATE_TYPE_CONTROLLER.to_string(),
15        TemplateType::Domain => TEMPLATE_TYPE_DOMAIN.to_string(),
16        TemplateType::Exception => TEMPLATE_TYPE_EXCEPTION.to_string(),
17        TemplateType::Mapper => TEMPLATE_TYPE_MAPPER.to_string(),
18        TemplateType::Model => TEMPLATE_TYPE_MODEL.to_string(),
19        TemplateType::Repository => TEMPLATE_TYPE_REPOSITORY.to_string(),
20        TemplateType::Service => TEMPLATE_TYPE_SERVICE.to_string(),
21        TemplateType::Utils => TEMPLATE_TYPE_UTILS.to_string(),
22        TemplateType::View => TEMPLATE_TYPE_VIEW.to_string(),
23    }
24}
25
26/// Get model subtype directory name
27///
28/// # Arguments
29///
30/// - `&ModelSubType` - The model subtype
31///
32/// # Returns
33///
34/// - `String` - Directory name
35fn get_model_sub_type_name(sub_type: &ModelSubType) -> String {
36    match sub_type {
37        ModelSubType::Application => MODEL_SUB_TYPE_APPLICATION.to_string(),
38        ModelSubType::Request => MODEL_SUB_TYPE_REQUEST.to_string(),
39        ModelSubType::Response => MODEL_SUB_TYPE_RESPONSE.to_string(),
40    }
41}
42
43/// Create directory if it does not exist
44///
45/// # Arguments
46///
47/// - `&Path` - Path to the directory
48///
49/// # Returns
50///
51/// - `Result<(), TemplateError>` - Success or error
52async fn ensure_directory(path: &Path) -> Result<(), TemplateError> {
53    if !path.exists() {
54        create_dir_all(path).await?;
55    }
56    Ok(())
57}
58
59/// Write mod.rs content with module declarations
60///
61/// # Arguments
62///
63/// - `&Path` - Path to mod.rs file
64/// - `&[&str]` - List of modules to include
65///
66/// # Returns
67///
68/// - `Result<(), TemplateError>` - Success or error
69async fn write_mod_rs(path: &Path, modules: &[&str]) -> Result<(), TemplateError> {
70    let mut content: String = String::new();
71    for module in modules {
72        let mod_name: String = if module.starts_with("r#") {
73            module.to_string()
74        } else {
75            format!("r#{module}")
76        };
77        content.push_str(&format!("mod {mod_name};\n"));
78    }
79    content.push('\n');
80    let mut pub_use_parts: Vec<String> = Vec::new();
81    for module in modules {
82        let raw_name: &str = if let Some(stripped) = module.strip_prefix("r#") {
83            stripped
84        } else {
85            module
86        };
87        let mod_name: String = if module.starts_with("r#") {
88            module.to_string()
89        } else {
90            format!("r#{module}")
91        };
92        if raw_name == MODULE_NAME_CONST || raw_name == MODULE_NAME_STATIC {
93            pub_use_parts.push(mod_name);
94        } else if raw_name == MODULE_NAME_ENUM || raw_name == MODULE_NAME_FN {
95            pub_use_parts.push(format!("{mod_name}::*"));
96        } else if raw_name == MODULE_NAME_STRUCT {
97            pub_use_parts.push(mod_name);
98        }
99    }
100    if !pub_use_parts.is_empty() {
101        content.push_str(PUB_USE_PREFIX);
102        content.push_str(&pub_use_parts.join(", "));
103        content.push_str("};\n");
104    }
105    content.push('\n');
106    content.push_str(KEYWORD_FILE_HEADER);
107    write(path, content).await?;
108    Ok(())
109}
110
111/// Write empty mod.rs
112///
113/// # Arguments
114///
115/// - `&Path` - Path to mod.rs file
116///
117/// # Returns
118///
119/// - `Result<(), TemplateError>` - Success or error
120async fn write_empty_mod_rs(path: &Path) -> Result<(), TemplateError> {
121    write(path, "\n").await?;
122    Ok(())
123}
124
125/// Create controller template files
126///
127/// # Arguments
128///
129/// - `&Path` - Target directory path
130/// - `&str` - Name of the component
131///
132/// # Returns
133///
134/// - `Result<(), TemplateError>` - Success or error
135async fn create_controller_template(
136    target_dir: &Path,
137    _component_name: &str,
138) -> Result<(), TemplateError> {
139    ensure_directory(target_dir).await?;
140    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
141    write_mod_rs(
142        &mod_rs,
143        &[MODULE_NAME_FN, MODULE_NAME_IMPL, MODULE_NAME_STRUCT],
144    )
145    .await?;
146    let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS);
147    write(&fn_rs, KEYWORD_FILE_HEADER).await?;
148    let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS);
149    write(&impl_rs, KEYWORD_FILE_HEADER).await?;
150    let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS);
151    write(&struct_rs, KEYWORD_FILE_HEADER).await?;
152    Ok(())
153}
154
155/// Create view template files
156///
157/// # Arguments
158///
159/// - `&Path` - Target directory path
160/// - `&str` - Name of the component
161///
162/// # Returns
163///
164/// - `Result<(), TemplateError>` - Success or error
165async fn create_view_template(
166    target_dir: &Path,
167    _component_name: &str,
168) -> Result<(), TemplateError> {
169    ensure_directory(target_dir).await?;
170    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
171    write_mod_rs(
172        &mod_rs,
173        &[MODULE_NAME_FN, MODULE_NAME_IMPL, MODULE_NAME_STRUCT],
174    )
175    .await?;
176    let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS);
177    write(&fn_rs, KEYWORD_FILE_HEADER).await?;
178    let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS);
179    write(&impl_rs, KEYWORD_FILE_HEADER).await?;
180    let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS);
181    write(&struct_rs, KEYWORD_FILE_HEADER).await?;
182    Ok(())
183}
184
185/// Create service template files
186///
187/// # Arguments
188///
189/// - `&Path` - Target directory path
190/// - `&str` - Name of the component
191///
192/// # Returns
193///
194/// - `Result<(), TemplateError>` - Success or error
195async fn create_service_template(
196    target_dir: &Path,
197    _component_name: &str,
198) -> Result<(), TemplateError> {
199    ensure_directory(target_dir).await?;
200    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
201    write_mod_rs(&mod_rs, &[MODULE_NAME_IMPL, MODULE_NAME_STRUCT]).await?;
202    let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS);
203    write(&impl_rs, KEYWORD_FILE_HEADER).await?;
204    let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS);
205    write(&struct_rs, KEYWORD_FILE_HEADER).await?;
206    Ok(())
207}
208
209/// Create domain template files
210///
211/// # Arguments
212///
213/// - `&Path` - Target directory path
214/// - `&str` - Name of the component
215///
216/// # Returns
217///
218/// - `Result<(), TemplateError>` - Success or error
219async fn create_domain_template(
220    target_dir: &Path,
221    _component_name: &str,
222) -> Result<(), TemplateError> {
223    ensure_directory(target_dir).await?;
224    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
225    write_mod_rs(&mod_rs, &[MODULE_NAME_IMPL, MODULE_NAME_STRUCT]).await?;
226    let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS);
227    write(&impl_rs, KEYWORD_FILE_HEADER).await?;
228    let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS);
229    write(&struct_rs, KEYWORD_FILE_HEADER).await?;
230    Ok(())
231}
232
233/// Create mapper template files
234///
235/// # Arguments
236///
237/// - `&Path` - Target directory path
238/// - `&str` - Name of the component
239///
240/// # Returns
241///
242/// - `Result<(), TemplateError>` - Success or error
243async fn create_mapper_template(
244    target_dir: &Path,
245    _component_name: &str,
246) -> Result<(), TemplateError> {
247    ensure_directory(target_dir).await?;
248    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
249    write_mod_rs(
250        &mod_rs,
251        &[
252            MODULE_NAME_CONST,
253            MODULE_NAME_ENUM,
254            MODULE_NAME_FN,
255            MODULE_NAME_IMPL,
256            MODULE_NAME_STATIC,
257            MODULE_NAME_STRUCT,
258        ],
259    )
260    .await?;
261    let const_rs: PathBuf = target_dir.join(FILE_NAME_CONST_RS);
262    write(&const_rs, KEYWORD_FILE_HEADER).await?;
263    let enum_rs: PathBuf = target_dir.join(FILE_NAME_ENUM_RS);
264    write(&enum_rs, KEYWORD_FILE_HEADER).await?;
265    let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS);
266    write(&fn_rs, KEYWORD_FILE_HEADER).await?;
267    let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS);
268    write(&impl_rs, KEYWORD_FILE_HEADER).await?;
269    let static_rs: PathBuf = target_dir.join(FILE_NAME_STATIC_RS);
270    write(&static_rs, KEYWORD_FILE_HEADER).await?;
271    let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS);
272    write(&struct_rs, KEYWORD_FILE_HEADER).await?;
273    Ok(())
274}
275
276/// Create utils template files
277///
278/// # Arguments
279///
280/// - `&Path` - Target directory path
281/// - `&str` - Name of the component
282///
283/// # Returns
284///
285/// - `Result<(), TemplateError>` - Success or error
286async fn create_utils_template(
287    target_dir: &Path,
288    _component_name: &str,
289) -> Result<(), TemplateError> {
290    ensure_directory(target_dir).await?;
291    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
292    write_mod_rs(&mod_rs, &[MODULE_NAME_FN]).await?;
293    let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS);
294    write(&fn_rs, KEYWORD_FILE_HEADER).await?;
295    Ok(())
296}
297
298/// Create exception template files
299///
300/// # Arguments
301///
302/// - `&Path` - Target directory path
303/// - `&str` - Name of the component
304///
305/// # Returns
306///
307/// - `Result<(), TemplateError>` - Success or error
308async fn create_exception_template(
309    target_dir: &Path,
310    _component_name: &str,
311) -> Result<(), TemplateError> {
312    ensure_directory(target_dir).await?;
313    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
314    write_empty_mod_rs(&mod_rs).await?;
315    Ok(())
316}
317
318/// Create repository template files
319///
320/// # Arguments
321///
322/// - `&Path` - Target directory path
323/// - `&str` - Name of the component
324///
325/// # Returns
326///
327/// - `Result<(), TemplateError>` - Success or error
328async fn create_repository_template(
329    target_dir: &Path,
330    _component_name: &str,
331) -> Result<(), TemplateError> {
332    ensure_directory(target_dir).await?;
333    let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS);
334    write_mod_rs(&mod_rs, &[MODULE_NAME_IMPL, MODULE_NAME_STRUCT]).await?;
335    let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS);
336    write(&impl_rs, KEYWORD_FILE_HEADER).await?;
337    let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS);
338    write(&struct_rs, KEYWORD_FILE_HEADER).await?;
339    Ok(())
340}
341
342/// Create model template files
343///
344/// # Arguments
345///
346/// - `&Path` - Target directory path
347/// - `&str` - Name of the component
348/// - `&ModelSubType` - Model subtype
349///
350/// # Returns
351///
352/// - `Result<(), TemplateError>` - Success or error
353async fn create_model_template(
354    target_dir: &Path,
355    _component_name: &str,
356    sub_type: &ModelSubType,
357) -> Result<(), TemplateError> {
358    let sub_type_name: String = get_model_sub_type_name(sub_type);
359    let model_dir: PathBuf = target_dir.join(&sub_type_name);
360    ensure_directory(&model_dir).await?;
361    let mod_rs: PathBuf = model_dir.join(FILE_NAME_MOD_RS);
362    write_mod_rs(&mod_rs, &[MODULE_NAME_STRUCT]).await?;
363    let struct_rs: PathBuf = model_dir.join(FILE_NAME_STRUCT_RS);
364    write(&struct_rs, KEYWORD_FILE_HEADER).await?;
365    Ok(())
366}
367
368/// Execute template generation
369///
370/// # Arguments
371///
372/// - `TemplateType` - Type of template component
373/// - `&str` - Name of the component
374/// - `Option<ModelSubType>` - Optional model subtype for model components
375///
376/// # Returns
377///
378/// - `Result<(), TemplateError>` - Success or error
379pub async fn execute_template(
380    template_type: TemplateType,
381    component_name: &str,
382    model_sub_type: Option<ModelSubType>,
383) -> Result<(), TemplateError> {
384    let config: TemplateConfig =
385        TemplateConfig::new(template_type, component_name.to_string(), model_sub_type);
386    let base_path: PathBuf = PathBuf::from(&config.base_directory);
387    let dir_name: String = get_directory_name(&config.template_type);
388    let type_dir: PathBuf = base_path.join(&dir_name);
389    let target_dir: PathBuf = type_dir.join(&config.component_name);
390    if target_dir.exists() {
391        return Err(TemplateError::DirectoryExists(
392            target_dir.to_string_lossy().to_string(),
393        ));
394    }
395    ensure_directory(&type_dir).await?;
396    match config.template_type {
397        TemplateType::Controller => {
398            create_controller_template(&target_dir, &config.component_name).await?
399        }
400        TemplateType::View => create_view_template(&target_dir, &config.component_name).await?,
401        TemplateType::Service => {
402            create_service_template(&target_dir, &config.component_name).await?
403        }
404        TemplateType::Domain => create_domain_template(&target_dir, &config.component_name).await?,
405        TemplateType::Mapper => create_mapper_template(&target_dir, &config.component_name).await?,
406        TemplateType::Utils => create_utils_template(&target_dir, &config.component_name).await?,
407        TemplateType::Exception => {
408            create_exception_template(&target_dir, &config.component_name).await?
409        }
410        TemplateType::Repository => {
411            create_repository_template(&target_dir, &config.component_name).await?
412        }
413        TemplateType::Model => {
414            let sub_type: ModelSubType = config.model_sub_type.ok_or_else(|| {
415                TemplateError::InvalidModelSubType(ERROR_MISSING_MODEL_SUB_TYPE.to_string())
416            })?;
417            create_model_template(&target_dir, &config.component_name, &sub_type).await?;
418        }
419    }
420    let _: Result<(), io::Error> = format_generated_path(&target_dir).await;
421    log::info!(
422        "Created {dir_name} '{}' at {}",
423        config.component_name,
424        target_dir.display()
425    );
426    Ok(())
427}
428
429/// Run `cargo fmt` on freshly generated template code so the scaffolded
430/// files match the project formatting out of the box.
431///
432/// # Arguments
433///
434/// - `&Path` - Path to format
435///
436/// # Returns
437///
438/// - `Result<(), io::Error>` - Success or error
439async fn format_generated_path(path: &Path) -> Result<(), io::Error> {
440    let mut cmd: Command = Command::new(CARGO_FMT_PROGRAM);
441    cmd.arg("fmt");
442    cmd.arg("--");
443    cmd.arg(path);
444    cmd.stdout(Stdio::null()).stderr(Stdio::null());
445    cmd.status().await?;
446    Ok(())
447}