sails-client-gen-js 1.0.0-beta.5

TypeScript client generator for the Sails framework
Documentation
use anyhow::{Context, Result, bail};
use root_generator::RootGenerator;
use sails_idl_parser_v2::{FsLoader, GitLoader, parse_idl, preprocess};
use std::{fs, path::Path};
use type_generator::TypeGenerator;

mod helpers;
mod naming;
mod root_generator;
mod service_generator;
mod type_generator;

#[derive(Default)]
pub enum OutputLayout {
    #[default]
    SingleFile,
    Split {
        types_file: String,
        client_file: String,
    },
}

pub struct IdlPath<'a>(&'a Path);
pub struct IdlString<'a>(&'a str);

pub struct JsClientGenerator<S> {
    idl: S,
    output_layout: OutputLayout,
}

impl<S> JsClientGenerator<S> {
    pub fn with_output_layout(self, output_layout: OutputLayout) -> Self {
        Self {
            output_layout,
            ..self
        }
    }
}

impl<'a> JsClientGenerator<IdlPath<'a>> {
    pub fn from_idl_path(idl_path: &'a Path) -> Self {
        Self {
            idl: IdlPath(idl_path),
            output_layout: OutputLayout::SingleFile,
        }
    }

    fn with_idl(self, idl: &'a str) -> JsClientGenerator<IdlString<'a>> {
        JsClientGenerator {
            idl: IdlString(idl),
            output_layout: self.output_layout,
        }
    }

    pub fn generate(self) -> Result<String> {
        let idl_path = self.idl.0;
        let path_str = idl_path.to_string_lossy();
        let idl = preprocess::preprocess(&path_str, &[&FsLoader, &GitLoader])
            .with_context(|| format!("Failed to preprocess IDL from {}", idl_path.display()))?;
        self.with_idl(&idl).generate()
    }

    pub fn generate_to(self, out_path: impl AsRef<Path>) -> Result<()> {
        let out_path = out_path.as_ref();
        let code = self
            .generate()
            .context("failed to generate TypeScript client")?;
        fs::write(out_path, code).with_context(|| {
            format!("Failed to write generated client to {}", out_path.display())
        })?;
        Ok(())
    }
}

impl<'a> JsClientGenerator<IdlString<'a>> {
    pub fn from_idl(idl: &'a str) -> Self {
        Self {
            idl: IdlString(idl),
            output_layout: OutputLayout::SingleFile,
        }
    }

    pub fn generate(self) -> Result<String> {
        let doc = parse_idl(self.idl.0).context("Failed to parse IDL")?;
        let type_gen = TypeGenerator::new();
        let mut generator = RootGenerator::new(&type_gen);
        let output = generator.generate(&doc);

        match self.output_layout {
            OutputLayout::SingleFile => Ok(output),
            OutputLayout::Split { .. } => {
                bail!("Split output layout is not implemented yet in Phase 1")
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use sails_idl_parser_v2::{FsLoader, preprocess};

    #[test]
    fn test_js_generator_includes() {
        let path = "tests/idls/includes/main.idl";
        let result = preprocess::preprocess(path, &[&FsLoader]).expect("Failed to preprocess IDL");

        assert!(result.contains("struct ResultData"));
        assert!(result.contains("enum Error"));
        assert!(result.contains("service ServiceA"));
        assert!(result.contains("program Main"));
        assert!(result.contains("alias MyResult = ResultData;"));

        let count = result.matches("struct ResultData").count();
        assert_eq!(count, 1, "ResultData should be included only once");
    }
}