diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index 2bf574567..c7a2734a1 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -1,6 +1,6 @@ use std::{ any::TypeId, - collections::BTreeMap, + collections::{BTreeMap, HashSet}, fmt::Write, path::{Component, Path, PathBuf}, }; @@ -161,3 +161,93 @@ where Some(comps.iter().map(|c| c.as_os_str()).collect()) } } + +#[derive(Debug)] +pub struct MissingDependenciesError { + dependencies: Vec, +} + +impl std::fmt::Display for MissingDependenciesError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Missing required types: {}", + self.dependencies.join(", ") + ) + } +} + +impl std::error::Error for MissingDependenciesError {} + +/// Allows exporting multiple types to a single file. +pub struct SingleFileExporter { + required_type_ids: HashSet<(TypeId, String)>, + added_types: HashSet, + buffer: String, +} + +impl SingleFileExporter { + pub fn new(with_codegen_warning: bool) -> Self { + let buffer = if with_codegen_warning { + NOTE.to_string() + } else { + String::new() + }; + + Self { + required_type_ids: HashSet::new(), + added_types: HashSet::new(), + buffer, + } + } + + pub fn add_type(&mut self) { + let id = TypeId::of::(); + if self.added_types.contains(&id) { + return; + } + + if !self.buffer.is_empty() { + self.buffer.push('\n'); + } + generate_decl::(&mut self.buffer); + self.buffer.push('\n'); + self.added_types.insert(id); + + for dep in T::dependencies() { + self.required_type_ids + .insert((dep.type_id, dep.ts_name.clone())); + } + } + + pub fn and(mut self) -> Self { + self.add_type::(); + self + } + + /// Finalize the export. + /// + /// Returns the generated typescritp code on success, or an error if any + /// required type dependencies were not added. + pub fn finish(self) -> Result { + let missing: Vec = self + .required_type_ids + .into_iter() + .filter_map(|(id, name)| { + if self.added_types.contains(&id) { + None + } else { + Some(name) + } + }) + .collect(); + + if !missing.is_empty() { + Err(MissingDependenciesError { + dependencies: missing, + }) + } else { + Ok(self.buffer) + } + } +} diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 49f46c67f..ef522c312 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -137,7 +137,7 @@ use std::{ pub use ts_rs_macros::TS; -pub use crate::export::ExportError; +pub use crate::export::{ExportError, MissingDependenciesError, SingleFileExporter}; #[cfg(feature = "chrono-impl")] mod chrono; diff --git a/ts-rs/tests/singlefile.rs b/ts-rs/tests/singlefile.rs new file mode 100644 index 000000000..eb5be6a90 --- /dev/null +++ b/ts-rs/tests/singlefile.rs @@ -0,0 +1,30 @@ +#![allow(dead_code)] + +use ts_rs::{SingleFileExporter, TS}; + +#[derive(TS)] +struct Alpha { + b: Beta, +} + +#[derive(TS)] +struct Beta { + x: bool, +} + +#[test] +fn test_singlefile() { + let out = SingleFileExporter::new(false) + .and::() + .and::() + .finish() + .unwrap(); + + assert_eq!( + out, + r#"export type Alpha = { b: Beta, } + +export type Beta = { x: boolean, } +"# + ); +}