use std::{
any::TypeId,
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
},
ops::{Range, RangeInclusive},
path::{Path, PathBuf},
};
pub use ts_rs_macros::TS;
pub use crate::export::ExportError;
#[cfg(feature = "chrono-impl")]
mod chrono;
mod export;
#[cfg(feature = "serde-json-impl")]
mod serde_json;
#[cfg(feature = "tokio-impl")]
mod tokio;
pub trait TS {
type WithoutGenerics: TS + ?Sized;
const DOCS: Option<&'static str> = None;
fn ident() -> String {
let name = Self::name();
match name.find('<') {
Some(i) => name[..i].to_owned(),
None => name,
}
}
fn decl() -> String;
fn decl_concrete() -> String;
fn name() -> String;
fn inline() -> String;
fn inline_flattened() -> String;
fn visit_dependencies(_: &mut impl TypeVisitor)
where
Self: 'static,
{
}
fn visit_generics(_: &mut impl TypeVisitor)
where
Self: 'static,
{
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
{
let mut deps: Vec<Dependency> = vec![];
struct Visit<'a>(&'a mut Vec<Dependency>);
impl<'a> TypeVisitor for Visit<'a> {
fn visit<T: TS + 'static + ?Sized>(&mut self) {
if let Some(dep) = Dependency::from_ty::<T>() {
self.0.push(dep);
}
}
}
Self::visit_dependencies(&mut Visit(&mut deps));
deps
}
fn export() -> Result<(), ExportError>
where
Self: 'static,
{
let path = Self::default_output_path()
.ok_or_else(std::any::type_name::<Self>)
.map_err(ExportError::CannotBeExported)?;
export::export_to::<Self, _>(path)
}
fn export_all() -> Result<(), ExportError>
where
Self: 'static,
{
export::export_all_into::<Self>(&*export::default_out_dir())
}
fn export_all_to(out_dir: impl AsRef<Path>) -> Result<(), ExportError>
where
Self: 'static,
{
export::export_all_into::<Self>(out_dir)
}
fn export_to_string() -> Result<String, ExportError>
where
Self: 'static,
{
export::export_to_string::<Self>()
}
fn output_path() -> Option<&'static Path> {
None
}
fn default_output_path() -> Option<PathBuf> {
Some(export::default_out_dir().join(Self::output_path()?))
}
}
pub trait TypeVisitor: Sized {
fn visit<T: TS + 'static + ?Sized>(&mut self);
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Dependency {
pub type_id: TypeId,
pub ts_name: String,
pub output_path: &'static Path,
}
impl Dependency {
pub fn from_ty<T: TS + 'static + ?Sized>() -> Option<Self> {
let output_path = T::output_path()?;
Some(Dependency {
type_id: TypeId::of::<T>(),
ts_name: T::ident(),
output_path,
})
}
}
macro_rules! impl_primitives {
($($($ty:ty),* => $l:literal),*) => { $($(
impl TS for $ty {
type WithoutGenerics = Self;
fn name() -> String { $l.to_owned() }
fn inline() -> String { <Self as $crate::TS>::name() }
fn inline_flattened() -> String { panic!("{} cannot be flattened", <Self as $crate::TS>::name()) }
fn decl() -> String { panic!("{} cannot be declared", <Self as $crate::TS>::name()) }
fn decl_concrete() -> String { panic!("{} cannot be declared", <Self as $crate::TS>::name()) }
}
)*)* };
}
macro_rules! impl_tuples {
( impl $($i:ident),* ) => {
impl<$($i: TS),*> TS for ($($i,)*) {
type WithoutGenerics = (Dummy, );
fn name() -> String {
format!("[{}]", [$(<$i as $crate::TS>::name()),*].join(", "))
}
fn inline() -> String {
panic!("tuple cannot be inlined!");
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static
{
$(
v.visit::<$i>();
<$i>::visit_generics(v);
)*
}
fn inline_flattened() -> String { panic!("tuple cannot be flattened") }
fn decl() -> String { panic!("tuple cannot be declared") }
fn decl_concrete() -> String { panic!("tuple cannot be declared") }
}
};
( $i2:ident $(, $i:ident)* ) => {
impl_tuples!(impl $i2 $(, $i)* );
impl_tuples!($($i),*);
};
() => {};
}
macro_rules! impl_wrapper {
($($t:tt)*) => {
$($t)* {
type WithoutGenerics = Self;
fn name() -> String { T::name() }
fn inline() -> String { T::inline() }
fn inline_flattened() -> String { T::inline_flattened() }
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_generics(v);
v.visit::<T>();
}
fn decl() -> String { panic!("wrapper type cannot be declared") }
fn decl_concrete() -> String { panic!("wrapper type cannot be declared") }
}
};
}
macro_rules! impl_shadow {
(as $s:ty: $($impl:tt)*) => {
$($impl)* {
type WithoutGenerics = <$s as $crate::TS>::WithoutGenerics;
fn ident() -> String { <$s as $crate::TS>::ident() }
fn name() -> String { <$s as $crate::TS>::name() }
fn inline() -> String { <$s as $crate::TS>::inline() }
fn inline_flattened() -> String { <$s as $crate::TS>::inline_flattened() }
fn visit_dependencies(v: &mut impl $crate::TypeVisitor)
where
Self: 'static,
{
<$s as $crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl $crate::TypeVisitor)
where
Self: 'static,
{
<$s as $crate::TS>::visit_generics(v);
}
fn decl() -> String { <$s as $crate::TS>::decl() }
fn decl_concrete() -> String { <$s as $crate::TS>::decl_concrete() }
fn output_path() -> Option<&'static std::path::Path> { <$s as $crate::TS>::output_path() }
}
};
}
impl<T: TS> TS for Option<T> {
type WithoutGenerics = Self;
fn name() -> String {
format!("{} | null", T::name())
}
fn inline() -> String {
format!("{} | null", T::inline())
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_generics(v);
v.visit::<T>();
}
fn decl() -> String {
panic!("{} cannot be declared", Self::name())
}
fn decl_concrete() -> String {
panic!("{} cannot be declared", Self::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
}
impl<T: TS, E: TS> TS for Result<T, E> {
type WithoutGenerics = Result<Dummy, Dummy>;
fn name() -> String {
format!("{{ Ok : {} }} | {{ Err : {} }}", T::name(), E::name())
}
fn inline() -> String {
format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline())
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_dependencies(v);
E::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_generics(v);
v.visit::<T>();
E::visit_generics(v);
v.visit::<E>();
}
fn decl() -> String {
panic!("{} cannot be declared", Self::name())
}
fn decl_concrete() -> String {
panic!("{} cannot be declared", Self::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
}
impl<T: TS> TS for Vec<T> {
type WithoutGenerics = Vec<Dummy>;
fn ident() -> String {
"Array".to_owned()
}
fn name() -> String {
format!("Array<{}>", T::name())
}
fn inline() -> String {
format!("Array<{}>", T::inline())
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_generics(v);
v.visit::<T>();
}
fn decl() -> String {
panic!("{} cannot be declared", Self::name())
}
fn decl_concrete() -> String {
panic!("{} cannot be declared", Self::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
}
const ARRAY_TUPLE_LIMIT: usize = 64;
impl<T: TS, const N: usize> TS for [T; N] {
type WithoutGenerics = [Dummy; N];
fn name() -> String {
if N > ARRAY_TUPLE_LIMIT {
return Vec::<T>::name();
}
format!(
"[{}]",
(0..N).map(|_| T::name()).collect::<Box<[_]>>().join(", ")
)
}
fn inline() -> String {
if N > ARRAY_TUPLE_LIMIT {
return Vec::<T>::inline();
}
format!(
"[{}]",
(0..N).map(|_| T::inline()).collect::<Box<[_]>>().join(", ")
)
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
T::visit_generics(v);
v.visit::<T>();
}
fn decl() -> String {
panic!("{} cannot be declared", Self::name())
}
fn decl_concrete() -> String {
panic!("{} cannot be declared", Self::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
}
impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {
type WithoutGenerics = HashMap<Dummy, Dummy>;
fn ident() -> String {
panic!()
}
fn name() -> String {
format!("{{ [key in {}]?: {} }}", K::name(), V::name())
}
fn inline() -> String {
format!("{{ [key in {}]?: {} }}", K::inline(), V::inline())
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
K::visit_dependencies(v);
V::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
K::visit_generics(v);
v.visit::<K>();
V::visit_generics(v);
v.visit::<V>();
}
fn decl() -> String {
panic!("{} cannot be declared", Self::name())
}
fn decl_concrete() -> String {
panic!("{} cannot be declared", Self::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
}
impl<I: TS> TS for Range<I> {
type WithoutGenerics = Range<Dummy>;
fn name() -> String {
format!("{{ start: {}, end: {}, }}", I::name(), I::name())
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
I::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
I::visit_generics(v);
v.visit::<I>();
}
fn decl() -> String {
panic!("{} cannot be declared", Self::name())
}
fn decl_concrete() -> String {
panic!("{} cannot be declared", Self::name())
}
fn inline() -> String {
panic!("{} cannot be inlined", Self::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
}
impl_shadow!(as Range<I>: impl<I: TS> TS for RangeInclusive<I>);
impl_shadow!(as Vec<T>: impl<T: TS, H> TS for HashSet<T, H>);
impl_shadow!(as Vec<T>: impl<T: TS> TS for BTreeSet<T>);
impl_shadow!(as HashMap<K, V>: impl<K: TS, V: TS> TS for BTreeMap<K, V>);
impl_shadow!(as Vec<T>: impl<T: TS> TS for [T]);
impl_wrapper!(impl<T: TS + ?Sized> TS for &T);
impl_wrapper!(impl<T: TS + ?Sized> TS for Box<T>);
impl_wrapper!(impl<T: TS + ?Sized> TS for std::sync::Arc<T>);
impl_wrapper!(impl<T: TS + ?Sized> TS for std::rc::Rc<T>);
impl_wrapper!(impl<'a, T: TS + ToOwned + ?Sized> TS for std::borrow::Cow<'a, T>);
impl_wrapper!(impl<T: TS> TS for std::cell::Cell<T>);
impl_wrapper!(impl<T: TS> TS for std::cell::RefCell<T>);
impl_wrapper!(impl<T: TS> TS for std::sync::Mutex<T>);
impl_wrapper!(impl<T: TS> TS for std::sync::RwLock<T>);
impl_wrapper!(impl<T: TS + ?Sized> TS for std::sync::Weak<T>);
impl_wrapper!(impl<T: TS> TS for std::marker::PhantomData<T>);
impl_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
#[cfg(feature = "bigdecimal-impl")]
impl_primitives! { bigdecimal::BigDecimal => "string" }
#[cfg(feature = "smol_str-impl")]
impl_primitives! { smol_str::SmolStr => "string" }
#[cfg(feature = "uuid-impl")]
impl_primitives! { uuid::Uuid => "string" }
#[cfg(feature = "url-impl")]
impl_primitives! { url::Url => "string" }
#[cfg(feature = "ordered-float-impl")]
impl_primitives! { ordered_float::OrderedFloat<f32> => "number" }
#[cfg(feature = "ordered-float-impl")]
impl_primitives! { ordered_float::OrderedFloat<f64> => "number" }
#[cfg(feature = "bson-uuid-impl")]
impl_primitives! { bson::oid::ObjectId => "string" }
#[cfg(feature = "bson-uuid-impl")]
impl_primitives! { bson::Uuid => "string" }
#[cfg(feature = "indexmap-impl")]
impl_shadow!(as Vec<T>: impl<T: TS> TS for indexmap::IndexSet<T>);
#[cfg(feature = "indexmap-impl")]
impl_shadow!(as HashMap<K, V>: impl<K: TS, V: TS> TS for indexmap::IndexMap<K, V>);
#[cfg(feature = "heapless-impl")]
impl_shadow!(as Vec<T>: impl<T: TS, const N: usize> TS for heapless::Vec<T, N>);
#[cfg(feature = "semver-impl")]
impl_primitives! { semver::Version => "string" }
#[cfg(feature = "bytes-impl")]
mod bytes {
use super::TS;
impl_shadow!(as Vec<u8>: impl TS for bytes::Bytes);
impl_shadow!(as Vec<u8>: impl TS for bytes::BytesMut);
}
impl_primitives! {
u8, i8, NonZeroU8, NonZeroI8,
u16, i16, NonZeroU16, NonZeroI16,
u32, i32, NonZeroU32, NonZeroI32,
usize, isize, NonZeroUsize, NonZeroIsize, f32, f64 => "number",
u64, i64, NonZeroU64, NonZeroI64,
u128, i128, NonZeroU128, NonZeroI128 => "bigint",
bool => "boolean",
char, Path, PathBuf, String, str,
Ipv4Addr, Ipv6Addr, IpAddr, SocketAddrV4, SocketAddrV6, SocketAddr => "string",
() => "null"
}
#[rustfmt::skip]
pub(crate) use impl_primitives;
#[rustfmt::skip]
pub(crate) use impl_shadow;
#[rustfmt::skip]
pub(crate) use impl_wrapper;
#[doc(hidden)]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct Dummy;
impl std::fmt::Display for Dummy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl TS for Dummy {
type WithoutGenerics = Self;
fn name() -> String {
"Dummy".to_owned()
}
fn decl() -> String {
panic!("{} cannot be declared", Self::name())
}
fn decl_concrete() -> String {
panic!("{} cannot be declared", Self::name())
}
fn inline() -> String {
panic!("{} cannot be inlined", Self::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
}