1use crate::{
7 EmptySchema, PropertiesSchema, RefSchema, TaggedUnionSchema, ValuesSchema, type_utils,
8};
9use crate::{Serializable, TypeSchema, Types, elements::ElementsSchema};
10use indexmap::{IndexMap, IndexSet};
11use std::cell::{Cell, RefCell};
12use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
13use std::ffi::{OsStr, OsString};
14use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
15use std::num::{
16 NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32,
17 NonZeroU64, NonZeroUsize,
18};
19use std::path::{Path, PathBuf};
20use std::ptr::NonNull;
21use std::rc::Rc;
22use std::sync::atomic::{
23 AtomicBool, AtomicI8, AtomicI16, AtomicI32, AtomicI64, AtomicU8, AtomicU16, AtomicU32,
24 AtomicU64,
25};
26use std::sync::{Arc, Mutex, RwLock};
27use std::time::{Duration, Instant, SystemTime};
28
29thread_local! {
30 static RECURSION_TRACKER: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
32}
33
34pub trait Exportable {
36 fn get_type_name() -> String {
38 type_utils::get_type_name::<Self>()
39 }
40
41 fn export() -> Box<dyn Serializable> {
45 Self::export_with_recursion_check()
46 }
47
48 fn export_internal() -> impl Serializable;
53
54 fn export_with_recursion_check() -> Box<dyn Serializable> {
56 let type_name = Self::get_type_name();
57
58 let is_recursive = RECURSION_TRACKER.with(|tracker| {
59 let mut tracker = tracker.borrow_mut();
60 let is_recursive = tracker.contains(&type_name);
61
62 if !is_recursive {
63 tracker.insert(type_name.clone());
65 }
66
67 is_recursive
68 });
69
70 if is_recursive {
71 return Box::new(RefSchema::new(type_utils::get_type_name_from(type_name)));
72 }
73
74 let result = Self::export_internal();
75
76 RECURSION_TRACKER.with(|tracker| {
78 let mut tracker = tracker.borrow_mut();
79 tracker.remove(&type_name);
80 });
81
82 Box::new(result)
83 }
84}
85
86macro_rules! exportable {
91 (generic: { $($gen:tt)* }) => {
94 exportable!(@parse_impls $($gen)*);
95 };
96 (typeschema: { $($ty:tt)* }) => {
97 exportable!(@parse_typeschema $($ty)*);
98 };
99 (features: { $($feat:tt)* }) => {
100 exportable!(@parse_features $($feat)*);
101 };
102
103 (
105 typeschema: { $($ty:tt)* },
106 generic: { $($gen:tt)* },
107 features: { $($feat:tt)* }
108 ) => {
109 exportable!(typeschema: { $($ty)* });
110 exportable!(generic: { $($gen)* });
111 exportable!(features: { $($feat)* });
112 };
113
114 (
115 typeschema: { $($ty:tt)* },
116 generic: { $($gen:tt)* }
117 ) => {
118 exportable!(typeschema: { $($ty)* });
119 exportable!(generic: { $($gen)* });
120 };
121
122 (@parse_features $feature:literal => { $($body:item)* }, $($rest:tt)*) => {
124 exportable!(@parse_features $feature => { $($body)* });
125 exportable!(@parse_features $($rest)*);
126 };
127 (@parse_features $feature:literal => { $($body:item)* }) => {
128 $(
129 #[cfg(feature = $feature)]
130 $body
131 )*
132 };
133 (@parse_features) => {};
134
135 (@parse_typeschema $ty:ty => $to:ident, $($rest:tt)*) => {
138 exportable!(@parse_typeschema $ty => {
139 TypeSchema::new(Types::$to)
140 }, $($rest)*);
141 };
142
143 (@parse_typeschema static $ty:ty => $to:expr, $($rest:tt)*) => {
144 exportable!(@parse_typeschema $ty => { $to }, $($rest)*);
145 };
146
147 (@parse_typeschema $ty:ty => $implementation:block, $($rest:tt)*) => {
149 impl Exportable for $ty {
150 fn export_internal() -> impl Serializable {
151 $implementation
152 }
153 }
154 exportable!(@parse_typeschema $($rest)*);
155 };
156
157 (@parse_typeschema) => {};
159
160 (@parse_impls $type:ident < $($type_param:ident $(: $trait_bound:path)?),* $(,)? > => $implementation:expr, $($rest:tt)*) => {
163 impl<$($type_param: 'static + Exportable $(+ $trait_bound)?),*> Exportable for $type<$($type_param),*> {
164 fn export_internal() -> impl Serializable {
165 $implementation
166 }
167 fn get_type_name() -> String {
168 format!(
169 "::ronky::--virtual--::generic::{}",
170 vec![$($type_param::get_type_name()),*].join("")
171 )
172 }
173 }
174 exportable!(@parse_impls $($rest)*);
175 };
176
177 (@parse_impls $type:ident < $($type_param:ident $(: $trait_bound:path)?),* $(,)? > => $implementation:block, $($rest:tt)*) => {
179 exportable!(@parse_impls $type < $($type_param $(: $trait_bound)?),* > => {
180 $implementation
181 }, $($rest)*);
182 };
183
184 (@parse_impls) => {};
186}
187
188type SliceOf<T> = [T];
192
193exportable! {
194 typeschema: {
195 static () => EmptySchema::new(),
196 char => String,
197 String => String,
198 &str => String,
199 bool => Boolean,
200 f32 => Float32,
201 f64 => Float64,
202 i8 => Int8,
203 u8 => Uint8,
204 i16 => Int16,
205 u16 => Uint16,
206 i32 => Int32,
207 u32 => Uint32,
208 i64 => Int64,
209 u64 => Uint64,
210 AtomicBool => Boolean,
211 AtomicI8 => Int8,
212 AtomicU8 => Uint8,
213 AtomicI16 => Int16,
214 AtomicU16 => Uint16,
215 AtomicI32 => Int32,
216 AtomicU32 => Uint32,
217 AtomicI64 => Int64,
218 AtomicU64 => Uint64,
219 NonZeroI8 => Int8,
220 NonZeroU8 => Uint8,
221 NonZeroI16 => Int16,
222 NonZeroU16 => Uint16,
223 NonZeroI32 => Int32,
224 NonZeroU32 => Uint32,
225 NonZeroI64 => Int64,
226 NonZeroU64 => Uint64,
227 NonZeroIsize => Int64,
228 NonZeroUsize => Uint64,
229 OsStr => String,
230 OsString => String,
231 PathBuf => String,
232 Path => String,
233 IpAddr => String,
234 Ipv4Addr => String,
235 Ipv6Addr => String,
236 SocketAddr => String,
237 Duration => Int64,
238 Instant => Int64,
239 SystemTime => Int64,
240 },
241 generic: {
242 Option<T> => T::export(), Rc<T> => T::export(),
245 Arc<T> => T::export(),
246 Cell<T> => T::export(),
247 RefCell<T> => T::export(),
248 Mutex<T> => T::export(),
249 RwLock<T> => T::export(),
250 NonNull<T> => T::export(),
251
252 Box<T> => T::export_with_recursion_check(),
254 Result<T, E> => {
255 let mut schema = TaggedUnionSchema::new();
256 let mut ok_props = PropertiesSchema::new();
257 let mut err_props = PropertiesSchema::new();
258
259 ok_props.set_property("value", Box::new(T::export()));
260 err_props.set_property("value", Box::new(E::export()));
261
262 schema.add_mapping("Ok", Box::new(ok_props));
263 schema.add_mapping("Err", Box::new(err_props));
264 schema
265 },
266
267 SliceOf<T> => ElementsSchema::new(Box::new(T::export())),
269 Vec<T> => ElementsSchema::new(Box::new(T::export())),
270 VecDeque<T> => ElementsSchema::new(Box::new(T::export())),
271 LinkedList<T> => ElementsSchema::new(Box::new(T::export())),
272 HashSet<T> => ElementsSchema::new(Box::new(T::export())),
273 BTreeSet<T> => ElementsSchema::new(Box::new(T::export())),
274 BinaryHeap<T> => ElementsSchema::new(Box::new(T::export())),
275
276 HashMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
278 BTreeMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
279 IndexMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
280 IndexSet<T> => ElementsSchema::new(Box::new(T::export())),
281 },
282 features: {
283 "chrono" => {
284 use chrono::{DateTime, FixedOffset, Utc, Local, NaiveTime, NaiveDate, NaiveDateTime, TimeZone};
285
286 exportable! {
287 typeschema: {
288 DateTime<Utc> => Timestamp,
289 DateTime<Local> => Timestamp,
290 DateTime<FixedOffset> => Timestamp,
291 NaiveDate => Timestamp,
292 NaiveTime => Timestamp,
293 NaiveDateTime => Timestamp,
294 chrono::Duration => Int64,
295 },
296 generic: {
297 DateTime<Tz: TimeZone> => TypeSchema::new(Types::Timestamp),
298 }
299 }
300 },
301 "time" => {
302 exportable! {
303 typeschema: {
304 time::OffsetDateTime => Timestamp,
305 time::PrimitiveDateTime => Timestamp,
306 time::Date => Timestamp,
307 time::Time => Timestamp,
308 time::Duration => Int64,
309 }
310 }
311 },
312 "uuid" => {
313 exportable! {
314 typeschema: {
315 uuid::Uuid => String,
316 }
317 }
318 },
319 "bigdecimal" => {
320 exportable! {
321 typeschema: {
322 bigdecimal::BigDecimal => String,
323 }
324 }
325 },
326 "num-bigint" => {
327 exportable! {
328 typeschema: {
329 num_bigint::BigInt => String,
330 }
331 }
332 },
333 "num-bigfloat" => {
334 exportable! {
335 typeschema: {
336 num_bigfloat::BigFloat => String,
337 }
338 }
339 },
340 "rust_decimal" => {
341 exportable! {
342 typeschema: {
343 rust_decimal::Decimal => String,
344 }
345 }
346 },
347 "decimal" => {
348 exportable! {
349 typeschema: {
350 decimal::d128 => String,
351 }
352 }
353 },
354 "url" => {
355 exportable! {
356 typeschema: {
357 url::Url => String,
358 }
359 }
360 },
361 "bytes" => {
362 exportable! {
363 typeschema: {
364 bytes::Bytes => String,
365 bytes::BytesMut => String,
366 }
367 }
368 },
369 "dashmap" => {
370 use dashmap::{DashMap, DashSet};
371
372 exportable! {
373 generic: {
374 DashMap<K: ToString, V> => ValuesSchema::new(Box::new(V::export())),
375 DashSet<T> => ElementsSchema::new(Box::new(T::export())),
376 }
377 }
378 },
379 "smallvec" => {
380 use smallvec::SmallVec;
381
382 exportable! {
383 generic: {
384 SmallVec<T: smallvec::Array> => ElementsSchema::new(Box::new(T::export())),
385 }
386 }
387 }
388 }
389}