codegenr_lib/
render.rs

1use handlebars::Handlebars;
2use serde_json::Value;
3use std::collections::HashMap;
4use walkdir::WalkDir;
5
6const PARTIAL_TEMPLATE_PREFIX: &str = "_";
7const HANDLEBARS_TEMPLATE_EXTENSION: &str = ".hbs";
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct TemplateCollection {
11  main: Template,
12  partials: HashMap<String, Template>,
13}
14
15impl TemplateCollection {
16  pub fn from_list(templates: impl IntoIterator<Item = Template>) -> Result<TemplateCollection, anyhow::Error> {
17    let mut main: Option<Template> = None;
18    let mut partials = HashMap::<String, Template>::new();
19
20    for t in templates {
21      match t.template_type() {
22        TemplateType::Main => {
23          if let Some(existing) = main.as_ref() {
24            return Err(anyhow::anyhow!(
25              "2 main templates were found : \n-{}\n-{}\nTheir should be only one in all the template directories",
26              existing.file_path(),
27              t.file_path()
28            ));
29          };
30          main = Some(t);
31        }
32        TemplateType::Partial => {
33          if let Some(existing) = partials.get(t.template_name()) {
34            return Err(anyhow::anyhow!(
35              "2 partial templates are named `{}` : \n-{}\n-{}\nThey should have unique names",
36              existing.template_name(),
37              existing.file_path(),
38              t.file_path()
39            ));
40          };
41          partials.insert(t.template_name().into(), t);
42        }
43      }
44    }
45
46    let main = main.ok_or_else(|| anyhow::anyhow!("No main template has been detected, we don't know what to execute..."))?;
47
48    Ok(Self { main, partials })
49  }
50
51  pub fn render(&self, json: &Value, mut handlebars: Handlebars) -> Result<String, anyhow::Error> {
52    let template_name = self.main.template_name();
53    handlebars.register_template_file(template_name, self.main.file_path())?;
54    for (_, value) in self.partials.iter() {
55      handlebars.register_template_file(value.template_name(), value.file_path())?
56    }
57    Ok(handlebars.render(template_name, json)?)
58  }
59}
60
61#[derive(Debug, Copy, Clone, PartialEq)]
62pub enum TemplateType {
63  Main,
64  Partial,
65}
66
67#[derive(Debug, Clone, PartialEq)]
68pub struct Template {
69  template_type: TemplateType,
70  file_name: String,
71  file_path: String,
72}
73
74impl Template {
75  pub fn new(template_type: TemplateType, file_name: impl Into<String>, file_path: impl Into<String>) -> Self {
76    Self {
77      template_type,
78      file_name: file_name.into(),
79      file_path: file_path.into(),
80    }
81  }
82
83  pub fn file_path(&self) -> &str {
84    &self.file_path
85  }
86
87  pub fn template_name(&self) -> &str {
88    self.file_name.trim_start_matches('_').trim_end_matches(".hbs")
89  }
90
91  pub fn template_type(&self) -> TemplateType {
92    self.template_type
93  }
94}
95
96pub fn get_templates_from_directory(dir_path: &str) -> Result<Vec<Template>, anyhow::Error> {
97  let mut result = vec![];
98  for entry in WalkDir::new(dir_path) {
99    let entry = entry?;
100
101    let file_path = entry.path().to_str();
102    let file_path = match file_path {
103      Some(f) => f,
104      None => continue,
105    };
106
107    let file_name = entry.file_name().to_str();
108    if let Some(file_name) = file_name {
109      if !file_name.ends_with(HANDLEBARS_TEMPLATE_EXTENSION) {
110        continue;
111      }
112      let t = if file_name.starts_with(PARTIAL_TEMPLATE_PREFIX) {
113        TemplateType::Partial
114      } else {
115        TemplateType::Main
116      };
117      result.push(Template::new(t, file_name, file_path));
118    }
119  }
120  Ok(result)
121}
122
123#[cfg(test)]
124mod test {
125  use super::*;
126  use crate::helpers::handlebars_setup;
127  use crate::loader::DocumentPath;
128  use crate::resolver::resolve_refs;
129
130  #[test]
131  fn handlebars_loading_test() -> Result<(), anyhow::Error> {
132    let document = DocumentPath::parse("_samples/resolver/petshop_with_external.yaml")?;
133    let json = resolve_refs(document)?;
134    let list = get_templates_from_directory("_samples/render/test_denis")?;
135    let collection = TemplateCollection::from_list(list)?;
136
137    let mut h = Handlebars::new();
138    handlebars_setup(&mut h, Default::default());
139    let result = collection.render(&json, h)?;
140    dbg!(result);
141
142    Ok(())
143  }
144
145  #[test]
146  fn get_templates_from_directory_test() -> Result<(), anyhow::Error> {
147    let mut templates = get_templates_from_directory("./_samples/render/templates")?;
148    templates.sort_by_key(|t| t.file_path.clone());
149    let expected = vec![
150      Template::new(
151        TemplateType::Partial,
152        "_other_partial.hbs",
153        "./_samples/render/templates/_other_partial.hbs",
154      ),
155      Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
156      Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub/_plop.hbs"),
157      Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
158      Template::new(TemplateType::Main, "test.hbs", "./_samples/render/templates/test.hbs"),
159    ];
160    // dbg!(&templates, &expected);
161    assert_eq!(templates, expected);
162
163    let first = templates.get(0).expect("?");
164    assert_eq!(first.template_name(), "other_partial");
165    Ok(())
166  }
167
168  #[test]
169  fn from_list_fails_with_double_main() {
170    let list = vec![
171      Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
172      Template::new(TemplateType::Main, "test.hbs", "./_samples/render/templates/test.hbs"),
173    ];
174
175    let test = TemplateCollection::from_list(list);
176    let err = test.expect_err("Should be an error");
177    assert!(err.to_string().starts_with("2 main templates were found"));
178  }
179
180  #[test]
181  fn from_list_fails_with_same_names_partials() {
182    let list = vec![
183      Template::new(
184        TemplateType::Partial,
185        "_other_partial.hbs",
186        "./_samples/render/templates/_other_partial.hbs",
187      ),
188      Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
189      Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub/_plop.hbs"),
190      Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub2/_plop.hbs"),
191    ];
192
193    let test = TemplateCollection::from_list(list);
194    let err = test.expect_err("Should be an error");
195    assert!(err.to_string().starts_with("2 partial templates are named `plop`"));
196  }
197
198  #[test]
199  fn from_list_fails_with_no_main_found() {
200    let list = vec![
201      Template::new(
202        TemplateType::Partial,
203        "_other_partial.hbs",
204        "./_samples/render/templates/_other_partial.hbs",
205      ),
206      Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
207      Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub/_plop.hbs"),
208    ];
209
210    let test = TemplateCollection::from_list(list);
211    let err = test.expect_err("Should be an error");
212    assert!(err
213      .to_string()
214      .starts_with("No main template has been detected, we don't know what to execute..."));
215  }
216
217  #[test]
218  fn from_list_success() {
219    let list = vec![
220      Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
221      Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
222    ];
223    let test = TemplateCollection::from_list(list).expect("?");
224    let mut map = HashMap::new();
225    map.insert(
226      "partial".into(),
227      Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
228    );
229    let expected = TemplateCollection {
230      main: Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
231      partials: map,
232    };
233
234    assert_eq!(test, expected);
235  }
236}