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;
pub trait TS {
const EXPORT_TO: Option<&'static str> = None;
fn decl() -> String {
panic!("{} cannot be declared", Self::name());
}
fn name() -> String;
fn name_with_type_args(args: Vec<String>) -> String {
format!("{}<{}>", Self::name(), args.join(", "))
}
fn inline() -> String {
panic!("{} cannot be inlined", Self::name());
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", Self::name())
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static;
fn transparent() -> bool;
fn export() -> Result<(), ExportError>
where
Self: 'static,
{
export::export_type::<Self>()
}
fn export_to(path: impl AsRef<Path>) -> Result<(), ExportError>
where
Self: 'static,
{
export::export_type_to::<Self, _>(path)
}
fn export_to_string() -> Result<String, ExportError>
where
Self: 'static,
{
export::export_type_to_string::<Self>()
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Dependency {
pub type_id: TypeId,
pub ts_name: String,
pub exported_to: &'static str,
}
impl Dependency {
pub fn from_ty<T: TS + 'static + ?Sized>() -> Option<Self> {
let exported_to = T::EXPORT_TO?;
Some(Dependency {
type_id: TypeId::of::<T>(),
ts_name: T::name(),
exported_to,
})
}
}
macro_rules! impl_primitives {
($($($ty:ty),* => $l:literal),*) => { $($(
impl TS for $ty {
fn name() -> String { $l.to_owned() }
fn name_with_type_args(args: Vec<String>) -> String {
assert!(args.is_empty(), "called name_with_type_args on primitive");
$l.to_owned()
}
fn inline() -> String { $l.to_owned() }
fn dependencies() -> Vec<Dependency> { vec![] }
fn transparent() -> bool { false }
}
)*)* };
}
macro_rules! impl_tuples {
( impl $($i:ident),* ) => {
impl<$($i: TS),*> TS for ($($i,)*) {
fn name() -> String {
format!("[{}]", [$($i::name()),*].join(", "))
}
fn inline() -> String {
format!("[{}]", [$($i::inline()),*].join(", "))
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static
{
[$( Dependency::from_ty::<$i>() ),*]
.into_iter()
.flatten()
.collect()
}
fn transparent() -> bool { true }
}
};
( $i2:ident $(, $i:ident)* ) => {
impl_tuples!(impl $i2 $(, $i)* );
impl_tuples!($($i),*);
};
() => {};
}
macro_rules! impl_wrapper {
($($t:tt)*) => {
$($t)* {
fn name() -> String { T::name() }
fn name_with_type_args(mut args: Vec<String>) -> String {
assert_eq!(args.len(), 1);
args.remove(0)
}
fn inline() -> String { T::inline() }
fn inline_flattened() -> String { T::inline_flattened() }
fn dependencies() -> Vec<Dependency>
where
Self: 'static
{
T::dependencies()
}
fn transparent() -> bool { T::transparent() }
}
};
}
macro_rules! impl_shadow {
(as $s:ty: $($impl:tt)*) => {
$($impl)* {
fn name() -> String { <$s>::name() }
fn name_with_type_args(args: Vec<String>) -> String { <$s>::name_with_type_args(args) }
fn inline() -> String { <$s>::inline() }
fn inline_flattened() -> String { <$s>::inline_flattened() }
fn dependencies() -> Vec<$crate::Dependency>
where
Self: 'static
{
<$s>::dependencies()
}
fn transparent() -> bool { <$s>::transparent() }
}
};
}
impl<T: TS> TS for Option<T> {
fn name() -> String {
unreachable!();
}
fn name_with_type_args(args: Vec<String>) -> String {
assert_eq!(
args.len(),
1,
"called Option::name_with_type_args with {} args",
args.len()
);
format!("{} | null", args[0])
}
fn inline() -> String {
format!("{} | null", T::inline())
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
{
[Dependency::from_ty::<T>()].into_iter().flatten().collect()
}
fn transparent() -> bool {
true
}
}
impl<T: TS> TS for Vec<T> {
fn name() -> String {
"Array".to_owned()
}
fn name_with_type_args(args: Vec<String>) -> String {
assert_eq!(
args.len(),
1,
"called Vec::name_with_type_args with {} args",
args.len()
);
format!("Array<{}>", args[0])
}
fn inline() -> String {
format!("Array<{}>", T::inline())
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
{
[Dependency::from_ty::<T>()].into_iter().flatten().collect()
}
fn transparent() -> bool {
true
}
}
impl<K: TS, V: TS> TS for HashMap<K, V> {
fn name() -> String {
"Record".to_owned()
}
fn name_with_type_args(args: Vec<String>) -> String {
assert_eq!(
args.len(),
2,
"called HashMap::name_with_type_args with {} args",
args.len()
);
format!("Record<{}, {}>", args[0], args[1])
}
fn inline() -> String {
format!("Record<{}, {}>", K::inline(), V::inline())
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
{
[Dependency::from_ty::<K>(), Dependency::from_ty::<V>()]
.into_iter()
.flatten()
.collect()
}
fn transparent() -> bool {
true
}
}
impl<I: TS> TS for Range<I> {
fn name() -> String {
panic!("called Range::name - Did you use a type alias?")
}
fn name_with_type_args(args: Vec<String>) -> String {
assert_eq!(
args.len(),
1,
"called Range::name_with_type_args with {} args",
args.len()
);
format!("{{ start: {}, end: {}, }}", &args[0], &args[0])
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
{
[Dependency::from_ty::<I>()].into_iter().flatten().collect()
}
fn transparent() -> bool {
true
}
}
impl<I: TS> TS for RangeInclusive<I> {
fn name() -> String {
panic!("called RangeInclusive::name - Did you use a type alias?")
}
fn name_with_type_args(args: Vec<String>) -> String {
assert_eq!(
args.len(),
1,
"called RangeInclusive::name_with_type_args with {} args",
args.len()
);
format!("{{ start: {}, end: {}, }}", &args[0], &args[0])
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
{
[Dependency::from_ty::<I>()].into_iter().flatten().collect()
}
fn transparent() -> bool {
true
}
}
impl_shadow!(as T: impl<'a, T: TS + ?Sized> TS for &T);
impl_shadow!(as Vec<T>: impl<T: TS> TS for HashSet<T>);
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, const N: usize> TS for [T; N]);
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 + ?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 = "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::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 = "serde-json-impl")]
impl_primitives! { serde_json::Value => "string | number | boolean | null" }
#[cfg(feature = "schemars-impl")]
impl_primitives! { schemars::schema::Schema => "object" }
#[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;