use crate::{
EmptySchema, PropertiesSchema, RefSchema, TaggedUnionSchema, ValuesSchema, type_utils,
};
use crate::{Serializable, TypeSchema, Types, elements::ElementsSchema};
use indexmap::{IndexMap, IndexSet};
use std::cell::{Cell, RefCell};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
use std::ffi::{OsStr, OsString};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32,
NonZeroU64, NonZeroUsize,
};
use std::path::{Path, PathBuf};
use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::atomic::{
AtomicBool, AtomicI8, AtomicI16, AtomicI32, AtomicI64, AtomicU8, AtomicU16, AtomicU32,
AtomicU64,
};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant, SystemTime};
thread_local! {
static RECURSION_TRACKER: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
}
pub trait Exportable {
fn get_type_name() -> String {
type_utils::get_type_name::<Self>()
}
fn export() -> Box<dyn Serializable> {
Self::export_with_recursion_check()
}
fn export_internal() -> impl Serializable;
fn export_with_recursion_check() -> Box<dyn Serializable> {
let type_name = Self::get_type_name();
let is_recursive = RECURSION_TRACKER.with(|tracker| {
let mut tracker = tracker.borrow_mut();
let is_recursive = tracker.contains(&type_name);
if !is_recursive {
tracker.insert(type_name.clone());
}
is_recursive
});
if is_recursive {
return Box::new(RefSchema::new(type_utils::get_type_name_from(type_name)));
}
let result = Self::export_internal();
RECURSION_TRACKER.with(|tracker| {
let mut tracker = tracker.borrow_mut();
tracker.remove(&type_name);
});
Box::new(result)
}
}
macro_rules! exportable {
(generic: { $($gen:tt)* }) => {
exportable!(@parse_impls $($gen)*);
};
(typeschema: { $($ty:tt)* }) => {
exportable!(@parse_typeschema $($ty)*);
};
(features: { $($feat:tt)* }) => {
exportable!(@parse_features $($feat)*);
};
(
typeschema: { $($ty:tt)* },
generic: { $($gen:tt)* },
features: { $($feat:tt)* }
) => {
exportable!(typeschema: { $($ty)* });
exportable!(generic: { $($gen)* });
exportable!(features: { $($feat)* });
};
(
typeschema: { $($ty:tt)* },
generic: { $($gen:tt)* }
) => {
exportable!(typeschema: { $($ty)* });
exportable!(generic: { $($gen)* });
};
(@parse_features $feature:literal => { $($body:item)* }, $($rest:tt)*) => {
exportable!(@parse_features $feature => { $($body)* });
exportable!(@parse_features $($rest)*);
};
(@parse_features $feature:literal => { $($body:item)* }) => {
$(
#[cfg(feature = $feature)]
$body
)*
};
(@parse_features) => {};
(@parse_typeschema $ty:ty => $to:ident, $($rest:tt)*) => {
exportable!(@parse_typeschema $ty => {
TypeSchema::new(Types::$to)
}, $($rest)*);
};
(@parse_typeschema static $ty:ty => $to:expr, $($rest:tt)*) => {
exportable!(@parse_typeschema $ty => { $to }, $($rest)*);
};
(@parse_typeschema $ty:ty => $implementation:block, $($rest:tt)*) => {
impl Exportable for $ty {
fn export_internal() -> impl Serializable {
$implementation
}
}
exportable!(@parse_typeschema $($rest)*);
};
(@parse_typeschema) => {};
(@parse_impls $type:ident < $($type_param:ident $(: $trait_bound:path)?),* $(,)? > => $implementation:expr, $($rest:tt)*) => {
impl<$($type_param: 'static + Exportable $(+ $trait_bound)?),*> Exportable for $type<$($type_param),*> {
fn export_internal() -> impl Serializable {
$implementation
}
fn get_type_name() -> String {
format!(
"::ronky::--virtual--::generic::{}",
vec![$($type_param::get_type_name()),*].join("")
)
}
}
exportable!(@parse_impls $($rest)*);
};
(@parse_impls $type:ident < $($type_param:ident $(: $trait_bound:path)?),* $(,)? > => $implementation:block, $($rest:tt)*) => {
exportable!(@parse_impls $type < $($type_param $(: $trait_bound)?),* > => {
$implementation
}, $($rest)*);
};
(@parse_impls) => {};
}
type SliceOf<T> = [T];
exportable! {
typeschema: {
static () => EmptySchema::new(),
char => String,
String => String,
&str => String,
bool => Boolean,
f32 => Float32,
f64 => Float64,
i8 => Int8,
u8 => Uint8,
i16 => Int16,
u16 => Uint16,
i32 => Int32,
u32 => Uint32,
i64 => Int64,
u64 => Uint64,
AtomicBool => Boolean,
AtomicI8 => Int8,
AtomicU8 => Uint8,
AtomicI16 => Int16,
AtomicU16 => Uint16,
AtomicI32 => Int32,
AtomicU32 => Uint32,
AtomicI64 => Int64,
AtomicU64 => Uint64,
NonZeroI8 => Int8,
NonZeroU8 => Uint8,
NonZeroI16 => Int16,
NonZeroU16 => Uint16,
NonZeroI32 => Int32,
NonZeroU32 => Uint32,
NonZeroI64 => Int64,
NonZeroU64 => Uint64,
NonZeroIsize => Int64,
NonZeroUsize => Uint64,
OsStr => String,
OsString => String,
PathBuf => String,
Path => String,
IpAddr => String,
Ipv4Addr => String,
Ipv6Addr => String,
SocketAddr => String,
Duration => Int64,
Instant => Int64,
SystemTime => Int64,
},
generic: {
Option<T> => T::export(), Rc<T> => T::export(),
Arc<T> => T::export(),
Cell<T> => T::export(),
RefCell<T> => T::export(),
Mutex<T> => T::export(),
RwLock<T> => T::export(),
NonNull<T> => T::export(),
Box<T> => T::export_with_recursion_check(),
Result<T, E> => {
let mut schema = TaggedUnionSchema::new();
let mut ok_props = PropertiesSchema::new();
let mut err_props = PropertiesSchema::new();
ok_props.set_property("value", Box::new(T::export()));
err_props.set_property("value", Box::new(E::export()));
schema.add_mapping("Ok", Box::new(ok_props));
schema.add_mapping("Err", Box::new(err_props));
schema
},
SliceOf<T> => ElementsSchema::new(Box::new(T::export())),
Vec<T> => ElementsSchema::new(Box::new(T::export())),
VecDeque<T> => ElementsSchema::new(Box::new(T::export())),
LinkedList<T> => ElementsSchema::new(Box::new(T::export())),
HashSet<T> => ElementsSchema::new(Box::new(T::export())),
BTreeSet<T> => ElementsSchema::new(Box::new(T::export())),
BinaryHeap<T> => ElementsSchema::new(Box::new(T::export())),
HashMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
BTreeMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
IndexMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
IndexSet<T> => ElementsSchema::new(Box::new(T::export())),
},
features: {
"chrono" => {
use chrono::{DateTime, FixedOffset, Utc, Local, NaiveTime, NaiveDate, NaiveDateTime, TimeZone};
exportable! {
typeschema: {
DateTime<Utc> => Timestamp,
DateTime<Local> => Timestamp,
DateTime<FixedOffset> => Timestamp,
NaiveDate => Timestamp,
NaiveTime => Timestamp,
NaiveDateTime => Timestamp,
chrono::Duration => Int64,
},
generic: {
DateTime<Tz: TimeZone> => TypeSchema::new(Types::Timestamp),
}
}
},
"time" => {
exportable! {
typeschema: {
time::OffsetDateTime => Timestamp,
time::PrimitiveDateTime => Timestamp,
time::Date => Timestamp,
time::Time => Timestamp,
time::Duration => Int64,
}
}
},
"uuid" => {
exportable! {
typeschema: {
uuid::Uuid => String,
}
}
},
"bigdecimal" => {
exportable! {
typeschema: {
bigdecimal::BigDecimal => String,
}
}
},
"num-bigint" => {
exportable! {
typeschema: {
num_bigint::BigInt => String,
}
}
},
"num-bigfloat" => {
exportable! {
typeschema: {
num_bigfloat::BigFloat => String,
}
}
},
"rust_decimal" => {
exportable! {
typeschema: {
rust_decimal::Decimal => String,
}
}
},
"decimal" => {
exportable! {
typeschema: {
decimal::d128 => String,
}
}
},
"url" => {
exportable! {
typeschema: {
url::Url => String,
}
}
},
"bytes" => {
exportable! {
typeschema: {
bytes::Bytes => String,
bytes::BytesMut => String,
}
}
},
"dashmap" => {
use dashmap::{DashMap, DashSet};
exportable! {
generic: {
DashMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
DashSet<T> => ElementsSchema::new(Box::new(T::export())),
}
}
},
"smallvec" => {
use smallvec::SmallVec;
exportable! {
generic: {
SmallVec<T: smallvec::Array> => ElementsSchema::new(Box::new(T::export())),
}
}
}
}
}