Skip to main content

lmn_core/command/
configure_template.rs

1use crate::command::{Body, Command};
2use crate::config::error::ConfigError;
3use crate::execution::RunStats;
4use std::error::Error;
5use std::fs::File;
6use std::io::Write;
7use std::path::PathBuf;
8
9// ── Constant definition ───────────────────────────────────────────────────────
10
11const TEMPLATE_ROOT_DIR: &str = ".templates";
12
13// ── Kind definition ───────────────────────────────────────────────────────────
14
15pub enum TemplateKind {
16    Request,
17    Response,
18}
19
20impl TemplateKind {
21    fn dir(&self) -> &'static str {
22        match self {
23            TemplateKind::Request => "requests",
24            TemplateKind::Response => "responses",
25        }
26    }
27}
28
29// ── Command definition ────────────────────────────────────────────────────────
30
31pub struct ConfigureTemplateCommand {
32    pub alias: String,
33    pub body: Option<Body>,
34    pub template_path: Option<PathBuf>,
35    pub kind: TemplateKind,
36}
37
38impl Command for ConfigureTemplateCommand {
39    async fn execute(self) -> Result<Option<RunStats>, Box<dyn Error>> {
40        let content: String = match (self.body, self.template_path) {
41            (Some(body), _) => body.into(),
42            (_, Some(path)) => {
43                let raw = std::fs::read_to_string(&path).map_err(|_| {
44                    ConfigError::TemplateNotFound(path.to_string_lossy().into_owned())
45                })?;
46                serde_json::from_str::<serde_json::Value>(&raw)
47                    .map_err(|_| ConfigError::InvalidFormat(path.to_string_lossy().into_owned()))?;
48                raw
49            }
50            (None, None) => return Err(Box::new(ConfigError::GeneralError)),
51        };
52
53        let mut file_name = PathBuf::from(&self.alias);
54        file_name.set_extension("json");
55        let full_file_alias = file_name
56            .to_str()
57            .ok_or_else(|| Box::<dyn Error>::from(ConfigError::InvalidFormat(self.alias)))?;
58
59        create_file(self.kind.dir(), full_file_alias.to_string(), content)
60            .map_err(|e| Box::new(e) as Box<dyn Error>)?;
61        Ok(None)
62    }
63}
64
65fn create_file(sub_dir: &str, file_name: String, content: String) -> Result<(), ConfigError> {
66    let dir = PathBuf::from(TEMPLATE_ROOT_DIR).join(sub_dir);
67    std::fs::create_dir_all(&dir).map_err(|_| ConfigError::Fs(file_name.clone()))?;
68
69    let file_path = dir.join(&file_name);
70    if file_path.exists() {
71        return Err(ConfigError::TemplateAlreadyExists(file_name));
72    }
73
74    let mut file = File::create(&file_path).map_err(|_| ConfigError::Fs(file_name.clone()))?;
75    file.write_all(content.as_bytes())
76        .map_err(|_| ConfigError::Fs(file_name))
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn request_kind_dir() {
85        assert_eq!(TemplateKind::Request.dir(), "requests");
86    }
87
88    #[test]
89    fn response_kind_dir() {
90        assert_eq!(TemplateKind::Response.dir(), "responses");
91    }
92
93    #[test]
94    fn create_file_writes_content() {
95        let file_name = "__test_create_file_writes.json".to_string();
96        let path = PathBuf::from(TEMPLATE_ROOT_DIR)
97            .join("requests")
98            .join(&file_name);
99        let _ = std::fs::remove_file(&path);
100
101        let result = create_file("requests", file_name.clone(), r#"{"ok":true}"#.to_string());
102        assert!(result.is_ok());
103        assert!(path.exists());
104        let _ = std::fs::remove_file(&path);
105    }
106
107    #[test]
108    fn create_file_rejects_duplicate() {
109        let file_name = "__test_create_file_duplicate.json".to_string();
110        let path = PathBuf::from(TEMPLATE_ROOT_DIR)
111            .join("requests")
112            .join(&file_name);
113        let _ = std::fs::remove_file(&path);
114
115        create_file("requests", file_name.clone(), "{}".to_string()).unwrap();
116        let result = create_file("requests", file_name.clone(), "{}".to_string());
117        assert!(matches!(result, Err(ConfigError::TemplateAlreadyExists(_))));
118        let _ = std::fs::remove_file(&path);
119    }
120}