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::collections::HashSet;
9
10use thiserror::Error;
11
12use specta::{
13 datatype::{
14 DataType, EnumRepr, EnumType, EnumVariants, LiteralType, PrimitiveType, StructFields,
15 },
16 internal::{resolve_generics, skip_fields, skip_fields_named},
17 SpectaID, TypeCollection,
18};
19
20#[derive(Error, Debug, PartialEq)]
23pub enum SerdeError {
24 #[error("A map key must be a 'string' or 'number' type")]
25 InvalidMapKey,
26 #[error("#[specta(tag = \"...\")] cannot be used with tuple variants")]
27 InvalidInternallyTaggedEnum,
28 #[error("the usage of #[specta(skip)] means the type can't be serialized")]
29 InvalidUsageOfSkip,
30}
31
32pub fn is_valid_ty(dt: &DataType, type_map: &TypeCollection) -> Result<(), SerdeError> {
36 is_valid_ty_internal(dt, type_map, &mut Default::default())
37}
38
39fn is_valid_ty_internal(
40 dt: &DataType,
41 type_map: &TypeCollection,
42 checked_references: &mut HashSet<SpectaID>,
43) -> Result<(), SerdeError> {
44 match dt {
45 DataType::Nullable(ty) => is_valid_ty(ty, type_map)?,
46 DataType::Map(ty) => {
47 is_valid_map_key(ty.key_ty(), type_map)?;
48 is_valid_ty_internal(ty.value_ty(), type_map, checked_references)?;
49 }
50 DataType::Struct(ty) => match ty.fields() {
51 StructFields::Unit => {}
52 StructFields::Unnamed(ty) => {
53 for (_, ty) in skip_fields(ty.fields()) {
54 is_valid_ty_internal(ty, type_map, checked_references)?;
55 }
56 }
57 StructFields::Named(ty) => {
58 for (_, (_, ty)) in skip_fields_named(ty.fields()) {
59 is_valid_ty_internal(ty, type_map, checked_references)?;
60 }
61 }
62 },
63 DataType::Enum(ty) => {
64 validate_enum(ty, type_map)?;
65
66 for (_variant_name, variant) in ty.variants().iter() {
67 match &variant.inner() {
68 EnumVariants::Unit => {}
69 EnumVariants::Named(variant) => {
70 for (_, (_, ty)) in skip_fields_named(variant.fields()) {
71 is_valid_ty_internal(ty, type_map, checked_references)?;
72 }
73 }
74 EnumVariants::Unnamed(variant) => {
75 for (_, ty) in skip_fields(variant.fields()) {
76 is_valid_ty_internal(ty, type_map, checked_references)?;
77 }
78 }
79 }
80 }
81 }
82 DataType::Tuple(ty) => {
83 for ty in ty.elements() {
84 is_valid_ty_internal(ty, type_map, checked_references)?;
85 }
86 }
87 DataType::Reference(ty) => {
88 for (_, generic) in ty.generics() {
89 is_valid_ty_internal(generic, type_map, checked_references)?;
90 }
91
92 #[allow(clippy::panic)]
93 if !checked_references.contains(&ty.sid()) {
94 checked_references.insert(ty.sid());
95 let ty = type_map.get(ty.sid()).unwrap_or_else(|| {
96 panic!("Type '{}' was never populated.", ty.sid().type_name())
97 }); is_valid_ty_internal(&ty.inner, type_map, checked_references)?;
100 }
101 }
102 _ => {}
103 }
104
105 Ok(())
106}
107
108fn is_valid_map_key(key_ty: &DataType, type_map: &TypeCollection) -> Result<(), SerdeError> {
110 match key_ty {
111 DataType::Any => Ok(()),
112 DataType::Primitive(ty) => match ty {
113 PrimitiveType::i8
114 | PrimitiveType::i16
115 | PrimitiveType::i32
116 | PrimitiveType::i64
117 | PrimitiveType::i128
118 | PrimitiveType::isize
119 | PrimitiveType::u8
120 | PrimitiveType::u16
121 | PrimitiveType::u32
122 | PrimitiveType::u64
123 | PrimitiveType::u128
124 | PrimitiveType::usize
125 | PrimitiveType::f32
126 | PrimitiveType::f64
127 | PrimitiveType::String
128 | PrimitiveType::char => Ok(()),
129 _ => Err(SerdeError::InvalidMapKey),
130 },
131 DataType::Literal(ty) => match ty {
132 LiteralType::i8(_)
133 | LiteralType::i16(_)
134 | LiteralType::i32(_)
135 | LiteralType::u8(_)
136 | LiteralType::u16(_)
137 | LiteralType::u32(_)
138 | LiteralType::f32(_)
139 | LiteralType::f64(_)
140 | LiteralType::String(_)
141 | LiteralType::char(_) => Ok(()),
142 _ => Err(SerdeError::InvalidMapKey),
143 },
144 DataType::Enum(ty) => {
146 for (_variant_name, variant) in ty.variants() {
147 match &variant.inner() {
148 EnumVariants::Unit => {}
149 EnumVariants::Unnamed(item) => {
150 if item.fields().len() > 1 {
151 return Err(SerdeError::InvalidMapKey);
152 }
153
154 if *ty.repr() != EnumRepr::Untagged {
155 return Err(SerdeError::InvalidMapKey);
156 }
157 }
158 _ => return Err(SerdeError::InvalidMapKey),
159 }
160 }
161
162 Ok(())
163 }
164 DataType::Reference(r) => {
165 let ty = type_map.get(r.sid()).expect("Type was never populated"); is_valid_map_key(&resolve_generics(ty.inner.clone(), r.generics()), type_map)
168 }
169 _ => Err(SerdeError::InvalidMapKey),
170 }
171}
172
173fn validate_enum(e: &EnumType, type_map: &TypeCollection) -> Result<(), SerdeError> {
175 let valid_variants = e.variants().iter().filter(|(_, v)| !v.skip()).count();
177 if valid_variants == 0 && !e.variants().is_empty() {
178 return Err(SerdeError::InvalidUsageOfSkip);
179 }
180
181 if let EnumRepr::Internal { .. } = e.repr() {
183 validate_internally_tag_enum(e, type_map)?;
184 }
185
186 Ok(())
187}
188
189fn validate_internally_tag_enum(e: &EnumType, type_map: &TypeCollection) -> Result<(), SerdeError> {
191 for (_variant_name, variant) in e.variants() {
192 match &variant.inner() {
193 EnumVariants::Unit => {}
194 EnumVariants::Named(_) => {}
195 EnumVariants::Unnamed(item) => {
196 let mut fields = skip_fields(item.fields());
197
198 let Some(first_field) = fields.next() else {
199 continue;
200 };
201
202 if fields.next().is_some() {
203 return Err(SerdeError::InvalidInternallyTaggedEnum);
204 }
205
206 validate_internally_tag_enum_datatype(first_field.1, type_map)?;
207 }
208 }
209 }
210
211 Ok(())
212}
213
214fn validate_internally_tag_enum_datatype(
217 ty: &DataType,
218 type_map: &TypeCollection,
219) -> Result<(), SerdeError> {
220 match ty {
221 DataType::Any => return Err(SerdeError::InvalidInternallyTaggedEnum),
223 DataType::Map(_) => {}
224 DataType::Struct(_) => {}
226 DataType::Enum(ty) => match ty.repr() {
227 EnumRepr::Untagged => validate_internally_tag_enum(ty, type_map)?,
229 EnumRepr::External => {}
231 EnumRepr::Internal { .. } => {}
233 EnumRepr::Adjacent { .. } => {}
235 },
236 DataType::Tuple(ty) if ty.elements().is_empty() => {}
238 DataType::Reference(ty) => {
240 let ty = type_map.get(ty.sid()).expect("Type was never populated"); validate_internally_tag_enum_datatype(&ty.inner, type_map)?;
243 }
244 _ => return Err(SerdeError::InvalidInternallyTaggedEnum),
245 }
246
247 Ok(())
248}