1#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc(
4 html_logo_url = "https://github.com/oscartbeaumont/specta/raw/main/.github/logo-128.png",
5 html_favicon_url = "https://github.com/oscartbeaumont/specta/raw/main/.github/logo-128.png"
6)]
7
8use std::borrow::Cow;
9use std::fmt::Write;
10
11pub mod comments;
12mod context;
13mod error;
14pub mod formatter;
15pub mod js_doc;
16mod reserved_terms;
17mod typescript;
18
19pub use context::*;
20pub use error::*;
21use reserved_terms::*;
22pub use typescript::*;
23
24use specta::datatype::{
25 DataType, DeprecatedType, EnumRepr, EnumType, EnumVariant, EnumVariants, FunctionResultVariant,
26 LiteralType, NamedDataType, PrimitiveType, StructFields, StructType, TupleType,
27};
28use specta::{
29 internal::{detect_duplicate_type_names, skip_fields, skip_fields_named, NonSkipField},
30 Generics, NamedType, Type, TypeCollection,
31};
32use specta_serde::is_valid_ty;
33
34#[allow(missing_docs)]
35pub type Result<T> = std::result::Result<T, ExportError>;
36
37pub(crate) type Output = Result<String>;
38
39pub fn export_ref<T: NamedType>(_: &T, conf: &Typescript) -> Output {
43 export::<T>(conf)
44}
45
46pub fn export<T: NamedType>(conf: &Typescript) -> Output {
50 let mut type_map = TypeCollection::default();
51 let named_data_type = T::definition_named_data_type(&mut type_map);
52 is_valid_ty(&named_data_type.inner, &type_map)?;
53 let result = export_named_datatype(conf, &named_data_type, &type_map);
54
55 if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() {
56 return Err(ExportError::DuplicateTypeName(ty_name, l0, l1));
57 }
58
59 result
60}
61
62pub fn inline_ref<T: Type>(_: &T, conf: &Typescript) -> Output {
66 inline::<T>(conf)
67}
68
69pub fn inline<T: Type>(conf: &Typescript) -> Output {
73 let mut type_map = TypeCollection::default();
74 let ty = T::inline(&mut type_map, Generics::NONE);
75 is_valid_ty(&ty, &type_map)?;
76 let result = datatype(conf, &FunctionResultVariant::Value(ty.clone()), &type_map);
77
78 if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() {
79 return Err(ExportError::DuplicateTypeName(ty_name, l0, l1));
80 }
81
82 result
83}
84
85pub fn export_named_datatype(
89 conf: &Typescript,
90 typ: &NamedDataType,
91 type_map: &TypeCollection,
92) -> Output {
93 is_valid_ty(&typ.inner, type_map)?;
96 export_datatype_inner(
97 ExportContext {
98 cfg: conf,
99 path: vec![],
100 is_export: true,
101 },
102 typ,
103 type_map,
104 )
105}
106
107#[allow(clippy::ptr_arg)]
108fn inner_comments(
109 ctx: ExportContext,
110 deprecated: Option<&DeprecatedType>,
111 docs: &Cow<'static, str>,
112 other: String,
113 start_with_newline: bool,
114) -> String {
115 if !ctx.is_export {
116 return other;
117 }
118
119 let comments = ctx
120 .cfg
121 .comment_exporter
122 .map(|v| v(CommentFormatterArgs { docs, deprecated }))
123 .unwrap_or_default();
124
125 let prefix = match start_with_newline && !comments.is_empty() {
126 true => "\n",
127 false => "",
128 };
129
130 format!("{prefix}{comments}{other}")
131}
132
133fn export_datatype_inner(
134 ctx: ExportContext,
135 typ: &NamedDataType,
136 type_map: &TypeCollection,
137) -> Output {
138 let name = typ.name();
139 let docs = typ.docs();
140 let ext = typ.ext();
141 let deprecated = typ.deprecated();
142 let item = &typ.inner;
143
144 let ctx = ctx.with(
145 ext.clone()
146 .map(|v| PathItem::TypeExtended(name.clone(), *v.impl_location()))
147 .unwrap_or_else(|| PathItem::Type(name.clone())),
148 );
149 let name = sanitise_type_name(ctx.clone(), NamedLocation::Type, name)?;
150
151 let generics = item
152 .generics()
153 .filter(|generics| !generics.is_empty())
154 .map(|generics| format!("<{}>", generics.join(", ")))
155 .unwrap_or_default();
156
157 let mut inline_ts = String::new();
158 datatype_inner(
159 ctx.clone(),
160 &FunctionResultVariant::Value((typ.inner).clone()),
161 type_map,
162 &mut inline_ts,
163 )?;
164
165 Ok(inner_comments(
166 ctx,
167 deprecated,
168 docs,
169 format!("export type {name}{generics} = {inline_ts}"),
170 false,
171 ))
172}
173
174pub fn datatype(
178 conf: &Typescript,
179 typ: &FunctionResultVariant,
180 type_map: &TypeCollection,
181) -> Output {
182 let mut s = String::new();
185 datatype_inner(
186 ExportContext {
187 cfg: conf,
188 path: vec![],
189 is_export: false,
190 },
191 typ,
192 type_map,
193 &mut s,
194 )
195 .map(|_| s)
196}
197
198macro_rules! primitive_def {
199 ($($t:ident)+) => {
200 $(PrimitiveType::$t)|+
201 }
202}
203
204pub(crate) fn datatype_inner(
205 ctx: ExportContext,
206 typ: &FunctionResultVariant,
207 type_map: &TypeCollection,
208 s: &mut String,
209) -> Result<()> {
210 let typ = match typ {
211 FunctionResultVariant::Value(t) => t,
212 FunctionResultVariant::Result(t, e) => {
213 let mut variants = vec![
214 {
215 let mut v = String::new();
216 datatype_inner(
217 ctx.clone(),
218 &FunctionResultVariant::Value(t.clone()),
219 type_map,
220 &mut v,
221 )?;
222 v
223 },
224 {
225 let mut v = String::new();
226 datatype_inner(
227 ctx,
228 &FunctionResultVariant::Value(e.clone()),
229 type_map,
230 &mut v,
231 )?;
232 v
233 },
234 ];
235 variants.dedup();
236 s.push_str(&variants.join(" | "));
237 return Ok(());
238 }
239 };
240
241 Ok(match &typ {
242 DataType::Any => s.push_str(ANY),
243 DataType::Unknown => s.push_str(UNKNOWN),
244 DataType::Primitive(p) => {
245 let ctx = ctx.with(PathItem::Type(p.to_rust_str().into()));
246 let str = match p {
247 primitive_def!(i8 i16 i32 u8 u16 u32 f32 f64) => NUMBER,
248 primitive_def!(usize isize i64 u64 i128 u128) => match ctx.cfg.bigint {
249 BigIntExportBehavior::String => STRING,
250 BigIntExportBehavior::Number => NUMBER,
251 BigIntExportBehavior::BigInt => BIGINT,
252 BigIntExportBehavior::Fail => {
253 return Err(ExportError::BigIntForbidden(ctx.export_path()));
254 }
255 BigIntExportBehavior::FailWithReason(reason) => {
256 return Err(ExportError::Other(ctx.export_path(), reason.to_owned()))
257 }
258 },
259 primitive_def!(String char) => STRING,
260 primitive_def!(bool) => BOOLEAN,
261 };
262
263 s.push_str(str);
264 }
265 DataType::Literal(literal) => match literal {
266 LiteralType::i8(v) => write!(s, "{v}")?,
267 LiteralType::i16(v) => write!(s, "{v}")?,
268 LiteralType::i32(v) => write!(s, "{v}")?,
269 LiteralType::u8(v) => write!(s, "{v}")?,
270 LiteralType::u16(v) => write!(s, "{v}")?,
271 LiteralType::u32(v) => write!(s, "{v}")?,
272 LiteralType::f32(v) => write!(s, "{v}")?,
273 LiteralType::f64(v) => write!(s, "{v}")?,
274 LiteralType::bool(v) => write!(s, "{v}")?,
275 LiteralType::String(v) => write!(s, r#""{v}""#)?,
276 LiteralType::char(v) => write!(s, r#""{v}""#)?,
277 LiteralType::None => s.write_str(NULL)?,
278 _ => unreachable!(),
279 },
280 DataType::Nullable(def) => {
281 datatype_inner(
282 ctx,
283 &FunctionResultVariant::Value((**def).clone()),
284 type_map,
285 s,
286 )?;
287
288 let or_null = format!(" | {NULL}");
289 if !s.ends_with(&or_null) {
290 s.push_str(&or_null);
291 }
292 }
293 DataType::Map(def) => {
294 s.push_str("Partial<{ [key in ");
297 datatype_inner(
298 ctx.clone(),
299 &FunctionResultVariant::Value(def.key_ty().clone()),
300 type_map,
301 s,
302 )?;
303 s.push_str("]: ");
304 datatype_inner(
305 ctx.clone(),
306 &FunctionResultVariant::Value(def.value_ty().clone()),
307 type_map,
308 s,
309 )?;
310 s.push_str(" }>");
311 }
312 DataType::List(def) => {
314 let mut dt = String::new();
315 datatype_inner(
316 ctx,
317 &FunctionResultVariant::Value(def.ty().clone()),
318 type_map,
319 &mut dt,
320 )?;
321
322 let dt = if (dt.contains(' ') && !dt.ends_with('}'))
323 || (dt.contains(' ') && (dt.contains('&') || dt.contains('|')))
326 {
327 format!("({dt})")
328 } else {
329 dt
330 };
331
332 if let Some(length) = def.length() {
333 s.push('[');
334
335 for n in 0..length {
336 if n != 0 {
337 s.push_str(", ");
338 }
339
340 s.push_str(&dt);
341 }
342
343 s.push(']');
344 } else {
345 write!(s, "{dt}[]")?;
346 }
347 }
348 DataType::Struct(item) => struct_datatype(
349 ctx.with(
350 item.sid()
351 .and_then(|sid| type_map.get(*sid))
352 .and_then(|v| v.ext())
353 .map(|v| PathItem::TypeExtended(item.name().clone(), *v.impl_location()))
354 .unwrap_or_else(|| PathItem::Type(item.name().clone())),
355 ),
356 item.name(),
357 item,
358 type_map,
359 s,
360 )?,
361 DataType::Enum(item) => {
362 let mut ctx = ctx.clone();
363 let cfg = ctx.cfg.clone().bigint(BigIntExportBehavior::Number);
364 if item.skip_bigint_checks() {
365 ctx.cfg = &cfg;
366 }
367
368 enum_datatype(
369 ctx.with(PathItem::Variant(item.name().clone())),
370 item,
371 type_map,
372 s,
373 )?
374 }
375 DataType::Tuple(tuple) => s.push_str(&tuple_datatype(ctx, tuple, type_map)?),
376 DataType::Reference(reference) => match &reference.generics()[..] {
377 [] => s.push_str(&reference.name()),
378 generics => {
379 s.push_str(&reference.name());
380 s.push('<');
381
382 for (i, (_, v)) in generics.iter().enumerate() {
383 if i != 0 {
384 s.push_str(", ");
385 }
386
387 datatype_inner(
388 ctx.with(PathItem::Type(reference.name().clone())),
389 &FunctionResultVariant::Value(v.clone()),
390 type_map,
391 s,
392 )?;
393 }
394
395 s.push('>');
396 }
397 },
398 DataType::Generic(ident) => s.push_str(&ident.to_string()),
399 })
400}
401
402fn unnamed_fields_datatype(
404 ctx: ExportContext,
405 fields: &[NonSkipField],
406 type_map: &TypeCollection,
407 s: &mut String,
408) -> Result<()> {
409 Ok(match fields {
410 [(field, ty)] => {
411 let mut v = String::new();
412 datatype_inner(
413 ctx.clone(),
414 &FunctionResultVariant::Value((*ty).clone()),
415 type_map,
416 &mut v,
417 )?;
418 s.push_str(&inner_comments(
419 ctx,
420 field.deprecated(),
421 field.docs(),
422 v,
423 true,
424 ));
425 }
426 fields => {
427 s.push('[');
428
429 for (i, (field, ty)) in fields.iter().enumerate() {
430 if i != 0 {
431 s.push_str(", ");
432 }
433
434 let mut v = String::new();
435 datatype_inner(
436 ctx.clone(),
437 &FunctionResultVariant::Value((*ty).clone()),
438 type_map,
439 &mut v,
440 )?;
441 s.push_str(&inner_comments(
442 ctx.clone(),
443 field.deprecated(),
444 field.docs(),
445 v,
446 true,
447 ));
448 }
449
450 s.push(']');
451 }
452 })
453}
454
455fn tuple_datatype(ctx: ExportContext, tuple: &TupleType, type_map: &TypeCollection) -> Output {
456 match &tuple.elements()[..] {
457 [] => Ok(NULL.to_string()),
458 tys => Ok(format!(
459 "[{}]",
460 tys.iter()
461 .map(|v| {
462 let mut s = String::new();
463 datatype_inner(
464 ctx.clone(),
465 &FunctionResultVariant::Value(v.clone()),
466 type_map,
467 &mut s,
468 )
469 .map(|_| s)
470 })
471 .collect::<Result<Vec<_>>>()?
472 .join(", ")
473 )),
474 }
475}
476
477fn struct_datatype(
478 ctx: ExportContext,
479 key: &str,
480 strct: &StructType,
481 type_map: &TypeCollection,
482 s: &mut String,
483) -> Result<()> {
484 Ok(match &strct.fields() {
485 StructFields::Unit => s.push_str(NULL),
486 StructFields::Unnamed(unnamed) => unnamed_fields_datatype(
487 ctx,
488 &skip_fields(unnamed.fields()).collect::<Vec<_>>(),
489 type_map,
490 s,
491 )?,
492 StructFields::Named(named) => {
493 let fields = skip_fields_named(named.fields()).collect::<Vec<_>>();
494
495 if fields.is_empty() {
496 return Ok(match named.tag().as_ref() {
497 Some(tag) => write!(s, r#"{{ "{tag}": "{key}" }}"#)?,
498 None => write!(s, "Record<{STRING}, {NEVER}>")?,
499 });
500 }
501
502 let (flattened, non_flattened): (Vec<_>, Vec<_>) =
503 fields.iter().partition(|(_, (f, _))| f.flatten());
504
505 let mut field_sections = flattened
506 .into_iter()
507 .map(|(key, (field, ty))| {
508 let mut s = String::new();
509 datatype_inner(
510 ctx.with(PathItem::Field(key.clone())),
511 &FunctionResultVariant::Value(ty.clone()),
512 type_map,
513 &mut s,
514 )
515 .map(|_| {
516 inner_comments(
517 ctx.clone(),
518 field.deprecated(),
519 field.docs(),
520 format!("({s})"),
521 true,
522 )
523 })
524 })
525 .collect::<Result<Vec<_>>>()?;
526
527 let mut unflattened_fields = non_flattened
528 .into_iter()
529 .map(|(key, field_ref)| {
530 let (field, _) = field_ref;
531
532 let mut other = String::new();
533 object_field_to_ts(
534 ctx.with(PathItem::Field(key.clone())),
535 key.clone(),
536 field_ref,
537 type_map,
538 &mut other,
539 )?;
540
541 Ok(inner_comments(
542 ctx.clone(),
543 field.deprecated(),
544 field.docs(),
545 other,
546 true,
547 ))
548 })
549 .collect::<Result<Vec<_>>>()?;
550
551 if let Some(tag) = &named.tag() {
552 unflattened_fields.push(format!("{tag}: \"{key}\""));
553 }
554
555 if !unflattened_fields.is_empty() {
556 field_sections.push(format!("{{ {} }}", unflattened_fields.join("; ")));
557 }
558
559 s.push_str(&field_sections.join(" & "));
560 }
561 })
562}
563
564fn enum_variant_datatype(
565 ctx: ExportContext,
566 type_map: &TypeCollection,
567 name: Cow<'static, str>,
568 variant: &EnumVariant,
569) -> Result<Option<String>> {
570 match &variant.inner() {
571 EnumVariants::Unit => unreachable!("Unit enum variants have no type!"),
573 EnumVariants::Named(obj) => {
574 let mut fields = if let Some(tag) = &obj.tag() {
575 let sanitised_name = sanitise_key(name, true);
576 vec![format!("{tag}: {sanitised_name}")]
577 } else {
578 vec![]
579 };
580
581 fields.extend(
582 skip_fields_named(obj.fields())
583 .map(|(name, field_ref)| {
584 let (field, _) = field_ref;
585
586 let mut other = String::new();
587 object_field_to_ts(
588 ctx.with(PathItem::Field(name.clone())),
589 name.clone(),
590 field_ref,
591 type_map,
592 &mut other,
593 )?;
594
595 Ok(inner_comments(
596 ctx.clone(),
597 field.deprecated(),
598 field.docs(),
599 other,
600 true,
601 ))
602 })
603 .collect::<Result<Vec<_>>>()?,
604 );
605
606 Ok(Some(match &fields[..] {
607 [] => format!("Record<{STRING}, {NEVER}>").to_string(),
608 fields => format!("{{ {} }}", fields.join("; ")),
609 }))
610 }
611 EnumVariants::Unnamed(obj) => {
612 let fields = skip_fields(obj.fields())
613 .map(|(_, ty)| {
614 let mut s = String::new();
615 datatype_inner(
616 ctx.clone(),
617 &FunctionResultVariant::Value(ty.clone()),
618 type_map,
619 &mut s,
620 )
621 .map(|_| s)
622 })
623 .collect::<Result<Vec<_>>>()?;
624
625 Ok(match &fields[..] {
626 [] => {
627 if obj.fields().is_empty() {
629 Some("[]".to_string())
630 } else {
631 None
633 }
634 }
635 [field] if obj.fields().len() == 1 => Some(field.to_string()),
637 fields => Some(format!("[{}]", fields.join(", "))),
638 })
639 }
640 }
641}
642
643fn enum_datatype(
644 ctx: ExportContext,
645 e: &EnumType,
646 type_map: &TypeCollection,
647 s: &mut String,
648) -> Result<()> {
649 if e.variants().is_empty() {
650 return Ok(write!(s, "{NEVER}")?);
651 }
652
653 Ok(match &e.repr() {
654 EnumRepr::Untagged => {
655 let mut variants = e
656 .variants()
657 .iter()
658 .filter(|(_, variant)| !variant.skip())
659 .map(|(name, variant)| {
660 Ok(match variant.inner() {
661 EnumVariants::Unit => NULL.to_string(),
662 _ => inner_comments(
663 ctx.clone(),
664 variant.deprecated(),
665 variant.docs(),
666 enum_variant_datatype(
667 ctx.with(PathItem::Variant(name.clone())),
668 type_map,
669 name.clone(),
670 variant,
671 )?
672 .expect("Invalid Serde type"),
673 true,
674 ),
675 })
676 })
677 .collect::<Result<Vec<_>>>()?;
678 variants.dedup();
679 s.push_str(&variants.join(" | "));
680 }
681 repr => {
682 let mut variants = e
683 .variants()
684 .iter()
685 .filter(|(_, variant)| !variant.skip())
686 .map(|(variant_name, variant)| {
687 let sanitised_name = sanitise_key(variant_name.clone(), true);
688
689 Ok(inner_comments(
690 ctx.clone(),
691 variant.deprecated(),
692 variant.docs(),
693 match (repr, &variant.inner()) {
694 (EnumRepr::Untagged, _) => unreachable!(),
695 (EnumRepr::Internal { tag }, EnumVariants::Unit) => {
696 format!("{{ {tag}: {sanitised_name} }}")
697 }
698 (EnumRepr::Internal { tag }, EnumVariants::Unnamed(tuple)) => {
699 let fields = skip_fields(tuple.fields()).collect::<Vec<_>>();
700
701 let dont_join_ty = if tuple.fields().len() == 1 {
703 let (_, ty) = fields.first().expect("checked length above");
704 validate_type_for_tagged_intersection(
705 ctx.clone(),
706 (**ty).clone(),
707 type_map,
708 )?
709 } else {
710 false
711 };
712
713 let mut typ = String::new();
714
715 unnamed_fields_datatype(ctx.clone(), &fields, type_map, &mut typ)?;
716
717 if dont_join_ty {
718 format!("({{ {tag}: {sanitised_name} }})")
719 } else {
720 if typ.contains('|') {
722 typ = format!("({typ})");
723 }
724 format!("({{ {tag}: {sanitised_name} }} & {typ})")
725 }
726 }
727 (EnumRepr::Internal { tag }, EnumVariants::Named(obj)) => {
728 let mut fields = vec![format!("{tag}: {sanitised_name}")];
729
730 for (name, field) in skip_fields_named(obj.fields()) {
731 let mut other = String::new();
732 object_field_to_ts(
733 ctx.with(PathItem::Field(name.clone())),
734 name.clone(),
735 field,
736 type_map,
737 &mut other,
738 )?;
739 fields.push(other);
740 }
741
742 format!("{{ {} }}", fields.join("; "))
743 }
744 (EnumRepr::External, EnumVariants::Unit) => sanitised_name.to_string(),
745 (EnumRepr::External, _) => {
746 let ts_values = enum_variant_datatype(
747 ctx.with(PathItem::Variant(variant_name.clone())),
748 type_map,
749 variant_name.clone(),
750 variant,
751 )?;
752 let sanitised_name = sanitise_key(variant_name.clone(), false);
753
754 match ts_values {
755 Some(ts_values) => {
756 format!("{{ {sanitised_name}: {ts_values} }}")
757 }
758 None => format!(r#""{sanitised_name}""#),
759 }
760 }
761 (EnumRepr::Adjacent { tag, .. }, EnumVariants::Unit) => {
762 format!("{{ {tag}: {sanitised_name} }}")
763 }
764 (EnumRepr::Adjacent { tag, content }, _) => {
765 let ts_value = enum_variant_datatype(
766 ctx.with(PathItem::Variant(variant_name.clone())),
767 type_map,
768 variant_name.clone(),
769 variant,
770 )?;
771
772 let mut s = String::new();
773
774 s.push_str("{ ");
775
776 write!(s, "{tag}: {sanitised_name}")?;
777 if let Some(ts_value) = ts_value {
778 write!(s, "; {content}: {ts_value}")?;
779 }
780
781 s.push_str(" }");
782
783 s
784 }
785 },
786 true,
787 ))
788 })
789 .collect::<Result<Vec<_>>>()?;
790 variants.dedup();
791 s.push_str(&variants.join(" | "));
792 }
793 })
794}
795
796fn object_field_to_ts(
817 ctx: ExportContext,
818 key: Cow<'static, str>,
819 (field, ty): NonSkipField,
820 type_map: &TypeCollection,
821 s: &mut String,
822) -> Result<()> {
823 let field_name_safe = sanitise_key(key, false);
824
825 let (key, ty) = match field.optional() {
827 true => (format!("{field_name_safe}?").into(), ty),
828 false => (field_name_safe, ty),
829 };
830
831 let mut value = String::new();
832 datatype_inner(
833 ctx,
834 &FunctionResultVariant::Value(ty.clone()),
835 type_map,
836 &mut value,
837 )?;
838
839 Ok(write!(s, "{key}: {value}",)?)
840}
841
842fn sanitise_key<'a>(field_name: Cow<'static, str>, force_string: bool) -> Cow<'a, str> {
844 let valid = field_name
845 .chars()
846 .all(|c| c.is_alphanumeric() || c == '_' || c == '$')
847 && field_name
848 .chars()
849 .next()
850 .map(|first| !first.is_numeric())
851 .unwrap_or(true);
852
853 if force_string || !valid {
854 format!(r#""{field_name}""#).into()
855 } else {
856 field_name
857 }
858}
859
860pub(crate) fn sanitise_type_name(ctx: ExportContext, loc: NamedLocation, ident: &str) -> Output {
861 if let Some(name) = RESERVED_TYPE_NAMES.iter().find(|v| **v == ident) {
862 return Err(ExportError::ForbiddenName(loc, ctx.export_path(), name));
863 }
864
865 if let Some(first_char) = ident.chars().next() {
866 if !first_char.is_alphabetic() && first_char != '_' {
867 return Err(ExportError::InvalidName(
868 loc,
869 ctx.export_path(),
870 ident.to_string(),
871 ));
872 }
873 }
874
875 if ident
876 .find(|c: char| !c.is_alphanumeric() && c != '_')
877 .is_some()
878 {
879 return Err(ExportError::InvalidName(
880 loc,
881 ctx.export_path(),
882 ident.to_string(),
883 ));
884 }
885
886 Ok(ident.to_string())
887}
888
889fn validate_type_for_tagged_intersection(
890 ctx: ExportContext,
891 ty: DataType,
892 type_map: &TypeCollection,
893) -> Result<bool> {
894 match ty {
895 DataType::Any
896 | DataType::Unknown
897 | DataType::Primitive(_)
898 | DataType::Nullable(_)
900 | DataType::List(_)
901 | DataType::Map(_)
902 | DataType::Generic(_) => Ok(false),
903 DataType::Literal(v) => match v {
904 LiteralType::None => Ok(true),
905 _ => Ok(false),
906 },
907 DataType::Struct(v) => match v.fields() {
908 StructFields::Unit => Ok(true),
909 StructFields::Unnamed(_) => {
910 Err(ExportError::InvalidTaggedVariantContainingTupleStruct(
911 ctx.export_path()
912 ))
913 }
914 StructFields::Named(fields) => {
915 if fields.tag().is_none() && fields.fields().is_empty() {
917 return Ok(true);
918 }
919
920 Ok(false)
921 }
922 },
923 DataType::Enum(v) => {
924 match v.repr() {
925 EnumRepr::Untagged => {
926 Ok(v.variants().iter().any(|(_, v)| match &v.inner() {
927 EnumVariants::Unit => true,
929 EnumVariants::Named(v) => v.tag().is_none() && v.fields().is_empty(),
931 EnumVariants::Unnamed(_) => false,
932 }))
933 },
934 EnumRepr::Internal { .. } | EnumRepr::Adjacent { .. } | EnumRepr::External => Ok(false),
936 }
937 }
938 DataType::Tuple(v) => {
939 if v.elements().is_empty() {
941 return Ok(true);
942 }
943
944 Ok(false)
945 }
946 DataType::Reference(r) => validate_type_for_tagged_intersection(
947 ctx,
948 type_map
949 .get(r.sid())
950 .expect("TypeCollection should have been populated by now")
951 .inner
952 .clone(),
953 type_map,
954 ),
955 }
956}
957
958const ANY: &str = "any";
959const UNKNOWN: &str = "unknown";
960const NUMBER: &str = "number";
961const STRING: &str = "string";
962const BOOLEAN: &str = "boolean";
963const NULL: &str = "null";
964const NEVER: &str = "never";
965const BIGINT: &str = "bigint";