sails_client_gen_js/
lib.rs1use anyhow::{Context, Result, bail};
2use root_generator::RootGenerator;
3use sails_idl_parser_v2::{FsLoader, GitLoader, parse_idl, preprocess};
4use std::{fs, path::Path};
5use type_generator::TypeGenerator;
6
7mod helpers;
8mod naming;
9mod root_generator;
10mod service_generator;
11mod type_generator;
12
13#[derive(Default)]
14pub enum OutputLayout {
15 #[default]
16 SingleFile,
17 Split {
18 types_file: String,
19 client_file: String,
20 },
21}
22
23pub struct IdlPath<'a>(&'a Path);
24pub struct IdlString<'a>(&'a str);
25
26pub struct JsClientGenerator<S> {
27 idl: S,
28 output_layout: OutputLayout,
29}
30
31impl<S> JsClientGenerator<S> {
32 pub fn with_output_layout(self, output_layout: OutputLayout) -> Self {
33 Self {
34 output_layout,
35 ..self
36 }
37 }
38}
39
40impl<'a> JsClientGenerator<IdlPath<'a>> {
41 pub fn from_idl_path(idl_path: &'a Path) -> Self {
42 Self {
43 idl: IdlPath(idl_path),
44 output_layout: OutputLayout::SingleFile,
45 }
46 }
47
48 fn with_idl(self, idl: &'a str) -> JsClientGenerator<IdlString<'a>> {
49 JsClientGenerator {
50 idl: IdlString(idl),
51 output_layout: self.output_layout,
52 }
53 }
54
55 pub fn generate(self) -> Result<String> {
56 let idl_path = self.idl.0;
57 let path_str = idl_path.to_string_lossy();
58 let idl = preprocess::preprocess(&path_str, &[&FsLoader, &GitLoader])
59 .with_context(|| format!("Failed to preprocess IDL from {}", idl_path.display()))?;
60 self.with_idl(&idl).generate()
61 }
62
63 pub fn generate_to(self, out_path: impl AsRef<Path>) -> Result<()> {
64 let out_path = out_path.as_ref();
65 let code = self
66 .generate()
67 .context("failed to generate TypeScript client")?;
68 fs::write(out_path, code).with_context(|| {
69 format!("Failed to write generated client to {}", out_path.display())
70 })?;
71 Ok(())
72 }
73}
74
75impl<'a> JsClientGenerator<IdlString<'a>> {
76 pub fn from_idl(idl: &'a str) -> Self {
77 Self {
78 idl: IdlString(idl),
79 output_layout: OutputLayout::SingleFile,
80 }
81 }
82
83 pub fn generate(self) -> Result<String> {
84 let doc = parse_idl(self.idl.0).context("Failed to parse IDL")?;
85 let type_gen = TypeGenerator::new();
86 let mut generator = RootGenerator::new(&type_gen);
87 let output = generator.generate(&doc);
88
89 match self.output_layout {
90 OutputLayout::SingleFile => Ok(output),
91 OutputLayout::Split { .. } => {
92 bail!("Split output layout is not implemented yet in Phase 1")
93 }
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use sails_idl_parser_v2::{FsLoader, preprocess};
101
102 #[test]
103 fn test_js_generator_includes() {
104 let path = "tests/idls/includes/main.idl";
105 let result = preprocess::preprocess(path, &[&FsLoader]).expect("Failed to preprocess IDL");
106
107 assert!(result.contains("struct ResultData"));
108 assert!(result.contains("enum Error"));
109 assert!(result.contains("service ServiceA"));
110 assert!(result.contains("program Main"));
111 assert!(result.contains("alias MyResult = ResultData;"));
112
113 let count = result.matches("struct ResultData").count();
114 assert_eq!(count, 1, "ResultData should be included only once");
115 }
116}