1use std::{collections::HashMap, error::Error, fmt::Display, str::FromStr};
25
26use crate::{
27 attribute::{Attribute, DataType},
28 markdown::frontmatter::FrontMatter,
29 object::Object,
30 option::AttrOption,
31 prelude::DataModel,
32 tree,
33 xmltype::XMLType,
34};
35use clap::ValueEnum;
36use colored::Colorize;
37use convert_case::{Case, Casing};
38use lazy_static::lazy_static;
39use minijinja::{
40 context,
41 value::{Kwargs, ValueKind, ViaDeserialize},
42 Environment, Value,
43};
44use textwrap::wrap;
45
46#[cfg(feature = "python")]
47use pyo3::pyclass;
48
49#[cfg(feature = "wasm")]
50use wasm_bindgen::prelude::wasm_bindgen;
51
52lazy_static! {
53 static ref PYTHON_TYPE_MAPS: std::collections::HashMap<String, String> = {
55 let mut m = std::collections::HashMap::new();
56 m.insert("string".to_string(), "str".to_string());
57 m.insert("integer".to_string(), "int".to_string());
58 m.insert("boolean".to_string(), "bool".to_string());
59 m.insert("number".to_string(), "float".to_string());
60 m
61 };
62
63 static ref XSD_TYPE_MAPS: std::collections::HashMap<String, String> = {
65 let mut m = std::collections::HashMap::new();
66 m.insert("str".to_string(), "string".to_string());
67 m.insert("bytes".to_string(), "base64Binary".to_string());
68 m
69 };
70
71 static ref TYPESCRIPT_TYPE_MAPS: std::collections::HashMap<String, String> = {
73 let mut m = std::collections::HashMap::new();
74 m.insert("integer".to_string(), "number".to_string());
75 m.insert("float".to_string(), "number".to_string());
76 m.insert("date".to_string(), "string".to_string());
77 m.insert("bytes".to_string(), "string".to_string());
78 m
79 };
80
81 static ref GRAPHQL_TYPE_MAPS: std::collections::HashMap<String, String> = {
83 let mut m = std::collections::HashMap::new();
84 m.insert("integer".to_string(), "Int".to_string());
85 m.insert("number".to_string(), "Float".to_string());
86 m.insert("float".to_string(), "Float".to_string());
87 m.insert("boolean".to_string(), "Boolean".to_string());
88 m.insert("string".to_string(), "String".to_string());
89 m.insert("bytes".to_string(), "String".to_string());
90 m.insert("date".to_string(), "String".to_string());
91 m
92 };
93
94 static ref OWL_TYPE_MAPS: std::collections::HashMap<String, String> = {
96 let mut m = std::collections::HashMap::new();
97 m.insert("integer".to_string(), "xsd:integer".to_string());
98 m.insert("number".to_string(), "xsd:decimal".to_string());
99 m.insert("float".to_string(), "xsd:decimal".to_string());
100 m.insert("boolean".to_string(), "xsd:boolean".to_string());
101 m.insert("string".to_string(), "xsd:string".to_string());
102 m.insert("bytes".to_string(), "xsd:base64Binary".to_string());
103 m.insert("date".to_string(), "xsd:date".to_string());
104 m
105 };
106
107 static ref FORBIDDEN_RUST_ENUM_VARIANTS: Vec<String> = {
109 vec![
110 "yield".to_string(),
111 ]
112 };
113}
114
115#[derive(Debug, ValueEnum, Clone, PartialEq)]
117#[cfg_attr(feature = "python", pyclass(eq, eq_int, from_py_object))]
118#[cfg_attr(feature = "wasm", wasm_bindgen)]
119pub enum Templates {
120 XmlSchema,
122 Markdown,
124 CompactMarkdown,
126 JsonSchema,
128 JsonSchemaAll,
130 JsonLd,
132 Shacl,
134 Owl,
136 Shex,
138 PythonDataclass,
140 PythonPydanticXML,
142 PythonPydantic,
144 MkDocs,
146 Internal,
148 Typescript,
150 TypescriptZod,
152 Rust,
154 Protobuf,
156 Graphql,
158 Golang,
160 Linkml,
162 Julia,
164 Mermaid,
166}
167
168impl Display for Templates {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 match self {
171 Templates::PythonDataclass => write!(f, "python-dataclass"),
172 Templates::PythonPydantic => write!(f, "python-pydantic"),
173 Templates::PythonPydanticXML => write!(f, "python-pydantic-xml"),
174 Templates::XmlSchema => write!(f, "xml-schema"),
175 Templates::Markdown => write!(f, "markdown"),
176 Templates::CompactMarkdown => write!(f, "compact-markdown"),
177 Templates::Shacl => write!(f, "shacl"),
178 Templates::JsonSchema => write!(f, "json-schema"),
179 Templates::JsonSchemaAll => write!(f, "json-schema-all"),
180 Templates::JsonLd => write!(f, "json-ld"),
181 Templates::Shex => write!(f, "shex"),
182 Templates::MkDocs => write!(f, "mk-docs"),
183 Templates::Internal => write!(f, "internal"),
184 Templates::Typescript => write!(f, "typescript"),
185 Templates::TypescriptZod => write!(f, "typescript-zod"),
186 Templates::Rust => write!(f, "rust"),
187 Templates::Protobuf => write!(f, "protobuf"),
188 Templates::Graphql => write!(f, "graphql"),
189 Templates::Golang => write!(f, "golang"),
190 Templates::Linkml => write!(f, "linkml"),
191 Templates::Julia => write!(f, "julia"),
192 Templates::Mermaid => write!(f, "mermaid"),
193 Templates::Owl => write!(f, "owl"),
194 }
195 }
196}
197
198impl FromStr for Templates {
201 type Err = Box<dyn Error>;
202 fn from_str(s: &str) -> Result<Self, Box<dyn Error>> {
203 match s {
204 "python-dataclass" => Ok(Templates::PythonDataclass),
205 "python-sdrdm" => Ok(Templates::PythonPydanticXML),
206 "python-pydantic" => Ok(Templates::PythonPydantic),
207 "python-pydantic-xml" => Ok(Templates::PythonPydanticXML),
208 "xml-schema" => Ok(Templates::XmlSchema),
209 "markdown" => Ok(Templates::Markdown),
210 "compact-markdown" => Ok(Templates::CompactMarkdown),
211 "shacl" => Ok(Templates::Shacl),
212 "json-schema" => Ok(Templates::JsonSchema),
213 "json-schema-all" => Ok(Templates::JsonSchemaAll),
214 "shex" => Ok(Templates::Shex),
215 "mk-docs" => Ok(Templates::MkDocs),
216 "internal" => Ok(Templates::Internal),
217 "typescript" => Ok(Templates::Typescript),
218 "typescript-zod" => Ok(Templates::TypescriptZod),
219 "rust" => Ok(Templates::Rust),
220 "protobuf" => Ok(Templates::Protobuf),
221 "graphql" => Ok(Templates::Graphql),
222 "golang" => Ok(Templates::Golang),
223 "linkml" => Ok(Templates::Linkml),
224 "julia" => Ok(Templates::Julia),
225 "mermaid" => Ok(Templates::Mermaid),
226 "owl" => Ok(Templates::Owl),
227 _ => {
228 let err = format!("Invalid template type: {s}");
229 Err(err.into())
230 }
231 }
232 }
233}
234
235pub fn render_jinja_template(
246 template: &Templates,
247 model: &mut DataModel,
248 config: Option<&HashMap<String, String>>,
249) -> Result<String, minijinja::Error> {
250 let mut env = Environment::new();
252 minijinja_embed::load_templates!(&mut env);
253
254 let mut artificial_fields = HashMap::new();
259
260 let config = config.cloned().unwrap_or_default();
263
264 match template {
266 Templates::XmlSchema => convert_model_types(model, &XSD_TYPE_MAPS),
267 Templates::Typescript => convert_model_types(model, &TYPESCRIPT_TYPE_MAPS),
268 Templates::Graphql => convert_model_types(model, &GRAPHQL_TYPE_MAPS),
269 Templates::Shacl | Templates::Shex => {
270 convert_model_types(model, &OWL_TYPE_MAPS);
271 if let Err(e) = filter_objects_wo_terms(model) {
272 println!(
273 " [{}] {}",
274 template.to_string().yellow().bold(),
275 e.to_string().bold(),
276 );
277 }
278 }
279 Templates::Owl => {
280 convert_model_types(model, &OWL_TYPE_MAPS);
281 remove_default_prefixes(model);
282 }
283 Templates::PythonDataclass | Templates::PythonPydanticXML | Templates::PythonPydantic => {
284 convert_astropy_types(model, &config);
285 convert_model_types(model, &PYTHON_TYPE_MAPS);
286 sort_attributes_by_required(model);
287 }
288 Templates::Julia => {
289 sort_by_dependency(model);
290 }
291 Templates::Golang => {
292 if config.contains_key("gorm") {
293 add_id_pks(model, &mut artificial_fields);
294 }
295 }
296 Templates::Rust => {
297 check_for_forbidden_rust_enum_variants(model);
298 }
299 _ => {}
300 }
301
302 env.add_function("wrap", wrap_text);
304 env.add_function("replace", replace);
305 env.add_function("trim", trim);
306 env.add_function("default_value", default_value);
307 env.add_filter("enumerate", enumerate);
308 env.add_filter("cap_first", cap_first);
309 env.add_filter("split_path_pairs", split_path_pairs);
310 env.add_filter("pascal_case", pascal_case);
311 env.add_filter("camel_case", camel_case);
312 env.add_filter("snake_case", snake_case);
313 env.add_filter("replace_lower", replace_lower);
314
315 let template = match template {
317 Templates::PythonDataclass => env.get_template("python-dataclass.jinja")?,
318 Templates::PythonPydantic => env.get_template("python-pydantic.jinja")?,
319 Templates::XmlSchema => env.get_template("xml-schema.jinja")?,
320 Templates::Markdown => env.get_template("markdown.jinja")?,
321 Templates::CompactMarkdown => env.get_template("markdown-compact.jinja")?,
322 Templates::Shacl => env.get_template("shacl.jinja")?,
323 Templates::Shex => env.get_template("shex.jinja")?,
324 Templates::PythonPydanticXML => env.get_template("python-pydantic-xml.jinja")?,
325 Templates::MkDocs => env.get_template("mkdocs.jinja")?,
326 Templates::Typescript => env.get_template("typescript.jinja")?,
327 Templates::TypescriptZod => env.get_template("typescript-zod.jinja")?,
328 Templates::Rust => env.get_template("rust.jinja")?,
329 Templates::Protobuf => env.get_template("protobuf.jinja")?,
330 Templates::Graphql => env.get_template("graphql.jinja")?,
331 Templates::Golang => env.get_template("golang.jinja")?,
332 Templates::Julia => env.get_template("julia.jinja")?,
333 Templates::Mermaid => env.get_template("mermaid.jinja")?,
334 Templates::Owl => env.get_template("owl.jinja")?,
335 _ => {
336 panic!(
337 "The template is not available as a Jinja Template and should not be used using the jinja exporter.
338 Instead, use the dedicated exporter in the DataModel struct (e.g. `DataModel::json_ld_header`, DataModel::json_schema)."
339 )
340 }
341 };
342
343 if model.config.is_none() {
346 model.config = Some(FrontMatter::default());
347 }
348
349 let prefixes = get_prefixes(model);
351 let rendered = template.render(context! {
352 objects => model.objects,
353 object_names => model.objects.iter().map(|o| o.name.clone()).collect::<Vec<String>>(),
354 enums => model.enums,
355 enum_names => model.enums.iter().map(|e| e.name.clone()).collect::<Vec<String>>(),
356 title => model.name,
357 prefixes => prefixes,
358 repo => model.config.as_ref().unwrap().repo.clone(),
359 prefix => model.config.as_ref().unwrap().prefix.clone(),
360 nsmap => model.config.as_ref().unwrap().nsmap.clone(),
361 config => config,
362 objects_with_wrapped => get_objects_with_wrapped(model),
363 pk_objects => pk_objects(model),
364 artificial_fields => artificial_fields,
365 has_union_types => has_union_types(model),
366 });
367
368 match rendered {
369 Ok(r) => Ok(clean_and_trim(&r)),
370 Err(e) => Err(e),
371 }
372}
373
374fn get_objects_with_wrapped(model: &mut DataModel) -> Vec<String> {
384 model
385 .objects
386 .iter()
387 .filter(|o| {
388 o.attributes.iter().any(|a| {
389 if let Some(xml) = &a.xml {
390 matches!(xml, XMLType::Wrapped { .. })
391 } else {
392 false
393 }
394 })
395 })
396 .map(|o| o.name.clone())
397 .collect()
398}
399
400fn replace(value: String, from: &str, to: &str) -> String {
412 value.replace(from, to)
413}
414
415fn replace_lower(value: String, from: String, to: String) -> String {
427 value.replace(&from, &to).to_lowercase()
428}
429
430fn wrap_text(
442 text: &str,
443 width: usize,
444 initial_offset: &str,
445 offset: &str,
446 delimiter: Option<&str>,
447) -> String {
448 let delimiter = delimiter.unwrap_or("");
449 let options = textwrap::Options::new(width)
451 .initial_indent(initial_offset)
452 .subsequent_indent(offset)
453 .width(width)
454 .break_words(false);
455
456 wrap(remove_multiple_spaces(text).as_str(), options).join(&format!("{delimiter}\n"))
457}
458
459fn split_path_pairs(path: String, initial: Option<String>) -> Vec<Vec<String>> {
470 let initial = initial.unwrap_or_default();
471 let parts: Vec<&str> = path.split('/').collect();
472 let mut pairs = Vec::new();
473 let mut prev = initial;
474
475 for part in parts {
476 if !part.is_empty() {
477 pairs.push(vec![part.to_string(), prev.clone()]);
478 prev = part.to_string();
479 }
480 }
481
482 pairs
483}
484
485fn pascal_case(s: String) -> String {
488 if s.ends_with("_") {
489 s.to_case(Case::Pascal) + "_"
490 } else {
491 s.to_case(Case::Pascal)
492 }
493}
494
495fn camel_case(s: String) -> String {
498 s.to_case(Case::Camel)
499}
500
501fn snake_case(s: String) -> String {
504 s.to_case(Case::Snake)
505}
506
507fn remove_multiple_spaces(input: &str) -> String {
509 input.split_whitespace().collect::<Vec<&str>>().join(" ")
510}
511
512fn trim(input: &str, prefix: &str) -> String {
514 input
515 .trim_start_matches(prefix)
516 .trim_end_matches(prefix)
517 .to_string()
518}
519
520fn pk_objects(model: &mut DataModel) -> HashMap<String, (String, String, bool)> {
522 let mut pk_objects = HashMap::new();
523 for object in &mut model.objects {
524 for attribute in &object.attributes {
525 for option in &attribute.options {
526 if let AttrOption::PrimaryKey(true) = option {
527 pk_objects.insert(
528 object.name.clone(),
529 (attribute.name.clone(), attribute.dtypes[0].clone(), true),
530 );
531 break;
532 }
533 }
534 }
535 }
536 pk_objects
537}
538
539fn convert_model_types(
546 model: &mut DataModel,
547 type_map: &std::collections::HashMap<String, String>,
548) {
549 for object in &mut model.objects {
550 for attribute in &mut object.attributes {
551 attribute.dtypes = attribute
552 .dtypes
553 .iter()
554 .map(|t| type_map.get(t).unwrap_or(t))
555 .map(|t| t.to_string())
556 .collect();
557 }
558 }
559}
560
561fn add_id_pks(model: &mut DataModel, artificially_added_fields: &mut HashMap<String, String>) {
571 for object in &mut model.objects {
572 match (has_primary_key(object), has_id(object)) {
573 (false, false) => {
574 object.attributes.insert(0, id_attribute());
575 artificially_added_fields.insert(object.name.clone(), "id".to_string());
576 }
577 (false, true) => {
578 object
579 .attributes
580 .iter_mut()
581 .find(|a| a.name == "id")
582 .unwrap()
583 .options
584 .push(AttrOption::PrimaryKey(true));
585 }
586 _ => {}
587 }
588 }
589}
590
591fn id_attribute() -> Attribute {
604 let mut attr = Attribute::new("id".to_string(), true);
605 attr.options.push(AttrOption::PrimaryKey(true));
606 attr.set_dtype("integer".to_string()).unwrap();
607 attr
608}
609
610fn has_primary_key(object: &Object) -> bool {
620 object
621 .attributes
622 .iter()
623 .any(|a| a.options.iter().any(|o| o.key() == "primary key"))
624}
625
626fn has_id(object: &Object) -> bool {
636 object.attributes.iter().any(|a| a.name == "id")
637}
638
639fn convert_astropy_types(model: &mut DataModel, config: &HashMap<String, String>) {
645 if !config.contains_key("astropy") {
646 return;
647 }
648
649 for object in &mut model.objects {
651 for attribute in &mut object.attributes {
652 if attribute.dtypes.contains(&"UnitDefinition".to_string()) {
653 attribute.dtypes = vec!["UnitDefinitionAnnot".to_string()];
654 }
655 }
656 }
657
658 model
659 .objects
660 .retain(|o| o.name != "UnitDefinition" && o.name != "BaseUnit");
661
662 model.enums.retain(|e| e.name != "UnitType");
663}
664
665fn get_prefixes(model: &mut DataModel) -> Vec<(String, String)> {
675 let mut prefixes = match &model.config {
676 Some(config) => config.prefixes().unwrap_or(vec![]),
677 None => vec![],
678 };
679
680 prefixes.sort_by(|a, b| a.0.cmp(&b.0));
681 prefixes
682}
683
684fn filter_objects_wo_terms(model: &mut DataModel) -> Result<(), Box<dyn Error>> {
690 model.objects.retain(|o| o.has_any_terms());
691
692 if model.objects.is_empty() {
693 return Err(Box::new(std::io::Error::new(
694 std::io::ErrorKind::InvalidInput,
695 "No objects with terms found in the model. Unable to build SHACL or ShEx.",
696 )));
697 }
698 Ok(())
699}
700
701fn check_for_forbidden_rust_enum_variants(model: &mut DataModel) {
707 for enumeration in &mut model.enums {
708 enumeration.mappings = enumeration
709 .mappings
710 .iter()
711 .map(|(key, value)| {
712 let new_key = if FORBIDDEN_RUST_ENUM_VARIANTS.contains(&key.to_lowercase()) {
713 format!("{key}_")
714 } else {
715 key.to_lowercase()
716 };
717 (new_key, value.clone())
718 })
719 .collect();
720 }
721}
722
723fn sort_by_dependency(model: &mut DataModel) {
732 let graph = tree::dependency_graph(model);
733 let mut class_order = tree::get_topological_order(&graph);
734 class_order.reverse();
735 model
736 .objects
737 .sort_by_key(|o| class_order.iter().position(|c| c == &o.name).unwrap());
738}
739
740fn sort_attributes_by_required(model: &mut DataModel) {
746 for object in &mut model.objects {
747 object.sort_attrs_by_required();
748 }
749}
750
751fn clean_and_trim(s: &str) -> String {
767 let splitted = s.split('\n').collect::<Vec<&str>>();
768 let mut cleaned = vec![];
769 let mut consec_empty = 0;
770
771 for line in splitted {
772 let trimmed = line.trim_end();
773 if !trimmed.is_empty() {
774 cleaned.push(trimmed);
775 consec_empty = 0;
776 } else {
777 consec_empty += 1;
778 if consec_empty < 3 {
779 cleaned.push(trimmed);
780 }
781 }
782 }
783
784 cleaned.join("\n").trim().to_string()
785}
786
787pub fn enumerate(v: &Value, _: Kwargs) -> Result<Value, minijinja::Error> {
789 if v.kind() != ValueKind::Seq {
790 return Err(minijinja::Error::new(
791 minijinja::ErrorKind::InvalidOperation,
792 "Can only enumerate sequences",
793 ));
794 }
795
796 Ok(v.try_iter()
798 .expect("Failed to iterate over sequence")
799 .enumerate()
800 .map(|(i, v)| Value::from(vec![Value::from(i), v]))
801 .collect())
802}
803
804fn cap_first(s: String) -> String {
814 let mut chars = s.chars();
815 match chars.next() {
816 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
817 None => s.to_string(),
818 }
819}
820
821fn default_value(attribute: ViaDeserialize<Attribute>) -> String {
829 match &attribute.default {
830 Some(DataType::String(s)) => format!("\"{s}\""),
831 Some(DataType::Integer(i)) => {
832 if contains_numeric_type(&attribute) {
833 i.to_string()
834 } else {
835 format!("\"{i}\"")
836 }
837 }
838 Some(DataType::Float(f)) => {
839 if contains_numeric_type(&attribute) {
840 f.to_string()
841 } else {
842 format!("\"{f}\"")
843 }
844 }
845 _ => "".to_string(),
846 }
847}
848
849fn contains_numeric_type(attribute: &Attribute) -> bool {
859 attribute
860 .dtypes
861 .iter()
862 .any(|t| t == "integer" || t == "float")
863}
864
865fn has_union_types(model: &mut DataModel) -> bool {
875 model
876 .objects
877 .iter()
878 .any(|o| o.attributes.iter().any(|a| a.dtypes.len() > 1))
879}
880
881fn remove_default_prefixes(model: &mut DataModel) {
887 if let Some(prefixes) = &mut model.config.as_mut().unwrap().prefixes {
888 prefixes.remove("xsd");
889 prefixes.remove("rdfs");
890 prefixes.remove("owl");
891 }
892}
893
894#[cfg(test)]
895mod tests {
896 use pretty_assertions::assert_eq;
897 use std::fs;
898
899 use crate::markdown::parser::parse_markdown;
900
901 use super::*;
902
903 fn build_and_convert(
913 path: &str,
914 template: Templates,
915 config: Option<&HashMap<String, String>>,
916 ) -> String {
917 let content = fs::read_to_string(path).expect("Could not read markdown file");
918 let mut model = parse_markdown(&content, None).expect("Failed to parse markdown file");
919 render_jinja_template(&template, &mut model, config)
920 .expect("Could not render template")
921 .to_string()
922 }
923
924 #[test]
925 fn test_convert_to_shex() {
926 let rendered = build_and_convert("tests/data/model.md", Templates::Shex, None);
928
929 let expected = fs::read_to_string("tests/data/expected_shex.shex")
931 .expect("Could not read expected file");
932 assert_eq!(rendered, expected);
933 }
934
935 #[test]
936 fn test_convert_to_shacl() {
937 let rendered = build_and_convert("tests/data/model.md", Templates::Shacl, None);
939
940 let expected = fs::read_to_string("tests/data/expected_shacl.ttl")
942 .expect("Could not read expected file");
943 assert_eq!(rendered, expected);
944 }
945
946 #[test]
947 fn test_convert_to_owl() {
948 let rendered = build_and_convert("tests/data/model.md", Templates::Owl, None);
950
951 let expected = fs::read_to_string("tests/data/expected_owl.ttl")
953 .expect("Could not read expected file");
954 assert_eq!(rendered, expected);
955 }
956
957 #[test]
958 fn test_convert_to_python_dc() {
959 let rendered = build_and_convert("tests/data/model.md", Templates::PythonDataclass, None);
961
962 let expected = fs::read_to_string("tests/data/expected_python_dc.py")
964 .expect("Could not read expected file");
965 assert_eq!(rendered, expected);
966 }
967
968 #[test]
969 fn test_convert_to_python_pydantic_xml() {
970 let rendered = build_and_convert("tests/data/model.md", Templates::PythonPydanticXML, None);
972
973 let expected = fs::read_to_string("tests/data/expected_python_pydantic_xml.py")
975 .expect("Could not read expected file");
976 assert_eq!(rendered, expected);
977 }
978
979 #[test]
980 fn test_convert_to_xsd() {
981 let rendered = build_and_convert("tests/data/model.md", Templates::XmlSchema, None);
983
984 let expected = fs::read_to_string("tests/data/expected_xml_schema.xsd")
986 .expect("Could not read expected file");
987 assert_eq!(rendered, expected);
988 }
989
990 #[test]
991 fn test_convert_to_mkdocs() {
992 let rendered = build_and_convert("tests/data/model.md", Templates::MkDocs, None);
994
995 let expected = fs::read_to_string("tests/data/expected_mkdocs.md")
997 .expect("Could not read expected file");
998 assert_eq!(rendered, expected);
999 }
1000
1001 #[test]
1002 fn test_convert_to_typescript() {
1003 let rendered = build_and_convert("tests/data/model.md", Templates::Typescript, None);
1005
1006 let expected = fs::read_to_string("tests/data/expected_typescript.ts")
1008 .expect("Could not read expected file");
1009 assert_eq!(rendered, expected);
1010 }
1011
1012 #[test]
1013 fn test_convert_to_typescript_zod() {
1014 let rendered = build_and_convert("tests/data/model.md", Templates::TypescriptZod, None);
1016
1017 let expected = fs::read_to_string("tests/data/expected_typescript_zod.ts")
1019 .expect("Could not read expected file");
1020 assert_eq!(rendered, expected);
1021 }
1022
1023 #[test]
1024 fn test_convert_to_typescript_zod_json_ld() {
1025 let rendered = build_and_convert(
1027 "tests/data/model.md",
1028 Templates::TypescriptZod,
1029 Some(&HashMap::from([(
1030 "json-ld".to_string(),
1031 "true".to_string(),
1032 )])),
1033 );
1034
1035 let expected = fs::read_to_string("tests/data/expected_typescript_zod_json_ld.ts")
1037 .expect("Could not read expected file");
1038 assert_eq!(rendered, expected);
1039 }
1040
1041 #[test]
1042 fn test_convert_to_pydantic() {
1043 let rendered = build_and_convert("tests/data/model.md", Templates::PythonPydantic, None);
1045
1046 let expected = fs::read_to_string("tests/data/expected_pydantic.py")
1048 .expect("Could not read expected file");
1049 assert_eq!(rendered, expected);
1050 }
1051
1052 #[test]
1053 fn test_convert_to_pydantic_unitdef() {
1054 let rendered = build_and_convert(
1056 "tests/data/model_unitdef.md",
1057 Templates::PythonPydantic,
1058 Some(&HashMap::from([(
1059 "astropy".to_string(),
1060 "true".to_string(),
1061 )])),
1062 );
1063
1064 let expected = fs::read_to_string("tests/data/expected_pydantic_unitdef.py")
1066 .expect("Could not read expected file");
1067 assert_eq!(rendered, expected);
1068 }
1069
1070 #[test]
1071 fn test_convert_to_graphql() {
1072 let rendered = build_and_convert("tests/data/model.md", Templates::Graphql, None);
1074
1075 let expected = fs::read_to_string("tests/data/expected_graphql.graphql")
1077 .expect("Could not read expected file");
1078 assert_eq!(rendered, expected);
1079 }
1080
1081 #[test]
1082 fn test_convert_to_golang() {
1083 let rendered = build_and_convert("tests/data/model.md", Templates::Golang, None);
1085
1086 let expected = fs::read_to_string("tests/data/expected_golang.go")
1088 .expect("Could not read expected file");
1089 assert_eq!(rendered, expected);
1090 }
1091
1092 #[test]
1093 fn test_convert_to_golang_gorm() {
1094 let rendered = build_and_convert(
1096 "tests/data/model_golang_gorm.md",
1097 Templates::Golang,
1098 Some(&HashMap::from([("gorm".to_string(), "true".to_string())])),
1099 );
1100
1101 let expected = fs::read_to_string("tests/data/expected_golang_gorm.go")
1103 .expect("Could not read expected file");
1104 assert_eq!(rendered, expected);
1105 }
1106
1107 #[test]
1108 fn test_convert_to_rust() {
1109 let rendered = build_and_convert("tests/data/model.md", Templates::Rust, None);
1111
1112 let expected = fs::read_to_string("tests/data/expected_rust.rs")
1114 .expect("Could not read expected file");
1115 assert_eq!(rendered, expected);
1116 }
1117
1118 #[test]
1119 fn test_convert_to_rust_forbidden_names() {
1120 let rendered =
1122 build_and_convert("tests/data/model_forbidden_names.md", Templates::Rust, None);
1123
1124 let expected = fs::read_to_string("tests/data/expected_rust_forbidden.rs")
1126 .expect("Could not read expected file");
1127 assert_eq!(rendered, expected);
1128 }
1129
1130 #[test]
1131 fn test_convert_to_rust_ld() {
1132 let rendered = build_and_convert(
1134 "tests/data/model.md",
1135 Templates::Rust,
1136 Some(&HashMap::from([("jsonld".to_string(), "true".to_string())])),
1137 );
1138
1139 let expected = fs::read_to_string("tests/data/expected_rust_ld.rs")
1141 .expect("Could not read expected file");
1142 assert_eq!(rendered, expected);
1143 }
1144
1145 #[test]
1146 fn test_convert_to_protobuf() {
1147 let rendered = build_and_convert("tests/data/model.md", Templates::Protobuf, None);
1149
1150 let expected = fs::read_to_string("tests/data/expected_protobuf.proto")
1152 .expect("Could not read expected file");
1153 assert_eq!(rendered, expected);
1154 }
1155
1156 #[test]
1157 fn test_convert_to_mermaid() {
1158 let rendered = build_and_convert("tests/data/model.md", Templates::Mermaid, None);
1160
1161 let expected = fs::read_to_string("tests/data/expected_mermaid.md")
1163 .expect("Could not read expected file");
1164 assert_eq!(rendered, expected);
1165 }
1166}