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;
type OptionInnerType: ?Sized;
#[doc(hidden)]
const IS_OPTION: bool = false;
fn docs() -> Option<String> {
None
}
fn ident() -> String {
let name = <Self as crate::TS>::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 TypeVisitor for Visit<'_> {
fn visit<T: TS + 'static + ?Sized>(&mut self) {
if let Some(dep) = Dependency::from_ty::<T>() {
self.0.push(dep);
}
}
}
<Self as crate::TS>::visit_dependencies(&mut Visit(&mut deps));
deps
}
fn export() -> Result<(), ExportError>
where
Self: 'static,
{
let path = <Self as crate::TS>::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<PathBuf> {
None
}
fn default_output_path() -> Option<PathBuf> {
Some(export::default_out_dir().join(<Self as crate::TS>::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: PathBuf,
}
impl Dependency {
pub fn from_ty<T: TS + 'static + ?Sized>() -> Option<Self> {
let output_path = <T as crate::TS>::output_path()?;
Some(Dependency {
type_id: TypeId::of::<T>(),
ts_name: <T as crate::TS>::ident(),
output_path,
})
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "`#[ts(optional)]` can only be used on fields of type `Option`",
note = "`#[ts(optional)]` was used on a field of type {Self}, which is not permitted",
label = "`#[ts(optional)]` is not allowed on field of type {Self}"
)]
pub trait IsOption {}
impl<T> IsOption for Option<T> {}
macro_rules! impl_primitives {
($($($ty:ty),* => $l:literal),*) => { $($(
impl TS for $ty {
type WithoutGenerics = Self;
type OptionInnerType = 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, );
type OptionInnerType = Self;
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 as $crate::TS>::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;
type OptionInnerType = Self;
fn name() -> String { <T as $crate::TS>::name() }
fn inline() -> String { <T as $crate::TS>::inline() }
fn inline_flattened() -> String { <T as $crate::TS>::inline_flattened() }
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as $crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as $crate::TS>::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;
type OptionInnerType = <$s as $crate::TS>::OptionInnerType;
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<std::path::PathBuf> { <$s as $crate::TS>::output_path() }
}
};
}
impl<T: TS> TS for Option<T> {
type WithoutGenerics = Self;
type OptionInnerType = T;
const IS_OPTION: bool = true;
fn name() -> String {
format!("{} | null", <T as crate::TS>::name())
}
fn inline() -> String {
format!("{} | null", <T as crate::TS>::inline())
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_generics(v);
v.visit::<T>();
}
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())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", <Self as crate::TS>::name())
}
}
impl<T: TS, E: TS> TS for Result<T, E> {
type WithoutGenerics = Result<Dummy, Dummy>;
type OptionInnerType = Self;
fn name() -> String {
format!(
"{{ Ok : {} }} | {{ Err : {} }}",
<T as crate::TS>::name(),
<E as crate::TS>::name()
)
}
fn inline() -> String {
format!(
"{{ Ok : {} }} | {{ Err : {} }}",
<T as crate::TS>::inline(),
<E as crate::TS>::inline()
)
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_dependencies(v);
<E as crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_generics(v);
v.visit::<T>();
<E as crate::TS>::visit_generics(v);
v.visit::<E>();
}
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())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", <Self as crate::TS>::name())
}
}
impl<T: TS> TS for Vec<T> {
type WithoutGenerics = Vec<Dummy>;
type OptionInnerType = Self;
fn ident() -> String {
"Array".to_owned()
}
fn name() -> String {
format!("Array<{}>", <T as crate::TS>::name())
}
fn inline() -> String {
format!("Array<{}>", <T as crate::TS>::inline())
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_generics(v);
v.visit::<T>();
}
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())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", <Self as crate::TS>::name())
}
}
const ARRAY_TUPLE_LIMIT: usize = 64;
impl<T: TS, const N: usize> TS for [T; N] {
type WithoutGenerics = [Dummy; N];
type OptionInnerType = Self;
fn name() -> String {
if N > ARRAY_TUPLE_LIMIT {
return <Vec<T> as crate::TS>::name();
}
format!(
"[{}]",
(0..N)
.map(|_| <T as crate::TS>::name())
.collect::<Box<[_]>>()
.join(", ")
)
}
fn inline() -> String {
if N > ARRAY_TUPLE_LIMIT {
return <Vec<T> as crate::TS>::inline();
}
format!(
"[{}]",
(0..N)
.map(|_| <T as crate::TS>::inline())
.collect::<Box<[_]>>()
.join(", ")
)
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<T as crate::TS>::visit_generics(v);
v.visit::<T>();
}
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())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", <Self as crate::TS>::name())
}
}
impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {
type WithoutGenerics = HashMap<Dummy, Dummy>;
type OptionInnerType = Self;
fn ident() -> String {
panic!()
}
fn name() -> String {
format!(
"{{ [key in {}]?: {} }}",
<K as crate::TS>::name(),
<V as crate::TS>::name()
)
}
fn inline() -> String {
format!(
"{{ [key in {}]?: {} }}",
<K as crate::TS>::inline(),
<V as crate::TS>::inline()
)
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<K as crate::TS>::visit_dependencies(v);
<V as crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<K as crate::TS>::visit_generics(v);
v.visit::<K>();
<V as crate::TS>::visit_generics(v);
v.visit::<V>();
}
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())
}
fn inline_flattened() -> String {
format!(
"({{ [key in {}]?: {} }})",
<K as crate::TS>::inline(),
<V as crate::TS>::inline()
)
}
}
impl<I: TS> TS for Range<I> {
type WithoutGenerics = Range<Dummy>;
type OptionInnerType = Self;
fn name() -> String {
format!(
"{{ start: {}, end: {}, }}",
<I as crate::TS>::name(),
<I as crate::TS>::name()
)
}
fn visit_dependencies(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<I as crate::TS>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl TypeVisitor)
where
Self: 'static,
{
<I as crate::TS>::visit_generics(v);
v.visit::<I>();
}
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())
}
fn inline() -> String {
panic!("{} cannot be inlined", <Self as crate::TS>::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", <Self as crate::TS>::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;
type OptionInnerType = Self;
fn name() -> String {
"Dummy".to_owned()
}
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())
}
fn inline() -> String {
panic!("{} cannot be inlined", <Self as crate::TS>::name())
}
fn inline_flattened() -> String {
panic!("{} cannot be flattened", <Self as crate::TS>::name())
}
}
#[doc(hidden)]
pub fn format_docs(docs: &[&str]) -> String {
match docs {
[] => String::new(),
[doc] if doc.contains('\n') => format!("/**{doc}*/\n"),
_ => {
let mut buffer = String::from("/**\n");
let mut lines = docs.iter().peekable();
while let Some(line) = lines.next() {
buffer.push_str(" *");
buffer.push_str(line);
if lines.peek().is_some() {
buffer.push('\n');
}
}
buffer.push_str("\n */\n");
buffer
}
}
}