Skip to main content

sails_client_gen_js/
lib.rs

1use 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}