mandolin/
lib.rs

1mod filter;
2mod function;
3pub mod templates;
4
5use std::sync::Mutex;
6
7use openapiv3::OpenAPI;
8
9pub type JpUnit = (String, minijinja::Value);
10pub type JpList = Vec<JpUnit>;
11
12pub fn environment(
13	mut value: OpenAPI,
14) -> Result<minijinja::Environment<'static>, minijinja::Error> {
15	// Add default api if no api defined
16	if value.servers.is_empty() {
17		value.servers.push(openapiv3::Server {
18			url: "/api".to_string(),
19			description: Some("Default server added by mandolin".to_string()),
20			..Default::default()
21		});
22	}
23	// Add operators
24	let value = minijinja::Value::from_serialize(&value);
25	let value_jp = function::jp_list(&value, "#");
26	let mut env = minijinja::Environment::new();
27	for [k, v] in templates::TEMPLATES {
28		env.add_template(k, v)?
29	}
30	{
31		let ls = value_jp.clone();
32		env.add_filter("include_ref", move |value: minijinja::Value| {
33			filter::include_ref(&ls, value)
34		});
35		let ls = value_jp.clone();
36		env.add_filter("include_pointer", move |value: &str| {
37			filter::include_pointer(&ls, value)
38		});
39	}
40	env.add_filter("decode", filter::decode);
41	env.add_filter("encode", filter::encode);
42	env.add_filter("split", filter::split);
43	env.add_filter("re_replace", filter::re_replace);
44	env.add_filter("to_pascal_case", filter::to_pascal_case);
45	env.add_filter("to_snake_case", filter::to_snake_case);
46	env.add_filter("to_camel_case", filter::to_camel_case);
47	{
48		let ls = value_jp.clone();
49		env.add_function("ls", move |value: &str| {
50			function::ls(&ls, function::LsMode::LS((value, false)))
51		});
52		let ls = value_jp.clone();
53		env.add_function("ls_recursive", move |value: &str| {
54			function::ls(&ls, function::LsMode::LS((value, true)))
55		});
56		let ls = value_jp.clone();
57		env.add_function("ls_operation", move || {
58			function::ls(&ls, function::LsMode::OPERATION)
59		});
60		let ls = value_jp.clone();
61		env.add_function("ls_schema", move || {
62			function::ls(&ls, function::LsMode::SCHEMA)
63		});
64	}
65	let queue = std::sync::Arc::new(Mutex::new(function::NestedSchema::default()));
66	{
67		let q = std::sync::Arc::clone(&queue);
68		env.add_function("schema_drain", move || {
69			function::schema_drain(&mut q.lock().unwrap())
70		});
71		let q = std::sync::Arc::clone(&queue);
72		env.add_function(
73			"schema_push",
74			move |pointer: &str, content: Option<&str>| {
75				function::schema_push(&mut q.lock().unwrap(), pointer, content)
76			},
77		);
78	}
79	Ok(env)
80}
81#[cfg(test)]
82mod tests {
83	use super::*;
84	use std::collections::HashMap;
85	use std::fs;
86	use std::fs::File;
87	use std::io::Write;
88	use std::path::Path;
89
90	fn api_map() -> HashMap<String, OpenAPI> {
91		fs::read_dir(&Path::new(".").join("openapi"))
92			.unwrap()
93			.filter_map(Result::ok)
94			.filter_map(|entry| {
95				let path = entry.path();
96				let extension = path.extension()?.to_str()?;
97				let name = entry.file_name().to_str()?.to_string();
98
99				match extension {
100					"yaml" | "yml" => {
101						let file = File::open(&path).ok()?;
102						let reader = std::io::BufReader::new(file);
103						let openapi = serde_yaml::from_reader(reader).ok()?;
104						Some((name, openapi))
105					}
106					"json" => {
107						let file = File::open(&path).ok()?;
108						let reader = std::io::BufReader::new(file);
109						let openapi = serde_json::from_reader(reader).ok()?;
110						Some((name, openapi))
111					}
112					_ => None,
113				}
114			})
115			.collect()
116	}
117	fn write<P: AsRef<Path>, S: AsRef<str>>(path: P, content: S) -> std::io::Result<()> {
118		let path = path.as_ref();
119		// 親ディレクトリがある場合は作成する
120		if let Some(parent) = path.parent() {
121			fs::create_dir_all(parent)?;
122		}
123		let mut writer = std::io::BufWriter::new(File::create(path)?);
124		writeln!(writer, "{}", content.as_ref())
125	}
126	fn render_target(template: &str, extention: &str) {
127		for (k, input_api) in api_map() {
128			println!("render start: {k}");
129			let env = environment(input_api).unwrap();
130			let template = env.get_template(template).unwrap();
131			let output = template.render(0).unwrap();
132			write(format!("out/{k}.{extention}"), output.as_str()).unwrap();
133			println!("render complete: {k}");
134		}
135	}
136	#[test]
137	fn render() {
138		render_target("TYPESCRIPT_HONO", "ts");
139		render_target("RUST_AXUM", "rs");
140		render_target("GO_SERVER", "go");
141	}
142}