1pub use ferro_type_derive::TypeScript;
17
18use std::collections::HashMap;
19
20pub trait TypeScript {
55 fn typescript() -> TypeDef;
57}
58
59#[derive(Debug, Clone, PartialEq)]
72pub enum TypeDef {
73 Primitive(Primitive),
75
76 Array(Box<TypeDef>),
78
79 Tuple(Vec<TypeDef>),
81
82 Object(Vec<Field>),
84
85 Union(Vec<TypeDef>),
87
88 Intersection(Vec<TypeDef>),
90
91 Record {
93 key: Box<TypeDef>,
94 value: Box<TypeDef>,
95 },
96
97 Named {
100 name: String,
101 def: Box<TypeDef>,
102 },
103
104 Ref(String),
107
108 Literal(Literal),
110
111 Function {
113 params: Vec<Field>,
114 return_type: Box<TypeDef>,
115 },
116
117 Generic {
119 base: String,
120 args: Vec<TypeDef>,
121 },
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub enum Primitive {
127 String,
129 Number,
131 Boolean,
133 Null,
135 Undefined,
137 Void,
139 Never,
141 Any,
143 Unknown,
145 BigInt,
147}
148
149#[derive(Debug, Clone, PartialEq)]
151pub struct Field {
152 pub name: String,
154 pub ty: TypeDef,
156 pub optional: bool,
158 pub readonly: bool,
160}
161
162impl Field {
163 pub fn new(name: impl Into<String>, ty: TypeDef) -> Self {
165 Self {
166 name: name.into(),
167 ty,
168 optional: false,
169 readonly: false,
170 }
171 }
172
173 pub fn optional(name: impl Into<String>, ty: TypeDef) -> Self {
175 Self {
176 name: name.into(),
177 ty,
178 optional: true,
179 readonly: false,
180 }
181 }
182
183 pub fn readonly(mut self) -> Self {
185 self.readonly = true;
186 self
187 }
188}
189
190#[derive(Debug, Clone, PartialEq)]
192pub enum Literal {
193 String(String),
195 Number(f64),
197 Boolean(bool),
199}
200
201impl TypeDef {
202 pub fn render(&self) -> String {
204 match self {
205 TypeDef::Primitive(p) => p.render().to_string(),
206 TypeDef::Array(inner) => {
207 let inner_str = inner.render();
208 if matches!(inner.as_ref(), TypeDef::Union(_)) {
210 format!("({})[]", inner_str)
211 } else {
212 format!("{}[]", inner_str)
213 }
214 }
215 TypeDef::Tuple(items) => {
216 let items_str: Vec<_> = items.iter().map(|t| t.render()).collect();
217 format!("[{}]", items_str.join(", "))
218 }
219 TypeDef::Object(fields) => {
220 if fields.is_empty() {
221 "{}".to_string()
222 } else {
223 let fields_str: Vec<_> = fields
224 .iter()
225 .map(|f| {
226 let readonly = if f.readonly { "readonly " } else { "" };
227 let opt = if f.optional { "?" } else { "" };
228 format!("{}{}{}: {}", readonly, f.name, opt, f.ty.render())
229 })
230 .collect();
231 format!("{{ {} }}", fields_str.join("; "))
232 }
233 }
234 TypeDef::Union(variants) => {
235 let variants_str: Vec<_> = variants.iter().map(|t| t.render()).collect();
236 variants_str.join(" | ")
237 }
238 TypeDef::Intersection(types) => {
239 let types_str: Vec<_> = types.iter().map(|t| t.render()).collect();
240 types_str.join(" & ")
241 }
242 TypeDef::Record { key, value } => {
243 format!("Record<{}, {}>", key.render(), value.render())
244 }
245 TypeDef::Named { name, .. } => name.clone(),
246 TypeDef::Ref(name) => name.clone(),
247 TypeDef::Literal(lit) => lit.render(),
248 TypeDef::Function {
249 params,
250 return_type,
251 } => {
252 let params_str: Vec<_> = params
253 .iter()
254 .map(|p| format!("{}: {}", p.name, p.ty.render()))
255 .collect();
256 format!("({}) => {}", params_str.join(", "), return_type.render())
257 }
258 TypeDef::Generic { base, args } => {
259 let args_str: Vec<_> = args.iter().map(|t| t.render()).collect();
260 format!("{}<{}>", base, args_str.join(", "))
261 }
262 }
263 }
264
265 pub fn render_declaration(&self) -> String {
270 match self {
271 TypeDef::Named { name, def } => {
272 format!("type {} = {};", name, def.render())
273 }
274 _ => self.render(),
275 }
276 }
277}
278
279impl Primitive {
280 pub fn render(&self) -> &'static str {
282 match self {
283 Primitive::String => "string",
284 Primitive::Number => "number",
285 Primitive::Boolean => "boolean",
286 Primitive::Null => "null",
287 Primitive::Undefined => "undefined",
288 Primitive::Void => "void",
289 Primitive::Never => "never",
290 Primitive::Any => "any",
291 Primitive::Unknown => "unknown",
292 Primitive::BigInt => "bigint",
293 }
294 }
295}
296
297impl Literal {
298 pub fn render(&self) -> String {
300 match self {
301 Literal::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
302 Literal::Number(n) => {
303 if n.fract() == 0.0 {
304 format!("{}", *n as i64)
305 } else {
306 format!("{}", n)
307 }
308 }
309 Literal::Boolean(b) => b.to_string(),
310 }
311 }
312}
313
314pub fn extract_object_fields(typedef: &TypeDef) -> Vec<Field> {
328 match typedef {
329 TypeDef::Object(fields) => fields.clone(),
330 TypeDef::Named { def, .. } => extract_object_fields(def),
331 other => panic!(
332 "#[ts(flatten)] can only be used on fields with object types, got: {:?}",
333 other
334 ),
335 }
336}
337
338pub fn inline_typedef(typedef: TypeDef) -> TypeDef {
344 match typedef {
345 TypeDef::Named { def, .. } => *def,
346 other => other,
347 }
348}
349
350use std::collections::{HashSet, VecDeque};
355
356#[derive(Debug, Default)]
377pub struct TypeRegistry {
378 types: HashMap<String, TypeDef>,
380 registration_order: Vec<String>,
382}
383
384impl TypeRegistry {
385 pub fn new() -> Self {
387 Self::default()
388 }
389
390 pub fn register<T: TypeScript>(&mut self) {
395 let typedef = T::typescript();
396 self.add_typedef(typedef);
397 }
398
399 pub fn add_typedef(&mut self, typedef: TypeDef) {
401 self.extract_named_types(&typedef);
402 }
403
404 fn extract_named_types(&mut self, typedef: &TypeDef) {
406 match typedef {
407 TypeDef::Named { name, def } => {
408 if !self.types.contains_key(name) {
409 self.types.insert(name.clone(), typedef.clone());
410 self.registration_order.push(name.clone());
411 self.extract_named_types(def);
413 }
414 }
415 TypeDef::Array(inner) => self.extract_named_types(inner),
416 TypeDef::Tuple(items) => {
417 for item in items {
418 self.extract_named_types(item);
419 }
420 }
421 TypeDef::Object(fields) => {
422 for field in fields {
423 self.extract_named_types(&field.ty);
424 }
425 }
426 TypeDef::Union(items) | TypeDef::Intersection(items) => {
427 for item in items {
428 self.extract_named_types(item);
429 }
430 }
431 TypeDef::Record { key, value } => {
432 self.extract_named_types(key);
433 self.extract_named_types(value);
434 }
435 TypeDef::Function { params, return_type } => {
436 for param in params {
437 self.extract_named_types(¶m.ty);
438 }
439 self.extract_named_types(return_type);
440 }
441 TypeDef::Generic { args, .. } => {
442 for arg in args {
443 self.extract_named_types(arg);
444 }
445 }
446 TypeDef::Primitive(_) | TypeDef::Ref(_) | TypeDef::Literal(_) => {}
448 }
449 }
450
451 pub fn len(&self) -> usize {
453 self.types.len()
454 }
455
456 pub fn is_empty(&self) -> bool {
458 self.types.is_empty()
459 }
460
461 pub fn type_names(&self) -> impl Iterator<Item = &str> {
463 self.types.keys().map(|s| s.as_str())
464 }
465
466 pub fn get(&self, name: &str) -> Option<&TypeDef> {
468 self.types.get(name)
469 }
470
471 fn get_dependencies(&self, typedef: &TypeDef) -> HashSet<String> {
473 let mut deps = HashSet::new();
474 self.collect_dependencies(typedef, &mut deps);
475 deps
476 }
477
478 fn collect_dependencies(&self, typedef: &TypeDef, deps: &mut HashSet<String>) {
480 match typedef {
481 TypeDef::Named { def, .. } => {
482 self.collect_dependencies(def, deps);
484 }
485 TypeDef::Ref(name) => {
486 if self.types.contains_key(name) {
487 deps.insert(name.clone());
488 }
489 }
490 TypeDef::Array(inner) => self.collect_dependencies(inner, deps),
491 TypeDef::Tuple(items) => {
492 for item in items {
493 self.collect_dependencies(item, deps);
494 }
495 }
496 TypeDef::Object(fields) => {
497 for field in fields {
498 self.collect_dependencies(&field.ty, deps);
499 }
500 }
501 TypeDef::Union(variants) => {
502 for v in variants {
503 self.collect_dependencies(v, deps);
504 }
505 }
506 TypeDef::Intersection(types) => {
507 for t in types {
508 self.collect_dependencies(t, deps);
509 }
510 }
511 TypeDef::Record { key, value } => {
512 self.collect_dependencies(key, deps);
513 self.collect_dependencies(value, deps);
514 }
515 TypeDef::Function { params, return_type } => {
516 for param in params {
517 self.collect_dependencies(¶m.ty, deps);
518 }
519 self.collect_dependencies(return_type, deps);
520 }
521 TypeDef::Generic { args, .. } => {
522 for arg in args {
523 self.collect_dependencies(arg, deps);
524 }
525 }
526 TypeDef::Primitive(_) | TypeDef::Literal(_) => {}
527 }
528 }
529
530 pub fn sorted_types(&self) -> Vec<&str> {
534 let mut in_degree: HashMap<&str, usize> = HashMap::new();
536 let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
537
538 for name in self.types.keys() {
540 in_degree.insert(name.as_str(), 0);
541 dependents.insert(name.as_str(), Vec::new());
542 }
543
544 for (name, typedef) in &self.types {
546 let deps = self.get_dependencies(typedef);
547 for dep in deps {
548 if let Some(dep_name) = self.types.get_key_value(&dep) {
549 *in_degree.get_mut(name.as_str()).unwrap() += 1;
550 dependents.get_mut(dep_name.0.as_str()).unwrap().push(name.as_str());
551 }
552 }
553 }
554
555 let mut queue: VecDeque<&str> = VecDeque::new();
557 let mut result: Vec<&str> = Vec::new();
558
559 for (name, °ree) in &in_degree {
561 if degree == 0 {
562 queue.push_back(name);
563 }
564 }
565
566 let mut initial: Vec<_> = queue.drain(..).collect();
568 initial.sort_by_key(|name| {
569 self.registration_order.iter().position(|n| n == *name).unwrap_or(usize::MAX)
570 });
571 queue.extend(initial);
572
573 while let Some(name) = queue.pop_front() {
574 result.push(name);
575
576 let mut deps: Vec<_> = dependents.get(name).map(|v| v.as_slice()).unwrap_or(&[]).to_vec();
578 deps.sort_by_key(|n| {
579 self.registration_order.iter().position(|name| name == *n).unwrap_or(usize::MAX)
580 });
581
582 for dependent in deps {
583 let degree = in_degree.get_mut(dependent).unwrap();
584 *degree -= 1;
585 if *degree == 0 {
586 queue.push_back(dependent);
587 }
588 }
589 }
590
591 if result.len() < self.types.len() {
594 for name in &self.registration_order {
595 if !result.contains(&name.as_str()) {
596 result.push(name.as_str());
597 }
598 }
599 }
600
601 result
602 }
603
604 pub fn render(&self) -> String {
608 let sorted = self.sorted_types();
609 let mut output = String::new();
610
611 output.push_str("// Generated by ferrotype\n");
613 output.push_str("// Do not edit manually\n\n");
614
615 for name in sorted {
616 if let Some(typedef) = self.types.get(name) {
617 output.push_str(&typedef.render_declaration());
618 output.push_str("\n\n");
619 }
620 }
621
622 output.trim_end().to_string() + "\n"
624 }
625
626 pub fn render_exported(&self) -> String {
628 let sorted = self.sorted_types();
629 let mut output = String::new();
630
631 output.push_str("// Generated by ferrotype\n");
633 output.push_str("// Do not edit manually\n\n");
634
635 for name in sorted {
636 if let Some(typedef) = self.types.get(name) {
637 if let TypeDef::Named { name, def } = typedef {
638 output.push_str(&format!("export type {} = {};\n\n", name, def.render()));
639 }
640 }
641 }
642
643 output.trim_end().to_string() + "\n"
645 }
646
647 pub fn clear(&mut self) {
649 self.types.clear();
650 self.registration_order.clear();
651 }
652}
653
654impl TypeScript for () {
659 fn typescript() -> TypeDef {
660 TypeDef::Primitive(Primitive::Void)
661 }
662}
663
664impl TypeScript for bool {
665 fn typescript() -> TypeDef {
666 TypeDef::Primitive(Primitive::Boolean)
667 }
668}
669
670impl TypeScript for String {
671 fn typescript() -> TypeDef {
672 TypeDef::Primitive(Primitive::String)
673 }
674}
675
676impl TypeScript for &str {
677 fn typescript() -> TypeDef {
678 TypeDef::Primitive(Primitive::String)
679 }
680}
681
682impl TypeScript for char {
683 fn typescript() -> TypeDef {
684 TypeDef::Primitive(Primitive::String)
685 }
686}
687
688macro_rules! impl_typescript_number {
689 ($($t:ty),*) => {
690 $(
691 impl TypeScript for $t {
692 fn typescript() -> TypeDef {
693 TypeDef::Primitive(Primitive::Number)
694 }
695 }
696 )*
697 };
698}
699
700impl_typescript_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
701
702impl TypeScript for i128 {
704 fn typescript() -> TypeDef {
705 TypeDef::Primitive(Primitive::BigInt)
706 }
707}
708
709impl TypeScript for u128 {
710 fn typescript() -> TypeDef {
711 TypeDef::Primitive(Primitive::BigInt)
712 }
713}
714
715impl<T: TypeScript> TypeScript for Option<T> {
720 fn typescript() -> TypeDef {
721 TypeDef::Union(vec![T::typescript(), TypeDef::Primitive(Primitive::Null)])
722 }
723}
724
725impl<T: TypeScript> TypeScript for Vec<T> {
726 fn typescript() -> TypeDef {
727 TypeDef::Array(Box::new(T::typescript()))
728 }
729}
730
731impl<T: TypeScript> TypeScript for Box<T> {
732 fn typescript() -> TypeDef {
733 T::typescript()
734 }
735}
736
737impl<T: TypeScript> TypeScript for std::rc::Rc<T> {
738 fn typescript() -> TypeDef {
739 T::typescript()
740 }
741}
742
743impl<T: TypeScript> TypeScript for std::sync::Arc<T> {
744 fn typescript() -> TypeDef {
745 T::typescript()
746 }
747}
748
749impl<T: TypeScript> TypeScript for std::cell::RefCell<T> {
750 fn typescript() -> TypeDef {
751 T::typescript()
752 }
753}
754
755impl<T: TypeScript> TypeScript for std::cell::Cell<T> {
756 fn typescript() -> TypeDef {
757 T::typescript()
758 }
759}
760
761impl<K: TypeScript, V: TypeScript> TypeScript for HashMap<K, V> {
762 fn typescript() -> TypeDef {
763 TypeDef::Record {
764 key: Box::new(K::typescript()),
765 value: Box::new(V::typescript()),
766 }
767 }
768}
769
770impl<K: TypeScript, V: TypeScript> TypeScript for std::collections::BTreeMap<K, V> {
771 fn typescript() -> TypeDef {
772 TypeDef::Record {
773 key: Box::new(K::typescript()),
774 value: Box::new(V::typescript()),
775 }
776 }
777}
778
779impl<T: TypeScript, E: TypeScript> TypeScript for Result<T, E> {
780 fn typescript() -> TypeDef {
781 TypeDef::Union(vec![
782 TypeDef::Object(vec![
783 Field::new("ok", TypeDef::Literal(Literal::Boolean(true))),
784 Field::new("value", T::typescript()),
785 ]),
786 TypeDef::Object(vec![
787 Field::new("ok", TypeDef::Literal(Literal::Boolean(false))),
788 Field::new("error", E::typescript()),
789 ]),
790 ])
791 }
792}
793
794impl<A: TypeScript> TypeScript for (A,) {
799 fn typescript() -> TypeDef {
800 TypeDef::Tuple(vec![A::typescript()])
801 }
802}
803
804impl<A: TypeScript, B: TypeScript> TypeScript for (A, B) {
805 fn typescript() -> TypeDef {
806 TypeDef::Tuple(vec![A::typescript(), B::typescript()])
807 }
808}
809
810impl<A: TypeScript, B: TypeScript, C: TypeScript> TypeScript for (A, B, C) {
811 fn typescript() -> TypeDef {
812 TypeDef::Tuple(vec![A::typescript(), B::typescript(), C::typescript()])
813 }
814}
815
816impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript> TypeScript for (A, B, C, D) {
817 fn typescript() -> TypeDef {
818 TypeDef::Tuple(vec![
819 A::typescript(),
820 B::typescript(),
821 C::typescript(),
822 D::typescript(),
823 ])
824 }
825}
826
827impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript> TypeScript
828 for (A, B, C, D, E)
829{
830 fn typescript() -> TypeDef {
831 TypeDef::Tuple(vec![
832 A::typescript(),
833 B::typescript(),
834 C::typescript(),
835 D::typescript(),
836 E::typescript(),
837 ])
838 }
839}
840
841impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript, F: TypeScript>
842 TypeScript for (A, B, C, D, E, F)
843{
844 fn typescript() -> TypeDef {
845 TypeDef::Tuple(vec![
846 A::typescript(),
847 B::typescript(),
848 C::typescript(),
849 D::typescript(),
850 E::typescript(),
851 F::typescript(),
852 ])
853 }
854}
855
856#[cfg(test)]
861mod tests {
862 use super::*;
863
864 #[test]
869 fn test_typedef_primitive_render() {
870 assert_eq!(TypeDef::Primitive(Primitive::String).render(), "string");
871 assert_eq!(TypeDef::Primitive(Primitive::Number).render(), "number");
872 assert_eq!(TypeDef::Primitive(Primitive::Boolean).render(), "boolean");
873 assert_eq!(TypeDef::Primitive(Primitive::Null).render(), "null");
874 assert_eq!(TypeDef::Primitive(Primitive::Undefined).render(), "undefined");
875 assert_eq!(TypeDef::Primitive(Primitive::Void).render(), "void");
876 assert_eq!(TypeDef::Primitive(Primitive::Never).render(), "never");
877 assert_eq!(TypeDef::Primitive(Primitive::Any).render(), "any");
878 assert_eq!(TypeDef::Primitive(Primitive::Unknown).render(), "unknown");
879 assert_eq!(TypeDef::Primitive(Primitive::BigInt).render(), "bigint");
880 }
881
882 #[test]
883 fn test_typedef_array_render() {
884 let arr = TypeDef::Array(Box::new(TypeDef::Primitive(Primitive::String)));
885 assert_eq!(arr.render(), "string[]");
886
887 let union_arr = TypeDef::Array(Box::new(TypeDef::Union(vec![
889 TypeDef::Primitive(Primitive::String),
890 TypeDef::Primitive(Primitive::Number),
891 ])));
892 assert_eq!(union_arr.render(), "(string | number)[]");
893 }
894
895 #[test]
896 fn test_typedef_tuple_render() {
897 let tuple = TypeDef::Tuple(vec![
898 TypeDef::Primitive(Primitive::String),
899 TypeDef::Primitive(Primitive::Number),
900 ]);
901 assert_eq!(tuple.render(), "[string, number]");
902 }
903
904 #[test]
905 fn test_typedef_object_render() {
906 let obj = TypeDef::Object(vec![
907 Field::new("name", TypeDef::Primitive(Primitive::String)),
908 Field::optional("age", TypeDef::Primitive(Primitive::Number)),
909 ]);
910 assert_eq!(obj.render(), "{ name: string; age?: number }");
911
912 let empty_obj = TypeDef::Object(vec![]);
913 assert_eq!(empty_obj.render(), "{}");
914 }
915
916 #[test]
917 fn test_typedef_object_readonly_field() {
918 let obj = TypeDef::Object(vec![
919 Field::new("id", TypeDef::Primitive(Primitive::String)).readonly(),
920 ]);
921 assert_eq!(obj.render(), "{ readonly id: string }");
922 }
923
924 #[test]
925 fn test_typedef_union_render() {
926 let union = TypeDef::Union(vec![
927 TypeDef::Primitive(Primitive::String),
928 TypeDef::Primitive(Primitive::Number),
929 TypeDef::Primitive(Primitive::Null),
930 ]);
931 assert_eq!(union.render(), "string | number | null");
932 }
933
934 #[test]
935 fn test_typedef_intersection_render() {
936 let intersection = TypeDef::Intersection(vec![
937 TypeDef::Ref("Base".into()),
938 TypeDef::Object(vec![
939 Field::new("extra", TypeDef::Primitive(Primitive::String)),
940 ]),
941 ]);
942 assert_eq!(intersection.render(), "Base & { extra: string }");
943 }
944
945 #[test]
946 fn test_typedef_record_render() {
947 let record = TypeDef::Record {
948 key: Box::new(TypeDef::Primitive(Primitive::String)),
949 value: Box::new(TypeDef::Primitive(Primitive::Number)),
950 };
951 assert_eq!(record.render(), "Record<string, number>");
952 }
953
954 #[test]
955 fn test_typedef_named_render() {
956 let named = TypeDef::Named {
957 name: "UserId".into(),
958 def: Box::new(TypeDef::Primitive(Primitive::String)),
959 };
960 assert_eq!(named.render(), "UserId");
962 assert_eq!(named.render_declaration(), "type UserId = string;");
964 }
965
966 #[test]
967 fn test_typedef_ref_render() {
968 let ref_type = TypeDef::Ref("User".into());
969 assert_eq!(ref_type.render(), "User");
970 }
971
972 #[test]
973 fn test_typedef_literal_render() {
974 assert_eq!(TypeDef::Literal(Literal::String("foo".into())).render(), "\"foo\"");
975 assert_eq!(TypeDef::Literal(Literal::Number(42.0)).render(), "42");
976 assert_eq!(TypeDef::Literal(Literal::Number(3.14)).render(), "3.14");
977 assert_eq!(TypeDef::Literal(Literal::Boolean(true)).render(), "true");
978 assert_eq!(TypeDef::Literal(Literal::Boolean(false)).render(), "false");
979 }
980
981 #[test]
982 fn test_typedef_literal_escaping() {
983 let lit = Literal::String("say \"hello\"".into());
984 assert_eq!(lit.render(), "\"say \\\"hello\\\"\"");
985 }
986
987 #[test]
988 fn test_typedef_function_render() {
989 let func = TypeDef::Function {
990 params: vec![
991 Field::new("name", TypeDef::Primitive(Primitive::String)),
992 Field::new("age", TypeDef::Primitive(Primitive::Number)),
993 ],
994 return_type: Box::new(TypeDef::Primitive(Primitive::Void)),
995 };
996 assert_eq!(func.render(), "(name: string, age: number) => void");
997 }
998
999 #[test]
1000 fn test_typedef_generic_render() {
1001 let generic = TypeDef::Generic {
1002 base: "Promise".into(),
1003 args: vec![TypeDef::Primitive(Primitive::String)],
1004 };
1005 assert_eq!(generic.render(), "Promise<string>");
1006
1007 let multi_generic = TypeDef::Generic {
1008 base: "Map".into(),
1009 args: vec![
1010 TypeDef::Primitive(Primitive::String),
1011 TypeDef::Primitive(Primitive::Number),
1012 ],
1013 };
1014 assert_eq!(multi_generic.render(), "Map<string, number>");
1015 }
1016
1017 #[test]
1018 fn test_typescript_trait_primitives() {
1019 assert_eq!(<()>::typescript().render(), "void");
1020 assert_eq!(bool::typescript().render(), "boolean");
1021 assert_eq!(String::typescript().render(), "string");
1022 assert_eq!(i32::typescript().render(), "number");
1023 assert_eq!(f64::typescript().render(), "number");
1024 assert_eq!(i128::typescript().render(), "bigint");
1025 assert_eq!(u128::typescript().render(), "bigint");
1026 }
1027
1028 #[test]
1029 fn test_typescript_trait_option() {
1030 let opt = <Option<String>>::typescript();
1031 assert_eq!(opt.render(), "string | null");
1032 }
1033
1034 #[test]
1035 fn test_typescript_trait_vec() {
1036 let vec_type = <Vec<i32>>::typescript();
1037 assert_eq!(vec_type.render(), "number[]");
1038 }
1039
1040 #[test]
1041 fn test_typescript_trait_hashmap() {
1042 let map = <HashMap<String, i32>>::typescript();
1043 assert_eq!(map.render(), "Record<string, number>");
1044 }
1045
1046 #[test]
1047 fn test_typescript_trait_result() {
1048 let result = <Result<String, String>>::typescript();
1049 assert_eq!(
1050 result.render(),
1051 "{ ok: true; value: string } | { ok: false; error: string }"
1052 );
1053 }
1054
1055 #[test]
1056 fn test_typescript_trait_tuples() {
1057 assert_eq!(<(String,)>::typescript().render(), "[string]");
1058 assert_eq!(<(String, i32)>::typescript().render(), "[string, number]");
1059 assert_eq!(
1060 <(String, i32, bool)>::typescript().render(),
1061 "[string, number, boolean]"
1062 );
1063 }
1064
1065 #[test]
1066 fn test_typescript_trait_box() {
1067 assert_eq!(<Box<String>>::typescript().render(), "string");
1069 }
1070
1071 #[test]
1072 fn test_typedef_equality() {
1073 let a = TypeDef::Primitive(Primitive::String);
1074 let b = TypeDef::Primitive(Primitive::String);
1075 let c = TypeDef::Primitive(Primitive::Number);
1076 assert_eq!(a, b);
1077 assert_ne!(a, c);
1078 }
1079
1080 #[test]
1081 fn test_field_builder() {
1082 let field = Field::new("name", TypeDef::Primitive(Primitive::String));
1083 assert!(!field.optional);
1084 assert!(!field.readonly);
1085
1086 let opt_field = Field::optional("name", TypeDef::Primitive(Primitive::String));
1087 assert!(opt_field.optional);
1088
1089 let readonly_field = Field::new("id", TypeDef::Primitive(Primitive::String)).readonly();
1090 assert!(readonly_field.readonly);
1091 }
1092
1093 #[test]
1098 fn test_registry_new() {
1099 let registry = TypeRegistry::new();
1100 assert!(registry.is_empty());
1101 assert_eq!(registry.len(), 0);
1102 }
1103
1104 #[test]
1105 fn test_registry_add_typedef() {
1106 let mut registry = TypeRegistry::new();
1107
1108 let user_type = TypeDef::Named {
1109 name: "User".to_string(),
1110 def: Box::new(TypeDef::Object(vec![
1111 Field::new("id", TypeDef::Primitive(Primitive::String)),
1112 Field::new("name", TypeDef::Primitive(Primitive::String)),
1113 ])),
1114 };
1115
1116 registry.add_typedef(user_type);
1117
1118 assert_eq!(registry.len(), 1);
1119 assert!(registry.get("User").is_some());
1120 }
1121
1122 #[test]
1123 fn test_registry_deduplication() {
1124 let mut registry = TypeRegistry::new();
1125
1126 let user_type = TypeDef::Named {
1127 name: "User".to_string(),
1128 def: Box::new(TypeDef::Primitive(Primitive::String)),
1129 };
1130
1131 registry.add_typedef(user_type.clone());
1132 registry.add_typedef(user_type);
1133
1134 assert_eq!(registry.len(), 1);
1136 }
1137
1138 #[test]
1139 fn test_registry_extracts_nested_types() {
1140 let mut registry = TypeRegistry::new();
1141
1142 let user_id = TypeDef::Named {
1144 name: "UserId".to_string(),
1145 def: Box::new(TypeDef::Primitive(Primitive::String)),
1146 };
1147
1148 let user = TypeDef::Named {
1150 name: "User".to_string(),
1151 def: Box::new(TypeDef::Object(vec![
1152 Field::new("id", TypeDef::Ref("UserId".to_string())),
1153 Field::new("name", TypeDef::Primitive(Primitive::String)),
1154 ])),
1155 };
1156
1157 let post_type = TypeDef::Named {
1159 name: "Post".to_string(),
1160 def: Box::new(TypeDef::Object(vec![
1161 Field::new("title", TypeDef::Primitive(Primitive::String)),
1162 Field::new("author", user),
1163 ])),
1164 };
1165
1166 registry.add_typedef(post_type);
1167 registry.add_typedef(user_id);
1168
1169 assert_eq!(registry.len(), 3);
1171 assert!(registry.get("Post").is_some());
1172 assert!(registry.get("User").is_some());
1173 assert!(registry.get("UserId").is_some());
1174 }
1175
1176 #[test]
1177 fn test_registry_render() {
1178 let mut registry = TypeRegistry::new();
1179
1180 let user_type = TypeDef::Named {
1181 name: "User".to_string(),
1182 def: Box::new(TypeDef::Object(vec![
1183 Field::new("id", TypeDef::Primitive(Primitive::String)),
1184 Field::new("name", TypeDef::Primitive(Primitive::String)),
1185 ])),
1186 };
1187
1188 registry.add_typedef(user_type);
1189
1190 let output = registry.render();
1191 assert!(output.contains("// Generated by ferrotype"));
1192 assert!(output.contains("type User = { id: string; name: string };"));
1193 }
1194
1195 #[test]
1196 fn test_registry_render_exported() {
1197 let mut registry = TypeRegistry::new();
1198
1199 let user_type = TypeDef::Named {
1200 name: "User".to_string(),
1201 def: Box::new(TypeDef::Primitive(Primitive::String)),
1202 };
1203
1204 registry.add_typedef(user_type);
1205
1206 let output = registry.render_exported();
1207 assert!(output.contains("export type User = string;"));
1208 }
1209
1210 #[test]
1211 fn test_registry_dependency_order() {
1212 let mut registry = TypeRegistry::new();
1213
1214 let user_id = TypeDef::Named {
1216 name: "UserId".to_string(),
1217 def: Box::new(TypeDef::Primitive(Primitive::String)),
1218 };
1219
1220 let user = TypeDef::Named {
1222 name: "User".to_string(),
1223 def: Box::new(TypeDef::Object(vec![
1224 Field::new("id", TypeDef::Ref("UserId".to_string())),
1225 Field::new("name", TypeDef::Primitive(Primitive::String)),
1226 ])),
1227 };
1228
1229 registry.add_typedef(user);
1231 registry.add_typedef(user_id);
1232
1233 let sorted = registry.sorted_types();
1234
1235 let user_id_pos = sorted.iter().position(|&n| n == "UserId").unwrap();
1237 let user_pos = sorted.iter().position(|&n| n == "User").unwrap();
1238 assert!(user_id_pos < user_pos, "UserId should come before User");
1239 }
1240
1241 #[test]
1242 fn test_registry_clear() {
1243 let mut registry = TypeRegistry::new();
1244
1245 let user_type = TypeDef::Named {
1246 name: "User".to_string(),
1247 def: Box::new(TypeDef::Primitive(Primitive::String)),
1248 };
1249
1250 registry.add_typedef(user_type);
1251 assert_eq!(registry.len(), 1);
1252
1253 registry.clear();
1254 assert!(registry.is_empty());
1255 }
1256
1257 #[test]
1258 fn test_registry_type_names() {
1259 let mut registry = TypeRegistry::new();
1260
1261 registry.add_typedef(TypeDef::Named {
1262 name: "Alpha".to_string(),
1263 def: Box::new(TypeDef::Primitive(Primitive::String)),
1264 });
1265 registry.add_typedef(TypeDef::Named {
1266 name: "Beta".to_string(),
1267 def: Box::new(TypeDef::Primitive(Primitive::Number)),
1268 });
1269
1270 let names: Vec<_> = registry.type_names().collect();
1271 assert_eq!(names.len(), 2);
1272 assert!(names.contains(&"Alpha"));
1273 assert!(names.contains(&"Beta"));
1274 }
1275
1276 #[test]
1277 fn test_registry_complex_dependencies() {
1278 let mut registry = TypeRegistry::new();
1279
1280 let c = TypeDef::Named {
1282 name: "C".to_string(),
1283 def: Box::new(TypeDef::Primitive(Primitive::String)),
1284 };
1285
1286 let b = TypeDef::Named {
1287 name: "B".to_string(),
1288 def: Box::new(TypeDef::Object(vec![
1289 Field::new("c", TypeDef::Ref("C".to_string())),
1290 ])),
1291 };
1292
1293 let a = TypeDef::Named {
1294 name: "A".to_string(),
1295 def: Box::new(TypeDef::Object(vec![
1296 Field::new("b", TypeDef::Ref("B".to_string())),
1297 ])),
1298 };
1299
1300 registry.add_typedef(a);
1302 registry.add_typedef(b);
1303 registry.add_typedef(c);
1304
1305 let sorted = registry.sorted_types();
1306
1307 let c_pos = sorted.iter().position(|&n| n == "C").unwrap();
1308 let b_pos = sorted.iter().position(|&n| n == "B").unwrap();
1309 let a_pos = sorted.iter().position(|&n| n == "A").unwrap();
1310
1311 assert!(c_pos < b_pos, "C should come before B");
1312 assert!(b_pos < a_pos, "B should come before A");
1313 }
1314}