Skip to main content

specta_serde/
lib.rs

1//! [Serde](https://serde.rs) support for Specta.
2//!
3//! # Choosing a mode
4//!
5//! - Use [`Format`] when serde behavior is symmetric and a single exported shape
6//!   should work for both serialization and deserialization.
7//! - Use [`PhasesFormat`] when serde behavior differs by direction (for example
8//!   deserialize-widening enums, asymmetric conversion attributes, or explicit
9//!   [`Phased`] overrides).
10//!
11//! # `serde_with` and `#[serde(with = ...)]`
12//!
13//! `serde_with` is supported through the same mechanism as raw serde codec
14//! attributes because it expands to serde metadata (`with`, `serialize_with`,
15//! `deserialize_with`).
16//!
17//! When codecs change the wire type, add an explicit Specta override:
18//!
19//! ```rust,ignore
20//! use serde::{Deserialize, Serialize};
21//! use specta::Type;
22//!
23//! #[derive(Type, Serialize, Deserialize)]
24//! struct Digest {
25//!     #[serde(with = "hex_bytes")]
26//!     #[specta(type = String)]
27//!     value: Vec<u8>,
28//! }
29//! ```
30//!
31//! If serialize and deserialize shapes are different, use [`Phased`] and
32//! [`PhasesFormat`].
33//!
34//! This is required because a single unified type graph cannot represent two
35//! different directional wire shapes at once.
36//!
37//! ```rust,ignore
38//! use serde::{Deserialize, Serialize};
39//! use serde_with::{OneOrMany, serde_as};
40//! use specta::{Type, Types};
41//!
42//! #[derive(Type, Serialize, Deserialize)]
43//! #[serde(untagged)]
44//! enum OneOrManyString {
45//!     One(String),
46//!     Many(Vec<String>),
47//! }
48//!
49//! #[serde_as]
50//! #[derive(Type, Serialize, Deserialize)]
51//! struct Filters {
52//!     #[serde_as(as = "OneOrMany<_>")]
53//!     #[specta(type = specta_serde::Phased<Vec<String>, OneOrManyString>)]
54//!     tags: Vec<String>,
55//! }
56//!
57//! let types = Types::default().register::<Filters>();
58//! let phased_types = specta_typescript::Typescript::default()
59//!     .export(&types, specta_serde::PhasesFormat)?;
60//! ```
61//!
62//! As an alternative to codec attributes, `#[serde(into = ...)]`,
63//! `#[serde(from = ...)]`, and `#[serde(try_from = ...)]` often produce better
64//! type inference because the wire type is modeled as an explicit Rust type:
65//!
66//! ```rust,ignore
67//! use serde::{Deserialize, Serialize};
68//! use specta::Type;
69//!
70//! #[derive(Type, Serialize, Deserialize)]
71//! struct UserWire {
72//!     id: String,
73//! }
74//!
75//! #[derive(Type, Clone, Serialize, Deserialize)]
76//! #[serde(into = "UserWire")]
77//! struct UserInto {
78//!     id: String,
79//! }
80//!
81//! #[derive(Type, Clone, Serialize, Deserialize)]
82//! #[serde(from = "UserWire")]
83//! struct UserFrom {
84//!     id: String,
85//! }
86//!
87//! #[derive(Type, Clone, Serialize, Deserialize)]
88//! #[serde(try_from = "UserWire")]
89//! struct UserTryFrom {
90//!     id: String,
91//! }
92//! ```
93//!
94//! See `examples/basic-ts/src/main.rs` for a complete exporter example using
95//! [`Format`] and [`PhasesFormat`].
96#![cfg_attr(docsrs, feature(doc_cfg))]
97#![doc(
98    html_logo_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png",
99    html_favicon_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png"
100)]
101
102use std::{
103    borrow::Cow,
104    collections::{HashMap, HashSet, VecDeque},
105};
106
107use specta::{
108    FormatError, Types,
109    datatype::{
110        DataType, Enum, Field, Fields, NamedDataType, NamedReference, NamedReferenceType,
111        Primitive, Reference, Struct, Tuple, UnnamedFields, Variant,
112    },
113};
114
115mod error;
116mod inflection;
117mod parser;
118mod phased;
119mod repr;
120mod validate;
121
122use inflection::RenameRule;
123use parser::{SerdeContainerAttrs, SerdeFieldAttrs, SerdeVariantAttrs};
124use phased::PhasedTy;
125use repr::EnumRepr;
126
127pub use error::Error;
128pub use phased::{Phased, phased};
129
130/// Selects which directional type shape to use with [`PhasesFormat`].
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum Phase {
133    /// The shape used when Rust serializes data to the wire.
134    Serialize,
135    /// The shape used when Rust deserializes data from the wire.
136    Deserialize,
137}
138
139/// Applies serde-aware rewrites to a single shared type graph.
140///
141/// Use this when the serialized and deserialized wire shape can be represented
142/// by the same exported schema. Exporters should pass this formatter to Specta's
143/// formatting hook, for example
144/// `specta_typescript::Typescript::default().export(&types, specta_serde::Format)`.
145///
146/// This formatter validates the graph for unified export and applies serde
147/// container, variant, and field behavior that affects the exported shape, such
148/// as renames, tagging, defaults, flattening, and compatible conversion attrs.
149///
150/// If serde metadata produces different serialize and deserialize shapes, this
151/// formatter returns an error instead of guessing. In that case, use
152/// [`PhasesFormat`].
153pub struct Format;
154
155impl specta::Format for Format {
156    fn map_types(&'_ self, types: &Types) -> Result<Cow<'_, Types>, FormatError> {
157        validate::validate_for_mode(types, validate::ApplyMode::Unified)?;
158
159        let mut out = types.clone();
160        let generated = HashMap::<TypeIdentity, SplitGeneratedTypes>::new();
161        let split_types = HashSet::<TypeIdentity>::new();
162        let mut rewrite_err = None;
163
164        out.iter_mut(|ndt| {
165            if rewrite_err.is_some() {
166                return;
167            }
168
169            let ndt_name = ndt.name.to_string();
170
171            if let Some(ty) = ndt.ty.as_mut() {
172                if rewrite_err.is_some() {
173                    return;
174                }
175
176                if let Err(err) = rewrite_datatype_for_phase(
177                    ty,
178                    PhaseRewrite::Unified,
179                    types,
180                    &generated,
181                    &split_types,
182                    Some(ndt_name.as_str()),
183                ) {
184                    rewrite_err = Some(err);
185                }
186            }
187
188            if rewrite_err.is_some() {
189                return;
190            }
191
192            if let Err(err) = rewrite_named_type_for_phase(ndt, PhaseRewrite::Unified) {
193                rewrite_err = Some(err);
194            }
195        });
196
197        if let Some(err) = rewrite_err {
198            return Err(Box::new(err));
199        }
200
201        Ok(Cow::Owned(out))
202    }
203
204    fn map_type(&'_ self, types: &Types, dt: &DataType) -> Result<Cow<'_, DataType>, FormatError> {
205        if datatype_is_registered_definition(types, dt) {
206            return Ok(Cow::Owned(dt.clone()));
207        }
208
209        validate::validate_datatype_for_mode(dt, types, validate::ApplyMode::Unified)?;
210
211        let mut dt = dt.clone();
212        rewrite_datatype_for_phase(
213            &mut dt,
214            PhaseRewrite::Unified,
215            types,
216            &HashMap::new(),
217            &HashSet::new(),
218            None,
219        )?;
220
221        Ok(Cow::Owned(dt))
222    }
223}
224
225/// Applies serde-aware rewrites while preserving separate serialize and
226/// deserialize shapes.
227///
228/// Use this when serde metadata makes the wire format directional, such as
229/// asymmetric renames, directional skips, `#[serde(with = ...)]`-style codecs,
230/// `#[serde(into = ...)]`/`#[serde(from = ...)]`, or explicit [`Phased`]
231/// overrides.
232///
233/// Exporters should pass this formatter to Specta's formatting hook, for
234/// example
235/// `specta_typescript::Typescript::default().export(&types, specta_serde::PhasesFormat)`.
236///
237/// The transformed type graph includes `*_Serialize` and `*_Deserialize` named
238/// types for definitions that need to diverge, while unchanged definitions stay
239/// shared. Inline datatype rendering uses the serialize-facing shape; use
240/// [`select_phase_datatype`] to inspect either direction explicitly.
241pub struct PhasesFormat;
242
243impl specta::Format for PhasesFormat {
244    fn map_types(&'_ self, types: &Types) -> Result<Cow<'_, Types>, FormatError> {
245        validate::validate_for_mode(types, validate::ApplyMode::Phases)?;
246
247        let originals = types.into_unsorted_iter().collect::<Vec<_>>();
248        let mut dependencies = HashMap::<TypeIdentity, HashSet<TypeIdentity>>::new();
249        let mut reverse_dependencies = HashMap::<TypeIdentity, HashSet<TypeIdentity>>::new();
250
251        for original in &originals {
252            let key = TypeIdentity::from_ndt(original);
253            let mut deps = HashSet::new();
254            if let Some(ty) = &original.ty {
255                collect_dependencies(ty, types, &mut deps)?;
256            }
257            for dep in &deps {
258                reverse_dependencies
259                    .entry(dep.clone())
260                    .or_default()
261                    .insert(key.clone());
262            }
263            dependencies.insert(key, deps);
264        }
265
266        let mut split_types = HashSet::new();
267        for ndt in &originals {
268            if ndt
269                .ty
270                .as_ref()
271                .is_some_and(|ty| has_local_phase_difference(ty).unwrap_or(false))
272            {
273                split_types.insert(TypeIdentity::from_ndt(ndt));
274            }
275        }
276
277        let mut queue = VecDeque::from_iter(split_types.iter().cloned());
278        while let Some(key) = queue.pop_front() {
279            if let Some(dependents) = reverse_dependencies.get(&key) {
280                for dependent in dependents {
281                    if split_types.insert(dependent.clone()) {
282                        queue.push_back(dependent.clone());
283                    }
284                }
285            }
286        }
287
288        let mut out = types.clone();
289        let mut generated = HashMap::<TypeIdentity, SplitGeneratedTypes>::new();
290        let mut generated_types = HashSet::<TypeIdentity>::new();
291
292        for original in &originals {
293            let key = TypeIdentity::from_ndt(original);
294
295            if split_types.contains(&key) {
296                let serialize_ndt = build_from_original(original, PhaseRewrite::Serialize)?;
297
298                let deserialize_ndt = build_from_original(original, PhaseRewrite::Deserialize)?;
299
300                generated.insert(
301                    key,
302                    SplitGeneratedTypes {
303                        serialize: serialize_ndt,
304                        deserialize: Box::new(deserialize_ndt),
305                    },
306                );
307            }
308        }
309
310        for original in &originals {
311            let key = TypeIdentity::from_ndt(original);
312
313            if !split_types.contains(&key) {
314                continue;
315            }
316
317            let Some(mut generated_types_for_phase) = generated.get(&key).cloned() else {
318                continue;
319            };
320
321            let mut rewrite_err = None;
322            if let Some(ty) = generated_types_for_phase.serialize.ty.as_mut()
323                && let Err(err) = rewrite_datatype_for_phase(
324                    ty,
325                    PhaseRewrite::Serialize,
326                    types,
327                    &generated,
328                    &split_types,
329                    Some(original.name.as_ref()),
330                )
331            {
332                rewrite_err = Some(err);
333            }
334            if let Some(err) = rewrite_err.take() {
335                return Err(Box::new(err));
336            }
337
338            rewrite_named_type_for_phase(
339                &mut generated_types_for_phase.serialize,
340                PhaseRewrite::Serialize,
341            )?;
342
343            if let Some(ty) = generated_types_for_phase.deserialize.ty.as_mut()
344                && let Err(err) = rewrite_datatype_for_phase(
345                    ty,
346                    PhaseRewrite::Deserialize,
347                    types,
348                    &generated,
349                    &split_types,
350                    Some(original.name.as_ref()),
351                )
352            {
353                rewrite_err = Some(err);
354            }
355            if let Some(err) = rewrite_err {
356                return Err(Box::new(err));
357            }
358
359            rewrite_named_type_for_phase(
360                &mut generated_types_for_phase.deserialize,
361                PhaseRewrite::Deserialize,
362            )?;
363
364            generated.insert(key, generated_types_for_phase);
365        }
366
367        for generated_types_for_phase in generated.values_mut() {
368            let serialize =
369                register_generated_type(&mut out, generated_types_for_phase.serialize.clone());
370            let deserialize = Box::new(register_generated_type(
371                &mut out,
372                (*generated_types_for_phase.deserialize).clone(),
373            ));
374
375            generated_types.insert(TypeIdentity::from_ndt(&serialize));
376            generated_types.insert(TypeIdentity::from_ndt(&deserialize));
377
378            generated_types_for_phase.serialize = serialize;
379            generated_types_for_phase.deserialize = deserialize;
380        }
381
382        let registered_generated = generated.clone();
383        for generated_types_for_phase in generated.values_mut() {
384            if let Some(ty) = generated_types_for_phase.serialize.ty.as_mut() {
385                rewrite_datatype_for_phase(
386                    ty,
387                    PhaseRewrite::Serialize,
388                    types,
389                    &registered_generated,
390                    &split_types,
391                    Some(generated_types_for_phase.serialize.name.as_ref()),
392                )?;
393            }
394
395            if let Some(ty) = generated_types_for_phase.deserialize.ty.as_mut() {
396                rewrite_datatype_for_phase(
397                    ty,
398                    PhaseRewrite::Deserialize,
399                    types,
400                    &registered_generated,
401                    &split_types,
402                    Some(generated_types_for_phase.deserialize.name.as_ref()),
403                )?;
404            }
405        }
406
407        out.iter_mut(|ndt| {
408            for generated_types_for_phase in generated.values() {
409                if ndt.name == generated_types_for_phase.serialize.name {
410                    ndt.ty = generated_types_for_phase.serialize.ty.clone();
411                    return;
412                }
413
414                if ndt.name == generated_types_for_phase.deserialize.name {
415                    ndt.ty = generated_types_for_phase.deserialize.ty.clone();
416                    return;
417                }
418            }
419        });
420
421        let mut rewrite_err = None;
422        out.iter_mut(|ndt| {
423            if rewrite_err.is_some() {
424                return;
425            }
426
427            let ndt_name = ndt.name.to_string();
428            let key = TypeIdentity::from_ndt(ndt);
429
430            if split_types.contains(&key) || generated_types.contains(&key) {
431                return;
432            }
433
434            if let Some(ty) = ndt.ty.as_mut()
435                && let Err(err) = rewrite_datatype_for_phase(
436                    ty,
437                    PhaseRewrite::Unified,
438                    types,
439                    &generated,
440                    &split_types,
441                    Some(ndt_name.as_str()),
442                )
443            {
444                rewrite_err = Some(err);
445                return;
446            }
447
448            if let Err(err) = rewrite_named_type_for_phase(ndt, PhaseRewrite::Unified) {
449                rewrite_err = Some(err);
450            }
451        });
452
453        if let Some(err) = rewrite_err {
454            return Err(Box::new(err));
455        }
456
457        out.iter_mut(|ndt| {
458            let key = TypeIdentity::from_ndt(ndt);
459            if !split_types.contains(&key) {
460                return;
461            }
462
463            let Some(SplitGeneratedTypes {
464                serialize,
465                deserialize,
466            }) = generated.get(&key)
467            else {
468                return;
469            };
470
471            let generic_args = ndt
472                .generics
473                .iter()
474                .map(|generic| {
475                    let generic = specta::datatype::Generic::new(generic.name.clone());
476                    (generic.clone(), generic.into())
477                })
478                .collect::<Vec<_>>();
479
480            let mut serialize_variant = Variant::unnamed().build();
481            if let Fields::Unnamed(fields) = &mut serialize_variant.fields {
482                fields
483                    .fields
484                    .push(Field::new(serialize.reference(generic_args.clone()).into()));
485            }
486
487            let mut deserialize_variant = Variant::unnamed().build();
488            if let Fields::Unnamed(fields) = &mut deserialize_variant.fields {
489                fields
490                    .fields
491                    .push(Field::new(deserialize.reference(generic_args).into()));
492            }
493
494            let mut wrapper = Enum::default();
495            wrapper
496                .variants
497                .push((Cow::Borrowed("Serialize"), serialize_variant));
498            wrapper
499                .variants
500                .push((Cow::Borrowed("Deserialize"), deserialize_variant));
501
502            ndt.ty = Some(DataType::Enum(wrapper));
503        });
504        Ok(Cow::Owned(out))
505    }
506
507    fn map_type(&'_ self, types: &Types, dt: &DataType) -> Result<Cow<'_, DataType>, FormatError> {
508        if datatype_is_registered_definition(types, dt) {
509            return Ok(Cow::Owned(dt.clone()));
510        }
511
512        let mut selected = select_phase_datatype(dt, types, Phase::Serialize);
513
514        validate::validate_datatype_for_mode_shallow(
515            &selected,
516            types,
517            validate::ApplyMode::Phases,
518        )?;
519
520        rewrite_datatype_for_phase(
521            &mut selected,
522            PhaseRewrite::Serialize,
523            types,
524            &HashMap::new(),
525            &HashSet::new(),
526            None,
527        )?;
528
529        Ok(Cow::Owned(selected))
530    }
531}
532
533fn datatype_is_registered_definition(types: &Types, dt: &DataType) -> bool {
534    types
535        .into_unsorted_iter()
536        .any(|ndt| ndt.ty.as_ref() == Some(dt))
537}
538
539/// Rewrites a [`DataType`] to the requested directional shape for [`PhasesFormat`].
540///
541/// This is useful for exporter integrations that need deserialize-specific input
542/// types and serialize-specific output types while still exporting against the
543/// resolved type graph produced by the `map_types` callback from
544/// [`PhasesFormat`].
545///
546/// # Examples
547///
548/// ```rust
549/// use serde::{Deserialize, Serialize};
550/// use specta::{Format as _, Type, Types, datatype::{DataType, Reference}};
551/// use specta_serde::{Phase, Phased, PhasesFormat, select_phase_datatype};
552///
553/// #[derive(Type, Serialize, Deserialize)]
554/// #[serde(untagged)]
555/// enum OneOrManyString {
556///     One(String),
557///     Many(Vec<String>),
558/// }
559///
560/// #[derive(Type, Serialize, Deserialize)]
561/// struct Filters {
562///     #[specta(type = Phased<Vec<String>, OneOrManyString>)]
563///     tags: Vec<String>,
564/// }
565///
566/// let mut types = Types::default();
567/// let dt = Filters::definition(&mut types);
568/// let format = PhasesFormat;
569/// let resolved = format.map_types(&types)
570///     .expect("PhasesFormat should succeed")
571///     .into_owned();
572///
573/// let serialize = select_phase_datatype(&dt, &resolved, Phase::Serialize);
574/// let deserialize = select_phase_datatype(&dt, &resolved, Phase::Deserialize);
575///
576/// let DataType::Reference(Reference::Named(serialize_reference)) = &serialize else {
577///     panic!("expected named serialize reference");
578/// };
579/// let DataType::Reference(Reference::Named(deserialize_reference)) = &deserialize else {
580///     panic!("expected named deserialize reference");
581/// };
582///
583/// assert_eq!(
584///     resolved.get(serialize_reference).unwrap().name,
585///     "Filters_Serialize"
586/// );
587/// assert_eq!(
588///     resolved.get(deserialize_reference).unwrap().name,
589///     "Filters_Deserialize"
590/// );
591/// # Ok::<(), specta_serde::Error>(())
592/// ```
593pub fn select_phase_datatype(dt: &DataType, types: &Types, phase: Phase) -> DataType {
594    let mut dt = dt.clone();
595    select_phase_datatype_inner(&mut dt, types, phase);
596    dt
597}
598
599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
600enum PhaseRewrite {
601    Unified,
602    Serialize,
603    Deserialize,
604}
605
606fn select_phase_datatype_inner(ty: &mut DataType, types: &Types, phase: Phase) {
607    if let Some(resolved) = select_split_wrapper_variant(ty, phase) {
608        *ty = resolved;
609        select_phase_datatype_inner(ty, types, phase);
610        return;
611    }
612
613    if let Some(resolved) = select_explicit_phased_type(ty, phase) {
614        *ty = resolved;
615        select_phase_datatype_inner(ty, types, phase);
616        return;
617    }
618
619    match ty {
620        DataType::Struct(s) => select_phase_fields(&mut s.fields, types, phase),
621        DataType::Enum(e) => {
622            for (_, variant) in &mut e.variants {
623                select_phase_fields(&mut variant.fields, types, phase);
624            }
625        }
626        DataType::Tuple(tuple) => {
627            for ty in &mut tuple.elements {
628                select_phase_datatype_inner(ty, types, phase);
629            }
630        }
631        DataType::List(list) => select_phase_datatype_inner(&mut list.ty, types, phase),
632        DataType::Map(map) => {
633            select_phase_datatype_inner(map.key_ty_mut(), types, phase);
634            select_phase_datatype_inner(map.value_ty_mut(), types, phase);
635        }
636        DataType::Intersection(types_) => {
637            for ty in types_ {
638                select_phase_datatype_inner(ty, types, phase);
639            }
640        }
641        DataType::Nullable(inner) => select_phase_datatype_inner(inner, types, phase),
642        DataType::Reference(Reference::Named(reference)) => {
643            if let NamedReferenceType::Inline { dt, .. } = &mut reference.inner {
644                select_phase_datatype_inner(dt, types, phase);
645                return;
646            }
647
648            let Some(referenced_ndt) = types.get(reference) else {
649                return;
650            };
651            for (_, dt) in named_reference_generics_mut(reference) {
652                select_phase_datatype_inner(dt, types, phase);
653            }
654
655            if let Some(mut selected) = referenced_ndt
656                .ty
657                .as_ref()
658                .and_then(|ty| select_split_wrapper_variant(ty, phase))
659            {
660                select_phase_datatype_inner(&mut selected, types, phase);
661                *ty = selected;
662                return;
663            }
664
665            let target_ndt =
666                select_split_type_variant(referenced_ndt, types, phase).unwrap_or(referenced_ndt);
667            let Reference::Named(new_reference) =
668                target_ndt.reference(named_reference_generics(reference).to_vec())
669            else {
670                unreachable!("named types always produce named references")
671            };
672            *reference = new_reference;
673        }
674        DataType::Reference(Reference::Opaque(_))
675        | DataType::Generic(_)
676        | DataType::Primitive(_) => {}
677    }
678}
679
680fn select_phase_fields(fields: &mut Fields, types: &Types, phase: Phase) {
681    match fields {
682        Fields::Unit => {}
683        Fields::Unnamed(fields) => {
684            for field in &mut fields.fields {
685                if let Some(ty) = field.ty.as_mut() {
686                    select_phase_datatype_inner(ty, types, phase);
687                }
688            }
689        }
690        Fields::Named(fields) => {
691            for (_, field) in &mut fields.fields {
692                if let Some(ty) = field.ty.as_mut() {
693                    select_phase_datatype_inner(ty, types, phase);
694                }
695            }
696        }
697    }
698}
699
700fn select_explicit_phased_type(ty: &DataType, phase: Phase) -> Option<DataType> {
701    let DataType::Reference(Reference::Opaque(reference)) = ty else {
702        return None;
703    };
704    let phased = reference.downcast_ref::<PhasedTy>()?;
705
706    Some(match phase {
707        Phase::Serialize => phased.serialize.clone(),
708        Phase::Deserialize => phased.deserialize.clone(),
709    })
710}
711
712fn select_split_wrapper_variant(ty: &DataType, phase: Phase) -> Option<DataType> {
713    let DataType::Enum(wrapper) = ty else {
714        return None;
715    };
716
717    if wrapper.variants.len() != 2 {
718        return None;
719    }
720
721    let variant_name = match phase {
722        Phase::Serialize => "Serialize",
723        Phase::Deserialize => "Deserialize",
724    };
725
726    let (_, variant) = wrapper
727        .variants
728        .iter()
729        .find(|(name, _)| name == variant_name)?;
730    let Fields::Unnamed(fields) = &variant.fields else {
731        return None;
732    };
733
734    let [field] = &fields.fields[..] else {
735        return None;
736    };
737
738    field.ty.clone()
739}
740
741fn select_split_type_variant<'a>(
742    ndt: &'a NamedDataType,
743    types: &'a Types,
744    phase: Phase,
745) -> Option<&'a NamedDataType> {
746    let Some(DataType::Enum(wrapper)) = &ndt.ty else {
747        return None;
748    };
749
750    if wrapper.variants.len() != 2 {
751        return None;
752    }
753
754    let variant_name = match phase {
755        Phase::Serialize => "Serialize",
756        Phase::Deserialize => "Deserialize",
757    };
758
759    let (_, variant) = wrapper
760        .variants
761        .iter()
762        .find(|(name, _)| name == variant_name)?;
763    let Fields::Unnamed(fields) = &variant.fields else {
764        return None;
765    };
766    let [field] = &fields.fields[..] else {
767        return None;
768    };
769    let Some(DataType::Reference(Reference::Named(reference))) = field.ty.as_ref() else {
770        return None;
771    };
772
773    types.get(reference)
774}
775
776fn named_reference_generics(
777    reference: &NamedReference,
778) -> &[(specta::datatype::Generic, DataType)] {
779    match &reference.inner {
780        NamedReferenceType::Reference { generics, .. } => generics,
781        NamedReferenceType::Inline { .. } | NamedReferenceType::Recursive(_) => &[],
782    }
783}
784
785fn named_reference_generics_mut(
786    reference: &mut NamedReference,
787) -> &mut [(specta::datatype::Generic, DataType)] {
788    match &mut reference.inner {
789        NamedReferenceType::Reference { generics, .. } => generics,
790        NamedReferenceType::Inline { .. } | NamedReferenceType::Recursive(_) => &mut [],
791    }
792}
793
794#[derive(Debug, Clone)]
795struct SplitGeneratedTypes {
796    serialize: NamedDataType,
797    deserialize: Box<NamedDataType>,
798}
799
800#[derive(Debug, Clone, PartialEq, Eq, Hash)]
801struct TypeIdentity {
802    name: String,
803    module_path: String,
804    file: &'static str,
805    line: u32,
806    column: u32,
807}
808
809impl TypeIdentity {
810    fn from_ndt(ty: &specta::datatype::NamedDataType) -> Self {
811        let location = ty.location;
812        Self {
813            name: ty.name.to_string(),
814            module_path: ty.module_path.to_string(),
815            file: location.file(),
816            line: location.line(),
817            column: location.column(),
818        }
819    }
820}
821
822fn rewrite_datatype_for_phase(
823    ty: &mut DataType,
824    mode: PhaseRewrite,
825    original_types: &Types,
826    generated: &HashMap<TypeIdentity, SplitGeneratedTypes>,
827    split_types: &HashSet<TypeIdentity>,
828    container_name: Option<&str>,
829) -> Result<(), Error> {
830    if let Some(resolved) = resolve_phased_type(ty, mode, "type")? {
831        *ty = resolved;
832    }
833
834    if let Some(converted) = conversion_datatype_for_mode(ty, mode)?
835        && converted != *ty
836    {
837        *ty = converted;
838        return rewrite_datatype_for_phase(
839            ty,
840            mode,
841            original_types,
842            generated,
843            split_types,
844            container_name,
845        );
846    }
847
848    match ty {
849        DataType::Struct(s) => {
850            let container_default = SerdeContainerAttrs::from_attributes(&s.attributes)?
851                .is_some_and(|attrs| attrs.default);
852            let container_rename_all = container_rename_all_rule(
853                &s.attributes,
854                mode,
855                "struct rename_all",
856                container_name.unwrap_or("<anonymous struct>"),
857            )?;
858
859            rewrite_fields_for_phase(
860                &mut s.fields,
861                mode,
862                original_types,
863                generated,
864                split_types,
865                container_rename_all,
866                container_default,
867                false,
868            )?;
869            rewrite_struct_repr_for_phase(s, mode, container_name)?;
870            if let Some(intersection) = lower_field_aliases_for_phase(&mut s.fields, mode)? {
871                *ty = intersection;
872                return Ok(());
873            }
874            if let Some(intersection) = lower_flattened_struct(s)? {
875                *ty = intersection;
876            }
877        }
878        DataType::Enum(e) => {
879            filter_enum_variants_for_phase(e, mode)?;
880            let container_attrs = SerdeContainerAttrs::from_attributes(&e.attributes)?;
881
882            for (variant_name, variant) in &mut e.variants {
883                let rename_rule =
884                    enum_variant_field_rename_rule(&container_attrs, variant, mode, variant_name)?;
885
886                rewrite_fields_for_phase(
887                    &mut variant.fields,
888                    mode,
889                    original_types,
890                    generated,
891                    split_types,
892                    rename_rule,
893                    false,
894                    true,
895                )?;
896
897                if let Some(aliases) = lower_field_aliases_for_phase(&mut variant.fields, mode)? {
898                    variant.fields = Variant::unnamed().field(Field::new(aliases)).build().fields;
899                }
900            }
901
902            if rewrite_identifier_enum_for_phase(e, mode, original_types, generated, split_types)? {
903                return Ok(());
904            }
905
906            rewrite_enum_repr_for_phase(e, mode, original_types)?;
907        }
908        DataType::Tuple(tuple) => {
909            for ty in &mut tuple.elements {
910                rewrite_datatype_for_phase(ty, mode, original_types, generated, split_types, None)?;
911            }
912        }
913        DataType::List(list) => rewrite_datatype_for_phase(
914            &mut list.ty,
915            mode,
916            original_types,
917            generated,
918            split_types,
919            None,
920        )?,
921        DataType::Map(map) => {
922            rewrite_datatype_for_phase(
923                map.key_ty_mut(),
924                mode,
925                original_types,
926                generated,
927                split_types,
928                None,
929            )?;
930            rewrite_datatype_for_phase(
931                map.value_ty_mut(),
932                mode,
933                original_types,
934                generated,
935                split_types,
936                None,
937            )?;
938        }
939        DataType::Intersection(types_) => {
940            for ty in types_ {
941                rewrite_datatype_for_phase(ty, mode, original_types, generated, split_types, None)?;
942            }
943        }
944        DataType::Nullable(inner) => {
945            rewrite_datatype_for_phase(inner, mode, original_types, generated, split_types, None)?
946        }
947        DataType::Reference(Reference::Named(reference)) => {
948            if let NamedReferenceType::Inline { dt, .. } = &mut reference.inner {
949                rewrite_datatype_for_phase(dt, mode, original_types, generated, split_types, None)?;
950            }
951
952            let Some(referenced_ndt) = original_types.get(reference) else {
953                return Ok(());
954            };
955            let key = TypeIdentity::from_ndt(referenced_ndt);
956            for (_, dt) in named_reference_generics_mut(reference) {
957                rewrite_datatype_for_phase(dt, mode, original_types, generated, split_types, None)?;
958            }
959
960            if !split_types.contains(&key) {
961                return Ok(());
962            }
963
964            let Some(target) = generated.get(&key) else {
965                return Ok(());
966            };
967
968            let Reference::Named(reference_from_target) = (match mode {
969                PhaseRewrite::Unified => {
970                    unreachable!("unified mode should not reference split types")
971                }
972                PhaseRewrite::Serialize => target
973                    .serialize
974                    .reference(named_reference_generics(reference).to_vec()),
975                PhaseRewrite::Deserialize => target
976                    .deserialize
977                    .reference(named_reference_generics(reference).to_vec()),
978            }) else {
979                unreachable!("named types always produce named references")
980            };
981            *reference = reference_from_target;
982        }
983        DataType::Reference(Reference::Opaque(_))
984        | DataType::Generic(_)
985        | DataType::Primitive(_) => {}
986    }
987
988    Ok(())
989}
990
991fn lower_flattened_struct(strct: &mut Struct) -> Result<Option<DataType>, Error> {
992    let Fields::Named(named) = &mut strct.fields else {
993        return Ok(None);
994    };
995
996    let has_flattened = named
997        .fields
998        .iter()
999        .any(|(_, field)| field_is_flattened(field));
1000    if !has_flattened {
1001        return Ok(None);
1002    }
1003
1004    let fields = std::mem::take(&mut named.fields);
1005    let mut base = Struct::named();
1006    let mut parts = Vec::new();
1007
1008    for (name, field) in fields {
1009        if field_is_flattened(&field) {
1010            if let Some(ty) = field.ty {
1011                parts.push(ty);
1012            }
1013        } else {
1014            base.field_mut(name, field);
1015        }
1016    }
1017
1018    let mut base = match base.build() {
1019        DataType::Struct(base) => base,
1020        _ => unreachable!("Struct::named always builds a struct"),
1021    };
1022    if matches!(&base.fields, Fields::Named(named) if !named.fields.is_empty()) {
1023        base.attributes = strct.attributes.clone();
1024        parts.insert(0, DataType::Struct(base));
1025    }
1026
1027    Ok(Some(DataType::Intersection(parts)))
1028}
1029
1030fn lower_field_aliases_for_phase(
1031    fields: &mut Fields,
1032    mode: PhaseRewrite,
1033) -> Result<Option<DataType>, Error> {
1034    if mode != PhaseRewrite::Deserialize {
1035        return Ok(None);
1036    }
1037
1038    let Fields::Named(named) = fields else {
1039        return Ok(None);
1040    };
1041
1042    if !named
1043        .fields
1044        .iter()
1045        .any(|(_, field)| field_has_aliases(field))
1046    {
1047        return Ok(None);
1048    }
1049
1050    let mut base = Struct::named();
1051    let mut parts = Vec::new();
1052
1053    for (name, field) in std::mem::take(&mut named.fields) {
1054        let Some(attrs) = SerdeFieldAttrs::from_attributes(&field.attributes)? else {
1055            base.field_mut(name, field);
1056            continue;
1057        };
1058
1059        if attrs.aliases.is_empty() {
1060            base.field_mut(name, field);
1061            continue;
1062        }
1063
1064        let mut accepted_names = Vec::with_capacity(attrs.aliases.len() + 1);
1065        accepted_names.push(name);
1066        accepted_names.extend(attrs.aliases.into_iter().map(Cow::Owned));
1067        parts.push(alias_field_union(accepted_names, field));
1068    }
1069
1070    let base = match base.build() {
1071        DataType::Struct(base) => base,
1072        _ => unreachable!("Struct::named always builds a struct"),
1073    };
1074
1075    if matches!(&base.fields, Fields::Named(named) if !named.fields.is_empty()) {
1076        parts.insert(0, DataType::Struct(base));
1077    }
1078
1079    Ok(Some(DataType::Intersection(parts)))
1080}
1081
1082fn field_has_aliases(field: &Field) -> bool {
1083    SerdeFieldAttrs::from_attributes(&field.attributes)
1084        .ok()
1085        .flatten()
1086        .is_some_and(|attrs| !attrs.aliases.is_empty())
1087}
1088
1089fn alias_field_union(names: Vec<Cow<'static, str>>, field: Field) -> DataType {
1090    let mut aliases = Enum::default();
1091    let empty_variant = Variant::unnamed().build();
1092
1093    for name in names {
1094        let mut field = field.clone();
1095        field.attributes.remove(parser::FIELD_ALIASES);
1096
1097        aliases.variants.push((
1098            Cow::Borrowed(""),
1099            clone_variant_with_unnamed_fields(
1100                &empty_variant,
1101                vec![Field::new(named_fields_datatype(vec![(name, field)]))],
1102            ),
1103        ));
1104    }
1105
1106    DataType::Enum(aliases)
1107}
1108
1109fn field_is_flattened(field: &Field) -> bool {
1110    SerdeFieldAttrs::from_attributes(&field.attributes)
1111        .ok()
1112        .flatten()
1113        .is_some_and(|attrs| attrs.flatten)
1114}
1115
1116fn rewrite_fields_for_phase(
1117    fields: &mut Fields,
1118    mode: PhaseRewrite,
1119    original_types: &Types,
1120    generated: &HashMap<TypeIdentity, SplitGeneratedTypes>,
1121    split_types: &HashSet<TypeIdentity>,
1122    rename_all_rule: Option<RenameRule>,
1123    container_default: bool,
1124    preserve_skipped_unnamed_fields: bool,
1125) -> Result<(), Error> {
1126    match fields {
1127        Fields::Unit => {}
1128        Fields::Unnamed(unnamed) => {
1129            for field in &mut unnamed.fields {
1130                if should_skip_field_for_mode(field, mode)? {
1131                    if preserve_skipped_unnamed_fields {
1132                        *field = skipped_field_marker(field);
1133                    }
1134
1135                    continue;
1136                }
1137
1138                apply_field_attrs(field, mode, container_default)?;
1139                rewrite_field_for_phase(field, mode, original_types, generated, split_types)?;
1140            }
1141
1142            if !preserve_skipped_unnamed_fields {
1143                unnamed.fields.retain(|field| field.ty.as_ref().is_some());
1144            }
1145        }
1146        Fields::Named(named) => {
1147            let mut skip_err = None;
1148            named
1149                .fields
1150                .retain(|(_, field)| match should_skip_field_for_mode(field, mode) {
1151                    Ok(skip) => !skip,
1152                    Err(err) => {
1153                        skip_err = Some(err);
1154                        true
1155                    }
1156                });
1157            if let Some(err) = skip_err {
1158                return Err(err);
1159            }
1160
1161            for (name, field) in &mut named.fields {
1162                apply_field_attrs(field, mode, container_default)?;
1163
1164                if let Some(serde_attrs) = SerdeFieldAttrs::from_attributes(&field.attributes)? {
1165                    let rename = select_phase_string(
1166                        mode,
1167                        serde_attrs.rename_serialize.as_deref(),
1168                        serde_attrs.rename_deserialize.as_deref(),
1169                        "field rename",
1170                        name.as_ref(),
1171                    )?;
1172
1173                    if let Some(rename) = rename {
1174                        *name = Cow::Owned(rename.to_string());
1175                    } else if let Some(rule) = rename_all_rule {
1176                        *name = Cow::Owned(rule.apply_to_field(name.as_ref()));
1177                    }
1178                } else if let Some(rule) = rename_all_rule {
1179                    *name = Cow::Owned(rule.apply_to_field(name.as_ref()));
1180                }
1181
1182                rewrite_field_for_phase(field, mode, original_types, generated, split_types)?;
1183            }
1184        }
1185    }
1186
1187    Ok(())
1188}
1189
1190fn rewrite_field_for_phase(
1191    field: &mut Field,
1192    mode: PhaseRewrite,
1193    original_types: &Types,
1194    generated: &HashMap<TypeIdentity, SplitGeneratedTypes>,
1195    split_types: &HashSet<TypeIdentity>,
1196) -> Result<(), Error> {
1197    if let Some(attrs) = SerdeFieldAttrs::from_attributes(&field.attributes)?
1198        && attrs.skip_serializing_if.is_some()
1199    {
1200        if let PhaseRewrite::Serialize = mode {
1201            field.optional = true;
1202        }
1203        // The attribute is meaningless on phase-split fields: the _Serialize
1204        // variant already has `optional = true`, and the _Deserialize variant
1205        // treats the field as present-or-default. Leaving it attached makes
1206        // `validate_datatype_for_mode(_, _, ApplyMode::Unified)` reject the
1207        // already-split variant — a footgun for downstream callers (e.g.
1208        // tauri-specta's `validate_exported_command`) that run unified
1209        // validation on the post-`apply_phases` graph.
1210        field.attributes.remove(parser::FIELD_SKIP_SERIALIZING_IF);
1211    }
1212
1213    if let Some(ty) = field.ty.clone()
1214        && let Some(resolved) = resolve_phased_type(&ty, mode, "field")?
1215    {
1216        field.ty = Some(resolved);
1217    }
1218
1219    if let Some(ty) = field.ty.as_mut() {
1220        rewrite_datatype_for_phase(ty, mode, original_types, generated, split_types, None)?;
1221    }
1222
1223    Ok(())
1224}
1225
1226fn rewrite_struct_repr_for_phase(
1227    strct: &mut Struct,
1228    mode: PhaseRewrite,
1229    container_name: Option<&str>,
1230) -> Result<(), Error> {
1231    let Some((tag, rename_serialize, rename_deserialize)) =
1232        SerdeContainerAttrs::from_attributes(&strct.attributes)?.map(|attrs| {
1233            (
1234                attrs.tag.clone(),
1235                attrs.rename_serialize.clone(),
1236                attrs.rename_deserialize.clone(),
1237            )
1238        })
1239    else {
1240        return Ok(());
1241    };
1242
1243    let Some(tag) = tag.as_deref() else {
1244        return Ok(());
1245    };
1246
1247    let Fields::Named(named) = &mut strct.fields else {
1248        return Ok(());
1249    };
1250
1251    if named.fields.iter().any(|(name, field)| {
1252        name.as_ref() == tag
1253            && field
1254                .ty
1255                .as_ref()
1256                .is_some_and(is_generated_string_literal_datatype)
1257    }) {
1258        return Ok(());
1259    }
1260
1261    let serialized_name = match select_phase_string(
1262        mode,
1263        rename_serialize.as_deref(),
1264        rename_deserialize.as_deref(),
1265        "struct rename",
1266        container_name.unwrap_or("<anonymous struct>"),
1267    )? {
1268        Some(rename) => rename.to_string(),
1269        None => container_name
1270            .map(str::to_owned)
1271            .ok_or_else(|| {
1272                Error::invalid_phased_type_usage(
1273                    "<anonymous struct>",
1274                    "`#[serde(tag = ...)]` on structs requires either a named type or `#[serde(rename = ...)]`",
1275                )
1276            })?,
1277    };
1278
1279    named.fields.insert(
1280        0,
1281        (
1282            Cow::Owned(tag.to_string()),
1283            Field::new(string_literal_datatype(serialized_name)),
1284        ),
1285    );
1286
1287    Ok(())
1288}
1289
1290fn should_skip_field_for_mode(field: &Field, mode: PhaseRewrite) -> Result<bool, Error> {
1291    let Some(attrs) = SerdeFieldAttrs::from_attributes(&field.attributes)? else {
1292        return Ok(false);
1293    };
1294
1295    Ok(match mode {
1296        PhaseRewrite::Serialize => attrs.skip_serializing,
1297        PhaseRewrite::Deserialize => attrs.skip_deserializing,
1298        PhaseRewrite::Unified => attrs.skip_serializing || attrs.skip_deserializing,
1299    })
1300}
1301
1302fn skipped_field_marker(field: &Field) -> Field {
1303    let mut skipped = Field::default();
1304    skipped.optional = field.optional;
1305    skipped.deprecated = field.deprecated.clone();
1306    skipped.docs = field.docs.clone();
1307    skipped.attributes = field.attributes.clone();
1308    skipped
1309}
1310
1311fn unnamed_live_fields(unnamed: &UnnamedFields) -> impl Iterator<Item = &Field> {
1312    unnamed.fields.iter().filter(|field| field.ty.is_some())
1313}
1314
1315fn unnamed_live_field_count(unnamed: &UnnamedFields) -> usize {
1316    unnamed_live_fields(unnamed).count()
1317}
1318
1319fn unnamed_has_effective_payload(unnamed: &UnnamedFields) -> bool {
1320    unnamed_live_field_count(unnamed) != 0
1321}
1322
1323fn unnamed_fields_all_skipped(unnamed: &UnnamedFields) -> bool {
1324    !unnamed.fields.is_empty() && !unnamed_has_effective_payload(unnamed)
1325}
1326
1327fn rewrite_enum_repr_for_phase(
1328    e: &mut Enum,
1329    mode: PhaseRewrite,
1330    original_types: &Types,
1331) -> Result<(), Error> {
1332    if enum_repr_already_rewritten(e) {
1333        return Ok(());
1334    }
1335
1336    let repr = EnumRepr::from_attrs(&e.attributes)?;
1337    if matches!(repr, EnumRepr::Untagged) {
1338        return Ok(());
1339    }
1340
1341    let container_attrs = SerdeContainerAttrs::from_attributes(&e.attributes)?;
1342    let variants = std::mem::take(&mut e.variants);
1343    let mut transformed = Vec::with_capacity(variants.len());
1344    for (variant_name, variant) in variants {
1345        if variant.skip {
1346            continue;
1347        }
1348
1349        let variant_attrs = SerdeVariantAttrs::from_attributes(&variant.attributes)?;
1350        if variant_attrs
1351            .as_ref()
1352            .is_some_and(|attrs| variant_is_skipped_for_mode(attrs, mode))
1353        {
1354            continue;
1355        }
1356
1357        if variant_attrs.as_ref().is_some_and(|attrs| attrs.untagged) {
1358            transformed.push((
1359                Cow::Owned(variant_name.into_owned()),
1360                transform_untagged_variant(&variant)?,
1361            ));
1362            continue;
1363        }
1364
1365        let serialized_name =
1366            serialized_variant_name(&variant_name, &variant, &container_attrs, mode)?;
1367        let aliases = variant_attrs
1368            .as_ref()
1369            .filter(|_| mode == PhaseRewrite::Deserialize)
1370            .map(|attrs| attrs.aliases.as_slice())
1371            .unwrap_or(&[]);
1372        let names = std::iter::once(serialized_name).chain(aliases.iter().cloned());
1373
1374        for serialized_name in names {
1375            let widen_tag = mode == PhaseRewrite::Deserialize
1376                && variant_attrs.as_ref().is_some_and(|attrs| attrs.other);
1377            let mut transformed_variant = match &repr {
1378                EnumRepr::External => {
1379                    transform_external_variant(serialized_name.clone(), &variant)?
1380                }
1381                EnumRepr::Internal { tag } => transform_internal_variant(
1382                    serialized_name.clone(),
1383                    tag.as_ref(),
1384                    &variant,
1385                    original_types,
1386                    widen_tag,
1387                )?,
1388                EnumRepr::Adjacent { tag, content } => {
1389                    if tag == content {
1390                        return Err(Error::invalid_enum_representation(
1391                            "serde adjacent tagging requires distinct `tag` and `content` field names",
1392                        ));
1393                    }
1394
1395                    transform_adjacent_variant(
1396                        serialized_name.clone(),
1397                        tag.as_ref(),
1398                        content.as_ref(),
1399                        &variant,
1400                        widen_tag,
1401                    )?
1402                }
1403                EnumRepr::Untagged => unreachable!(),
1404            };
1405
1406            transformed_variant.attributes = Default::default();
1407            transformed.push((Cow::Owned(serialized_name), transformed_variant));
1408        }
1409    }
1410
1411    e.variants = transformed;
1412    e.attributes = Default::default();
1413
1414    Ok(())
1415}
1416
1417fn enum_repr_already_rewritten(e: &Enum) -> bool {
1418    e.attributes.is_empty()
1419        && !e.variants.is_empty()
1420        && e.variants.iter().all(|(name, variant)| {
1421            variant.attributes.is_empty() && variant_repr_already_rewritten(name, variant)
1422        })
1423}
1424
1425fn variant_repr_already_rewritten(name: &str, variant: &Variant) -> bool {
1426    match &variant.fields {
1427        Fields::Unit => false,
1428        Fields::Unnamed(fields) if name.is_empty() => unnamed_live_field_count(fields) == 1,
1429        Fields::Unnamed(fields) if fields.fields.len() == 1 => fields
1430            .fields
1431            .first()
1432            .and_then(|field| field.ty.as_ref())
1433            .is_some_and(is_generated_string_literal_datatype),
1434        Fields::Named(fields) => fields.fields.iter().any(|(field_name, field)| {
1435            field_name == name
1436                || field
1437                    .ty
1438                    .as_ref()
1439                    .is_some_and(is_generated_string_literal_datatype)
1440        }),
1441        _ => false,
1442    }
1443}
1444
1445fn rewrite_identifier_enum_for_phase(
1446    e: &mut Enum,
1447    mode: PhaseRewrite,
1448    original_types: &Types,
1449    generated: &HashMap<TypeIdentity, SplitGeneratedTypes>,
1450    split_types: &HashSet<TypeIdentity>,
1451) -> Result<bool, Error> {
1452    let Some(attrs) = SerdeContainerAttrs::from_attributes(&e.attributes)? else {
1453        return Ok(false);
1454    };
1455
1456    if !attrs.variant_identifier && !attrs.field_identifier {
1457        return Ok(false);
1458    }
1459
1460    if mode != PhaseRewrite::Deserialize {
1461        return Ok(false);
1462    }
1463
1464    let container_attrs = SerdeContainerAttrs::from_attributes(&e.attributes)?;
1465    let mut variants = Vec::new();
1466    let mut seen = HashSet::new();
1467
1468    for (variant_name, variant) in e.variants.iter() {
1469        let serialized_name = serialized_variant_name(
1470            variant_name,
1471            variant,
1472            &container_attrs,
1473            PhaseRewrite::Deserialize,
1474        )?;
1475
1476        if seen.insert(serialized_name.clone()) {
1477            variants.push((
1478                Cow::Owned(serialized_name.clone()),
1479                identifier_union_variant(string_literal_datatype(serialized_name)),
1480            ));
1481        }
1482
1483        if let Some(variant_attrs) = SerdeVariantAttrs::from_attributes(&variant.attributes)? {
1484            for alias in &variant_attrs.aliases {
1485                if seen.insert(alias.clone()) {
1486                    variants.push((
1487                        Cow::Owned(alias.clone()),
1488                        identifier_union_variant(string_literal_datatype(alias.clone())),
1489                    ));
1490                }
1491            }
1492        }
1493    }
1494
1495    variants.push((
1496        Cow::Borrowed(""),
1497        identifier_union_variant(DataType::Primitive(specta::datatype::Primitive::u32)),
1498    ));
1499
1500    if attrs.field_identifier
1501        && let Some((_, fallback)) = &e.variants.last()
1502        && let Fields::Unnamed(unnamed) = &fallback.fields
1503        && let Some(field) = unnamed.fields.first()
1504        && let Some(ty) = field.ty.as_ref()
1505    {
1506        let mut fallback_ty = ty.clone();
1507        rewrite_datatype_for_phase(
1508            &mut fallback_ty,
1509            mode,
1510            original_types,
1511            generated,
1512            split_types,
1513            None,
1514        )?;
1515        variants.push((Cow::Borrowed(""), identifier_union_variant(fallback_ty)));
1516    }
1517
1518    e.attributes = Default::default();
1519    e.variants = variants;
1520    Ok(true)
1521}
1522
1523fn container_rename_all_rule(
1524    attrs: &specta::datatype::Attributes,
1525    mode: PhaseRewrite,
1526    context: &str,
1527    container_name: &str,
1528) -> Result<Option<RenameRule>, Error> {
1529    let attrs = SerdeContainerAttrs::from_attributes(attrs)?;
1530
1531    select_phase_rule(
1532        mode,
1533        attrs.as_ref().and_then(|attrs| attrs.rename_all_serialize),
1534        attrs
1535            .as_ref()
1536            .and_then(|attrs| attrs.rename_all_deserialize),
1537        context,
1538        container_name,
1539    )
1540}
1541
1542fn enum_variant_field_rename_rule(
1543    container_attrs: &Option<SerdeContainerAttrs>,
1544    variant: &Variant,
1545    mode: PhaseRewrite,
1546    variant_name: &str,
1547) -> Result<Option<RenameRule>, Error> {
1548    let variant_attrs = SerdeVariantAttrs::from_attributes(&variant.attributes)?;
1549
1550    let variant_rule = select_phase_rule(
1551        mode,
1552        variant_attrs
1553            .as_ref()
1554            .and_then(|attrs| attrs.rename_all_serialize),
1555        variant_attrs
1556            .as_ref()
1557            .and_then(|attrs| attrs.rename_all_deserialize),
1558        "enum variant rename_all",
1559        variant_name,
1560    )?;
1561
1562    if variant_rule.is_some() {
1563        return Ok(variant_rule);
1564    }
1565
1566    select_phase_rule(
1567        mode,
1568        container_attrs
1569            .as_ref()
1570            .and_then(|attrs| attrs.rename_all_fields_serialize),
1571        container_attrs
1572            .as_ref()
1573            .and_then(|attrs| attrs.rename_all_fields_deserialize),
1574        "enum rename_all_fields",
1575        variant_name,
1576    )
1577}
1578
1579fn identifier_union_variant(ty: DataType) -> Variant {
1580    let mut variant = Variant::unnamed().build();
1581    if let Fields::Unnamed(fields) = &mut variant.fields {
1582        fields.fields.push(Field::new(ty));
1583    }
1584    variant
1585}
1586
1587fn transform_untagged_variant(variant: &Variant) -> Result<Variant, Error> {
1588    let payload = variant_payload_field(variant)
1589        .ok_or_else(|| Error::invalid_external_tagged_variant("<untagged variant>"))?;
1590    Ok(clone_variant_with_unnamed_fields(variant, vec![payload]))
1591}
1592
1593fn filter_enum_variants_for_phase(e: &mut Enum, mode: PhaseRewrite) -> Result<(), Error> {
1594    let mut filter_err = None;
1595    e.variants.retain(|(_, variant)| {
1596        if variant.skip {
1597            return false;
1598        }
1599
1600        match SerdeVariantAttrs::from_attributes(&variant.attributes) {
1601            Ok(Some(attrs)) => !variant_is_skipped_for_mode(&attrs, mode),
1602            Ok(None) => true,
1603            Err(err) => {
1604                filter_err = Some(err);
1605                true
1606            }
1607        }
1608    });
1609
1610    if let Some(err) = filter_err {
1611        return Err(err);
1612    }
1613
1614    Ok(())
1615}
1616
1617fn variant_is_skipped_for_mode(attrs: &SerdeVariantAttrs, mode: PhaseRewrite) -> bool {
1618    match mode {
1619        PhaseRewrite::Serialize => attrs.skip_serializing,
1620        PhaseRewrite::Deserialize => attrs.skip_deserializing,
1621        PhaseRewrite::Unified => attrs.skip_serializing || attrs.skip_deserializing,
1622    }
1623}
1624
1625fn serialized_variant_name(
1626    variant_name: &str,
1627    variant: &Variant,
1628    container_attrs: &Option<SerdeContainerAttrs>,
1629    mode: PhaseRewrite,
1630) -> Result<String, Error> {
1631    let variant_attrs = SerdeVariantAttrs::from_attributes(&variant.attributes)?;
1632
1633    if let Some(rename) = select_phase_string(
1634        mode,
1635        variant_attrs
1636            .as_ref()
1637            .and_then(|attrs| attrs.rename_serialize.as_deref()),
1638        variant_attrs
1639            .as_ref()
1640            .and_then(|attrs| attrs.rename_deserialize.as_deref()),
1641        "enum variant rename",
1642        variant_name,
1643    )? {
1644        return Ok(rename.to_string());
1645    }
1646
1647    Ok(select_phase_rule(
1648        mode,
1649        container_attrs
1650            .as_ref()
1651            .and_then(|attrs| attrs.rename_all_serialize),
1652        container_attrs
1653            .as_ref()
1654            .and_then(|attrs| attrs.rename_all_deserialize),
1655        "enum rename_all",
1656        variant_name,
1657    )?
1658    .map_or_else(
1659        || variant_name.to_string(),
1660        |rule| rule.apply_to_variant(variant_name),
1661    ))
1662}
1663
1664fn select_phase_string<'a>(
1665    mode: PhaseRewrite,
1666    serialize: Option<&'a str>,
1667    deserialize: Option<&'a str>,
1668    context: &str,
1669    name: &str,
1670) -> Result<Option<&'a str>, Error> {
1671    Ok(match mode {
1672        PhaseRewrite::Serialize => serialize,
1673        PhaseRewrite::Deserialize => deserialize,
1674        PhaseRewrite::Unified => match (serialize, deserialize) {
1675            (Some(serialize), Some(deserialize)) if serialize != deserialize => {
1676                return Err(Error::incompatible_rename(
1677                    context.to_string(),
1678                    name,
1679                    Some(serialize.to_string()),
1680                    Some(deserialize.to_string()),
1681                ));
1682            }
1683            (serialize, deserialize) => serialize.or(deserialize),
1684        },
1685    })
1686}
1687
1688fn select_phase_rule(
1689    mode: PhaseRewrite,
1690    serialize: Option<RenameRule>,
1691    deserialize: Option<RenameRule>,
1692    context: &str,
1693    name: &str,
1694) -> Result<Option<RenameRule>, Error> {
1695    Ok(match mode {
1696        PhaseRewrite::Serialize => serialize,
1697        PhaseRewrite::Deserialize => deserialize,
1698        PhaseRewrite::Unified => match (serialize, deserialize) {
1699            (Some(serialize), Some(deserialize)) if serialize != deserialize => {
1700                return Err(Error::incompatible_rename(
1701                    context.to_string(),
1702                    name,
1703                    Some(format!("{serialize:?}")),
1704                    Some(format!("{deserialize:?}")),
1705                ));
1706            }
1707            (serialize, deserialize) => serialize.or(deserialize),
1708        },
1709    })
1710}
1711
1712fn resolve_phased_type(
1713    ty: &DataType,
1714    mode: PhaseRewrite,
1715    path: &str,
1716) -> Result<Option<DataType>, Error> {
1717    let DataType::Reference(Reference::Opaque(reference)) = ty else {
1718        return Ok(None);
1719    };
1720    let Some(phased) = reference.downcast_ref::<PhasedTy>() else {
1721        return Ok(None);
1722    };
1723
1724    Ok(match mode {
1725        // Note that we won't hit this if `TSerialize == TDeserialize` because it will just return `T` directly in the `impl Type for Phased<...>`
1726        PhaseRewrite::Unified => {
1727            return Err(Error::invalid_phased_type_usage(
1728                path,
1729                "`specta_serde::Phased<Serialize, Deserialize>` requires `PhasesFormat`",
1730            ));
1731        }
1732        PhaseRewrite::Serialize => Some(phased.serialize.clone()),
1733        PhaseRewrite::Deserialize => Some(phased.deserialize.clone()),
1734    })
1735}
1736
1737fn conversion_datatype_for_mode(
1738    ty: &DataType,
1739    mode: PhaseRewrite,
1740) -> Result<Option<DataType>, Error> {
1741    let attrs = match ty {
1742        DataType::Struct(s) => &s.attributes,
1743        DataType::Enum(e) => &e.attributes,
1744        _ => return Ok(None),
1745    };
1746
1747    select_conversion_target(attrs, mode)
1748}
1749
1750fn select_conversion_target(
1751    attrs: &specta::datatype::Attributes,
1752    mode: PhaseRewrite,
1753) -> Result<Option<DataType>, Error> {
1754    let parsed = SerdeContainerAttrs::from_attributes(attrs)?;
1755    let resolved = parsed.as_ref();
1756
1757    let serialize_target = resolved.and_then(|v| v.resolved_into.as_ref());
1758    let deserialize_target =
1759        resolved.and_then(|v| v.resolved_from.as_ref().or(v.resolved_try_from.as_ref()));
1760
1761    match mode {
1762        PhaseRewrite::Serialize => Ok(serialize_target.cloned()),
1763        PhaseRewrite::Deserialize => Ok(deserialize_target.cloned()),
1764        PhaseRewrite::Unified => match (serialize_target, deserialize_target) {
1765            (None, None) => Ok(None),
1766            (Some(serialize), Some(deserialize)) if serialize == deserialize => {
1767                Ok(Some(serialize.clone()))
1768            }
1769            _ => Err(Error::incompatible_conversion(
1770                "container conversion",
1771                resolved
1772                    .and_then(|attrs| {
1773                        attrs
1774                            .into
1775                            .as_ref()
1776                            .map(|v| format!("into({})", v.type_src))
1777                            .or_else(|| {
1778                                attrs.from.as_ref().map(|v| format!("from({})", v.type_src))
1779                            })
1780                            .or_else(|| {
1781                                attrs
1782                                    .try_from
1783                                    .as_ref()
1784                                    .map(|v| format!("try_from({})", v.type_src))
1785                            })
1786                    })
1787                    .unwrap_or_else(|| "<container>".to_string()),
1788                resolved.and_then(|attrs| attrs.into.as_ref().map(|v| v.type_src.clone())),
1789                resolved.and_then(|attrs| {
1790                    attrs.from.as_ref().map(|v| v.type_src.clone()).or_else(|| {
1791                        attrs
1792                            .try_from
1793                            .as_ref()
1794                            .map(|v| format!("try_from({})", v.type_src))
1795                    })
1796                }),
1797            )),
1798        },
1799    }
1800}
1801
1802fn transform_external_variant(
1803    serialized_name: String,
1804    variant: &Variant,
1805) -> Result<Variant, Error> {
1806    let skipped_only_unnamed = match &variant.fields {
1807        Fields::Unnamed(unnamed) => unnamed_fields_all_skipped(unnamed),
1808        Fields::Unit | Fields::Named(_) => false,
1809    };
1810
1811    Ok(match &variant.fields {
1812        Fields::Unit => clone_variant_with_unnamed_fields(
1813            variant,
1814            vec![Field::new(string_literal_datatype(serialized_name))],
1815        ),
1816        _ if skipped_only_unnamed => clone_variant_with_unnamed_fields(
1817            variant,
1818            vec![Field::new(string_literal_datatype(serialized_name))],
1819        ),
1820        _ => {
1821            let payload = variant_payload_field(variant)
1822                .ok_or_else(|| Error::invalid_external_tagged_variant(serialized_name.clone()))?;
1823
1824            clone_variant_with_named_fields(variant, vec![(Cow::Owned(serialized_name), payload)])
1825        }
1826    })
1827}
1828
1829fn transform_adjacent_variant(
1830    serialized_name: String,
1831    tag: &str,
1832    content: &str,
1833    variant: &Variant,
1834    widen_tag: bool,
1835) -> Result<Variant, Error> {
1836    let mut fields = vec![(
1837        Cow::Owned(tag.to_string()),
1838        Field::new(if widen_tag {
1839            DataType::Primitive(Primitive::str)
1840        } else {
1841            string_literal_datatype(serialized_name.clone())
1842        }),
1843    )];
1844
1845    if variant_has_effective_payload(variant) {
1846        let payload = variant_payload_field(variant)
1847            .ok_or_else(|| Error::invalid_adjacent_tagged_variant(serialized_name.clone()))?;
1848        fields.push((Cow::Owned(content.to_string()), payload));
1849    }
1850
1851    Ok(clone_variant_with_named_fields(variant, fields))
1852}
1853
1854fn transform_internal_variant(
1855    serialized_name: String,
1856    tag: &str,
1857    variant: &Variant,
1858    original_types: &Types,
1859    widen_tag: bool,
1860) -> Result<Variant, Error> {
1861    let mut fields = vec![(
1862        Cow::Owned(tag.to_string()),
1863        Field::new(if widen_tag {
1864            DataType::Primitive(Primitive::str)
1865        } else {
1866            string_literal_datatype(serialized_name.clone())
1867        }),
1868    )];
1869
1870    match &variant.fields {
1871        Fields::Unit => {}
1872        Fields::Named(named) => {
1873            fields.extend(named.fields.iter().cloned());
1874        }
1875        Fields::Unnamed(unnamed) => {
1876            let live_field_count = unnamed_live_field_count(unnamed);
1877
1878            if live_field_count == 0 {
1879                return Ok(clone_variant_with_named_fields(variant, fields));
1880            }
1881
1882            let non_skipped = unnamed_live_fields(unnamed).collect::<Vec<_>>();
1883
1884            if live_field_count != 1 {
1885                return Err(Error::invalid_internally_tagged_variant(
1886                    serialized_name,
1887                    "tuple variant must have exactly one non-skipped field",
1888                ));
1889            }
1890
1891            let payload_field = non_skipped
1892                .into_iter()
1893                .next()
1894                .expect("checked above")
1895                .clone();
1896            let payload_ty = payload_field.ty.clone().expect("checked above");
1897            let Some(payload_is_effectively_empty) = internal_tag_payload_compatibility(
1898                &payload_ty,
1899                original_types,
1900                &mut HashSet::new(),
1901            )?
1902            else {
1903                return Err(Error::invalid_internally_tagged_variant(
1904                    serialized_name,
1905                    "payload cannot be merged with a tag",
1906                ));
1907            };
1908
1909            if !payload_is_effectively_empty {
1910                return Ok(clone_variant_with_unnamed_fields(
1911                    variant,
1912                    vec![Field::new(DataType::Intersection(vec![
1913                        named_fields_datatype(fields),
1914                        payload_ty,
1915                    ]))],
1916                ));
1917            }
1918        }
1919    }
1920
1921    Ok(clone_variant_with_named_fields(variant, fields))
1922}
1923
1924fn named_fields_datatype(fields: Vec<(Cow<'static, str>, Field)>) -> DataType {
1925    let mut builder = Struct::named();
1926    for (name, field) in fields {
1927        builder = builder.field(name, field);
1928    }
1929
1930    builder.build()
1931}
1932
1933fn string_literal_datatype(value: String) -> DataType {
1934    let mut value_enum = Enum::default();
1935    value_enum
1936        .variants
1937        .push((Cow::Owned(value), Variant::unit()));
1938    DataType::Enum(value_enum)
1939}
1940
1941fn is_generated_string_literal_datatype(ty: &DataType) -> bool {
1942    let DataType::Enum(e) = ty else {
1943        return false;
1944    };
1945
1946    let Some((_, variant)) = e.variants.first() else {
1947        return false;
1948    };
1949
1950    if e.variants.len() != 1 {
1951        return false;
1952    }
1953
1954    match &variant.fields {
1955        Fields::Unit => true,
1956        Fields::Unnamed(fields) if fields.fields.len() == 1 => fields
1957            .fields
1958            .first()
1959            .and_then(|field| field.ty.as_ref())
1960            .is_some_and(is_generated_string_literal_datatype),
1961        _ => false,
1962    }
1963}
1964
1965fn variant_has_effective_payload(variant: &Variant) -> bool {
1966    match &variant.fields {
1967        Fields::Unit => false,
1968        Fields::Named(named) => !&named.fields.is_empty(),
1969        Fields::Unnamed(unnamed) => unnamed_has_effective_payload(unnamed),
1970    }
1971}
1972
1973fn variant_payload_field(variant: &Variant) -> Option<Field> {
1974    match &variant.fields {
1975        Fields::Unit => Some(Field::new(DataType::Tuple(Tuple::new(vec![])))),
1976        Fields::Named(named) => {
1977            let mut out = Struct::named();
1978            for (name, field) in named.fields.iter().cloned() {
1979                out.field_mut(name, field);
1980            }
1981            Some(Field::new(out.build()))
1982        }
1983        Fields::Unnamed(unnamed) => {
1984            let original_unnamed_len = unnamed.fields.len();
1985
1986            let non_skipped = unnamed_live_fields(unnamed).collect::<Vec<_>>();
1987
1988            match non_skipped.as_slice() {
1989                [] => Some(Field::new(DataType::Tuple(Tuple::new(vec![])))),
1990                [single] if original_unnamed_len == 1 => Some((*single).clone()),
1991                _ => Some(Field::new(DataType::Tuple(Tuple::new(
1992                    non_skipped
1993                        .iter()
1994                        .filter_map(|field| field.ty.clone())
1995                        .collect(),
1996                )))),
1997            }
1998        }
1999    }
2000}
2001
2002fn clone_variant_with_named_fields(
2003    original: &Variant,
2004    fields: Vec<(Cow<'static, str>, Field)>,
2005) -> Variant {
2006    let mut builder = Variant::named();
2007    for (name, field) in fields {
2008        builder = builder.field(name, field);
2009    }
2010
2011    let mut transformed = builder.build();
2012    transformed.skip = original.skip;
2013    transformed.docs = original.docs.clone();
2014    transformed.deprecated = original.deprecated.clone();
2015    transformed.attributes = original.attributes.clone();
2016    transformed
2017}
2018
2019fn clone_variant_with_unnamed_fields(original: &Variant, fields: Vec<Field>) -> Variant {
2020    let mut builder = Variant::unnamed();
2021    for field in fields {
2022        builder = builder.field(field);
2023    }
2024
2025    let mut transformed = builder.build();
2026    transformed.skip = original.skip;
2027    transformed.docs = original.docs.clone();
2028    transformed.deprecated = original.deprecated.clone();
2029    transformed.attributes = original.attributes.clone();
2030    transformed
2031}
2032
2033fn internal_tag_payload_compatibility(
2034    ty: &DataType,
2035    original_types: &Types,
2036    seen: &mut HashSet<TypeIdentity>,
2037) -> Result<Option<bool>, Error> {
2038    match ty {
2039        DataType::Map(_) => Ok(Some(false)),
2040        DataType::Struct(strct) => {
2041            if SerdeContainerAttrs::from_attributes(&strct.attributes)?
2042                .is_some_and(|attrs| attrs.transparent)
2043            {
2044                let payload_fields = match &strct.fields {
2045                    Fields::Unit => return Ok(Some(true)),
2046                    Fields::Unnamed(unnamed) => unnamed
2047                        .fields
2048                        .iter()
2049                        .filter_map(|field| field.ty.as_ref())
2050                        .collect::<Vec<_>>(),
2051                    Fields::Named(named) => named
2052                        .fields
2053                        .iter()
2054                        .filter_map(|(_, field)| field.ty.as_ref())
2055                        .collect::<Vec<_>>(),
2056                };
2057
2058                let [inner_ty] = payload_fields.as_slice() else {
2059                    if payload_fields.is_empty() {
2060                        return Ok(Some(true));
2061                    }
2062
2063                    return Ok(None);
2064                };
2065
2066                return internal_tag_payload_compatibility(inner_ty, original_types, seen);
2067            }
2068
2069            Ok(match &strct.fields {
2070                Fields::Named(named) => Some(
2071                    named
2072                        .fields
2073                        .iter()
2074                        .all(|(_, field)| field.ty.as_ref().is_none()),
2075                ),
2076                Fields::Unit | Fields::Unnamed(_) => None,
2077            })
2078        }
2079        DataType::Tuple(tuple) => Ok(tuple.elements.is_empty().then_some(true)),
2080        DataType::Intersection(types) => {
2081            let mut is_effectively_empty = true;
2082
2083            for ty in types {
2084                let Some(part_empty) =
2085                    internal_tag_payload_compatibility(ty, original_types, seen)?
2086                else {
2087                    return Ok(None);
2088                };
2089
2090                is_effectively_empty &= part_empty;
2091            }
2092
2093            Ok(Some(is_effectively_empty))
2094        }
2095        DataType::Reference(Reference::Named(reference)) => {
2096            if let NamedReferenceType::Inline { dt, .. } = &reference.inner {
2097                return internal_tag_payload_compatibility(dt, original_types, seen);
2098            }
2099
2100            let Some(referenced) = original_types.get(reference) else {
2101                return Ok(None);
2102            };
2103            let Some(referenced_ty) = referenced.ty.as_ref() else {
2104                return Ok(None);
2105            };
2106
2107            let key = TypeIdentity::from_ndt(referenced);
2108            if !seen.insert(key.clone()) {
2109                return Ok(Some(false));
2110            }
2111
2112            let compatible =
2113                internal_tag_payload_compatibility(referenced_ty, original_types, seen);
2114            seen.remove(&key);
2115            compatible
2116        }
2117        DataType::Enum(enm) => match EnumRepr::from_attrs(&enm.attributes) {
2118            Ok(EnumRepr::Untagged) => {
2119                let mut is_effectively_empty = true;
2120                for (_, variant) in &enm.variants {
2121                    let Some(variant_empty) =
2122                        internal_tag_variant_payload_compatibility(variant, original_types, seen)?
2123                    else {
2124                        return Ok(None);
2125                    };
2126
2127                    is_effectively_empty &= variant_empty;
2128                }
2129
2130                Ok(Some(is_effectively_empty))
2131            }
2132            Ok(EnumRepr::External | EnumRepr::Internal { .. } | EnumRepr::Adjacent { .. }) => {
2133                Ok(Some(false))
2134            }
2135            Err(_) => Ok(None),
2136        },
2137        DataType::Primitive(_)
2138        | DataType::List(_)
2139        | DataType::Nullable(_)
2140        | DataType::Reference(Reference::Opaque(_))
2141        | DataType::Generic(_) => Ok(None),
2142    }
2143}
2144
2145fn internal_tag_variant_payload_compatibility(
2146    variant: &Variant,
2147    original_types: &Types,
2148    seen: &mut HashSet<TypeIdentity>,
2149) -> Result<Option<bool>, Error> {
2150    match &variant.fields {
2151        Fields::Unit => Ok(Some(true)),
2152        Fields::Named(named) => Ok(Some(
2153            named
2154                .fields
2155                .iter()
2156                .all(|(_, field)| field.ty.as_ref().is_none()),
2157        )),
2158        Fields::Unnamed(unnamed) => {
2159            if unnamed.fields.len() != 1 {
2160                return Ok(None);
2161            }
2162
2163            unnamed
2164                .fields
2165                .iter()
2166                .find_map(|field| field.ty.as_ref())
2167                .map_or(Ok(None), |ty| {
2168                    internal_tag_payload_compatibility(ty, original_types, seen)
2169                })
2170        }
2171    }
2172}
2173
2174fn has_local_phase_difference(dt: &DataType) -> Result<bool, Error> {
2175    match dt {
2176        DataType::Struct(s) => Ok(container_has_local_difference(&s.attributes)?
2177            || fields_have_local_difference(&s.fields)?),
2178        DataType::Enum(e) => Ok(container_has_local_difference(&e.attributes)?
2179            || e.variants
2180                .iter()
2181                .try_fold(false, |has_difference, (_, variant)| {
2182                    if has_difference {
2183                        return Ok(true);
2184                    }
2185
2186                    Ok(variant_has_local_difference(variant)?
2187                        || fields_have_local_difference(&variant.fields)?)
2188                })?),
2189        DataType::Tuple(tuple) => tuple.elements.iter().try_fold(false, |has_difference, ty| {
2190            if has_difference {
2191                return Ok(true);
2192            }
2193
2194            has_local_phase_difference(ty)
2195        }),
2196        DataType::List(list) => has_local_phase_difference(&list.ty),
2197        DataType::Map(map) => Ok(has_local_phase_difference(map.key_ty())?
2198            || has_local_phase_difference(map.value_ty())?),
2199        DataType::Intersection(types_) => types_.iter().try_fold(false, |has_difference, ty| {
2200            if has_difference {
2201                return Ok(true);
2202            }
2203
2204            has_local_phase_difference(ty)
2205        }),
2206        DataType::Nullable(inner) => has_local_phase_difference(inner),
2207        DataType::Reference(Reference::Opaque(reference)) => {
2208            Ok(reference.downcast_ref::<PhasedTy>().is_some())
2209        }
2210        DataType::Primitive(_)
2211        | DataType::Reference(Reference::Named(_))
2212        | DataType::Generic(_) => Ok(false),
2213    }
2214}
2215
2216fn container_has_local_difference(attrs: &specta::datatype::Attributes) -> Result<bool, Error> {
2217    let Some(conversions) = SerdeContainerAttrs::from_attributes(attrs)? else {
2218        return Ok(false);
2219    };
2220
2221    Ok(conversions.resolved_into.as_ref()
2222        != conversions
2223            .resolved_from
2224            .as_ref()
2225            .or(conversions.resolved_try_from.as_ref())
2226        || conversions.rename_serialize != conversions.rename_deserialize
2227        || conversions.rename_all_serialize != conversions.rename_all_deserialize
2228        || conversions.rename_all_fields_serialize != conversions.rename_all_fields_deserialize
2229        || conversions.variant_identifier
2230        || conversions.field_identifier)
2231}
2232
2233fn fields_have_local_difference(fields: &Fields) -> Result<bool, Error> {
2234    match fields {
2235        Fields::Unit => Ok(false),
2236        Fields::Unnamed(unnamed) => {
2237            unnamed
2238                .fields
2239                .iter()
2240                .try_fold(false, |has_difference, field| {
2241                    if has_difference {
2242                        return Ok(true);
2243                    }
2244
2245                    Ok(field_has_local_difference(field)?
2246                        || field
2247                            .ty
2248                            .as_ref()
2249                            .map_or(Ok(false), has_local_phase_difference)?)
2250                })
2251        }
2252        Fields::Named(named) => {
2253            named
2254                .fields
2255                .iter()
2256                .try_fold(false, |has_difference, (_, field)| {
2257                    if has_difference {
2258                        return Ok(true);
2259                    }
2260
2261                    Ok(field_has_local_difference(field)?
2262                        || field
2263                            .ty
2264                            .as_ref()
2265                            .map_or(Ok(false), has_local_phase_difference)?)
2266                })
2267        }
2268    }
2269}
2270
2271fn field_has_local_difference(field: &Field) -> Result<bool, Error> {
2272    Ok(SerdeFieldAttrs::from_attributes(&field.attributes)?
2273        .map(|attrs| {
2274            attrs.rename_serialize.as_deref() != attrs.rename_deserialize.as_deref()
2275                || !attrs.aliases.is_empty()
2276                || attrs.skip_serializing != attrs.skip_deserializing
2277                || attrs.skip_serializing_if.is_some()
2278                || attrs.has_serialize_with
2279                || attrs.has_deserialize_with
2280                || attrs.has_with
2281        })
2282        .unwrap_or_default())
2283}
2284
2285fn variant_has_local_difference(variant: &Variant) -> Result<bool, Error> {
2286    Ok(SerdeVariantAttrs::from_attributes(&variant.attributes)?
2287        .map(|attrs| {
2288            attrs.rename_serialize.as_deref() != attrs.rename_deserialize.as_deref()
2289                || attrs.rename_all_serialize != attrs.rename_all_deserialize
2290                || !attrs.aliases.is_empty()
2291                || attrs.skip_serializing != attrs.skip_deserializing
2292                || attrs.has_serialize_with
2293                || attrs.has_deserialize_with
2294                || attrs.has_with
2295                || attrs.other
2296        })
2297        .unwrap_or_default())
2298}
2299
2300fn collect_dependencies(
2301    dt: &DataType,
2302    types: &Types,
2303    deps: &mut HashSet<TypeIdentity>,
2304) -> Result<(), Error> {
2305    match dt {
2306        DataType::Struct(s) => {
2307            collect_conversion_dependencies(&s.attributes, types, deps)?;
2308            collect_fields_dependencies(&s.fields, types, deps)?;
2309        }
2310        DataType::Enum(e) => {
2311            collect_conversion_dependencies(&e.attributes, types, deps)?;
2312            for (_, variant) in &e.variants {
2313                collect_fields_dependencies(&variant.fields, types, deps)?;
2314            }
2315        }
2316        DataType::Tuple(tuple) => {
2317            for ty in &tuple.elements {
2318                collect_dependencies(ty, types, deps)?;
2319            }
2320        }
2321        DataType::List(list) => collect_dependencies(&list.ty, types, deps)?,
2322        DataType::Map(map) => {
2323            collect_dependencies(map.key_ty(), types, deps)?;
2324            collect_dependencies(map.value_ty(), types, deps)?;
2325        }
2326        DataType::Intersection(types_) => {
2327            for ty in types_ {
2328                collect_dependencies(ty, types, deps)?;
2329            }
2330        }
2331        DataType::Nullable(inner) => collect_dependencies(inner, types, deps)?,
2332        DataType::Reference(Reference::Named(reference)) => {
2333            if let NamedReferenceType::Inline { dt, .. } = &reference.inner {
2334                collect_dependencies(dt, types, deps)?;
2335            }
2336
2337            if let Some(referenced) = types.get(reference) {
2338                deps.insert(TypeIdentity::from_ndt(referenced));
2339            }
2340
2341            for (_, generic) in named_reference_generics(reference) {
2342                collect_dependencies(generic, types, deps)?;
2343            }
2344        }
2345        DataType::Reference(Reference::Opaque(_)) => {
2346            if let DataType::Reference(Reference::Opaque(reference)) = dt
2347                && let Some(phased) = reference.downcast_ref::<PhasedTy>()
2348            {
2349                collect_dependencies(&phased.serialize, types, deps)?;
2350                collect_dependencies(&phased.deserialize, types, deps)?;
2351            }
2352        }
2353        DataType::Primitive(_) | DataType::Generic(_) => {}
2354    }
2355
2356    Ok(())
2357}
2358
2359fn collect_conversion_dependencies(
2360    attrs: &specta::datatype::Attributes,
2361    types: &Types,
2362    deps: &mut HashSet<TypeIdentity>,
2363) -> Result<(), Error> {
2364    let Some(conversions) = SerdeContainerAttrs::from_attributes(attrs)? else {
2365        return Ok(());
2366    };
2367
2368    for conversion in [
2369        conversions.resolved_into.as_ref(),
2370        conversions.resolved_from.as_ref(),
2371        conversions.resolved_try_from.as_ref(),
2372    ]
2373    .into_iter()
2374    .flatten()
2375    {
2376        collect_dependencies(conversion, types, deps)?;
2377    }
2378
2379    Ok(())
2380}
2381
2382fn collect_fields_dependencies(
2383    fields: &Fields,
2384    types: &Types,
2385    deps: &mut HashSet<TypeIdentity>,
2386) -> Result<(), Error> {
2387    match fields {
2388        Fields::Unit => {}
2389        Fields::Unnamed(unnamed) => {
2390            for field in &unnamed.fields {
2391                if let Some(ty) = field.ty.as_ref() {
2392                    collect_dependencies(ty, types, deps)?;
2393                }
2394            }
2395        }
2396        Fields::Named(named) => {
2397            for (_, field) in &named.fields {
2398                if let Some(ty) = field.ty.as_ref() {
2399                    collect_dependencies(ty, types, deps)?;
2400                }
2401            }
2402        }
2403    }
2404
2405    Ok(())
2406}
2407
2408fn build_from_original(
2409    original: &NamedDataType,
2410    mode: PhaseRewrite,
2411) -> Result<NamedDataType, Error> {
2412    let mut ndt = original.clone();
2413    ndt.name = Cow::Owned(split_type_name(original, mode)?);
2414
2415    Ok(ndt)
2416}
2417
2418fn register_generated_type(types: &mut Types, generated: NamedDataType) -> NamedDataType {
2419    NamedDataType::new(generated.name.clone(), types, move |_, ndt| {
2420        ndt.docs = generated.docs;
2421        ndt.deprecated = generated.deprecated;
2422        ndt.module_path = generated.module_path;
2423        ndt.location = generated.location;
2424        ndt.generics = generated.generics;
2425        ndt.ty = generated.ty;
2426    })
2427}
2428
2429fn rewrite_named_type_for_phase(ndt: &mut NamedDataType, mode: PhaseRewrite) -> Result<(), Error> {
2430    if let Some(ty) = &ndt.ty
2431        && let Some(rename) = renamed_type_name_for_phase(ty, mode, ndt.name.as_ref())?
2432    {
2433        ndt.name = Cow::Owned(rename);
2434    }
2435
2436    Ok(())
2437}
2438
2439fn split_type_name(original: &NamedDataType, mode: PhaseRewrite) -> Result<String, Error> {
2440    let suffix = match mode {
2441        PhaseRewrite::Serialize => "Serialize",
2442        PhaseRewrite::Deserialize => "Deserialize",
2443        PhaseRewrite::Unified => return Ok(original.name.to_string()),
2444    };
2445
2446    let base_name = original
2447        .ty
2448        .as_ref()
2449        .map(|ty| renamed_type_name_for_phase(ty, mode, original.name.as_ref()))
2450        .transpose()?
2451        .flatten()
2452        .unwrap_or_else(|| original.name.to_string());
2453
2454    Ok(format!("{base_name}_{suffix}"))
2455}
2456
2457fn renamed_type_name_for_phase(
2458    ty: &DataType,
2459    mode: PhaseRewrite,
2460    current_name: &str,
2461) -> Result<Option<String>, Error> {
2462    let DataType::Struct(strct) = ty else {
2463        return Ok(None);
2464    };
2465    let Some(attrs) = SerdeContainerAttrs::from_attributes(&strct.attributes)? else {
2466        return Ok(None);
2467    };
2468
2469    Ok(select_phase_string(
2470        mode,
2471        attrs.rename_serialize.as_deref(),
2472        attrs.rename_deserialize.as_deref(),
2473        "container rename",
2474        current_name,
2475    )?
2476    .map(str::to_string))
2477}
2478
2479fn apply_field_attrs(
2480    field: &mut Field,
2481    mode: PhaseRewrite,
2482    container_default: bool,
2483) -> Result<(), Error> {
2484    let mut optional = field.optional;
2485    if let Some(attrs) = SerdeFieldAttrs::from_attributes(&field.attributes)? {
2486        if field_is_optional_for_mode(Some(&attrs), container_default, mode) {
2487            optional = true;
2488        }
2489    } else if field_is_optional_for_mode(None, container_default, mode) {
2490        optional = true;
2491    }
2492    field.optional = optional;
2493
2494    Ok(())
2495}
2496
2497fn field_is_optional_for_mode(
2498    attrs: Option<&SerdeFieldAttrs>,
2499    container_default: bool,
2500    mode: PhaseRewrite,
2501) -> bool {
2502    match mode {
2503        PhaseRewrite::Serialize => false,
2504        PhaseRewrite::Deserialize | PhaseRewrite::Unified => {
2505            container_default
2506                || attrs.is_some_and(|attrs| attrs.default || attrs.skip_deserializing)
2507        }
2508    }
2509}
2510
2511#[cfg(test)]
2512mod tests {
2513    #![allow(clippy::panic)]
2514
2515    use serde::{Deserialize, Serialize};
2516    use specta::{Format as _, Type, Types, datatype::DataType};
2517
2518    use super::{
2519        Phase, Phased, PhasesFormat, parser, select_phase_datatype,
2520        validate::{ApplyMode, validate_datatype_for_mode},
2521    };
2522
2523    #[derive(Type, Serialize, Deserialize)]
2524    #[serde(untagged)]
2525    enum OneOrManyString {
2526        One(String),
2527        Many(Vec<String>),
2528    }
2529
2530    #[derive(Type, Serialize, Deserialize)]
2531    struct Filters {
2532        #[specta(type = Phased<Vec<String>, OneOrManyString>)]
2533        tags: Vec<String>,
2534    }
2535
2536    #[derive(Type, Serialize, Deserialize)]
2537    struct FilterList {
2538        items: Vec<Filters>,
2539    }
2540
2541    #[derive(Type, Serialize, Deserialize)]
2542    struct Plain {
2543        name: String,
2544    }
2545
2546    #[derive(Type, Serialize, Deserialize)]
2547    struct WithSkipIf {
2548        #[serde(default, skip_serializing_if = "Option::is_none")]
2549        nickname: Option<String>,
2550    }
2551
2552    #[test]
2553    fn selects_split_named_reference_for_each_phase() {
2554        let mut types = specta::Types::default();
2555        let dt = Filters::definition(&mut types);
2556        let resolved = formatted_phases(types);
2557
2558        let serialize = select_phase_datatype(&dt, &resolved, Phase::Serialize);
2559        let deserialize = select_phase_datatype(&dt, &resolved, Phase::Deserialize);
2560
2561        assert_named_reference(&serialize, &resolved, "Filters_Serialize");
2562        assert_named_reference(&deserialize, &resolved, "Filters_Deserialize");
2563    }
2564
2565    #[test]
2566    fn rewrites_nested_generics_for_each_phase() {
2567        let mut types = specta::Types::default();
2568        let dt = FilterList::definition(&mut types);
2569        let resolved = formatted_phases(types);
2570
2571        let serialize = select_phase_datatype(&dt, &resolved, Phase::Serialize);
2572        let deserialize = select_phase_datatype(&dt, &resolved, Phase::Deserialize);
2573
2574        assert_named_reference(&serialize, &resolved, "FilterList_Serialize");
2575        assert_named_reference(&deserialize, &resolved, "FilterList_Deserialize");
2576
2577        let serialize_inner = named_field_type(&serialize, &resolved, "items");
2578        let deserialize_inner = named_field_type(&deserialize, &resolved, "items");
2579
2580        assert_named_reference(
2581            list_item_type(serialize_inner),
2582            &resolved,
2583            "Filters_Serialize",
2584        );
2585        assert_named_reference(
2586            list_item_type(deserialize_inner),
2587            &resolved,
2588            "Filters_Deserialize",
2589        );
2590    }
2591
2592    #[test]
2593    fn preserves_unsplit_types() {
2594        let mut types = specta::Types::default();
2595        let dt = Plain::definition(&mut types);
2596        let resolved = formatted_phases(types);
2597
2598        let serialize = select_phase_datatype(&dt, &resolved, Phase::Serialize);
2599        let deserialize = select_phase_datatype(&dt, &resolved, Phase::Deserialize);
2600
2601        assert_named_reference(&serialize, &resolved, "Plain");
2602        assert_named_reference(&deserialize, &resolved, "Plain");
2603    }
2604
2605    #[test]
2606    fn clears_skip_serializing_if_attribute_after_phase_split() {
2607        let mut types = specta::Types::default();
2608        let dt = WithSkipIf::definition(&mut types);
2609        let resolved = formatted_phases(types);
2610
2611        let serialize = select_phase_datatype(&dt, &resolved, Phase::Serialize);
2612        let deserialize = select_phase_datatype(&dt, &resolved, Phase::Deserialize);
2613
2614        assert!(!field_has_skip_serializing_if(
2615            &serialize, &resolved, "nickname"
2616        ));
2617        assert!(!field_has_skip_serializing_if(
2618            &deserialize,
2619            &resolved,
2620            "nickname"
2621        ));
2622    }
2623
2624    #[test]
2625    fn phase_split_field_passes_unified_mode_validation() {
2626        // Regression test for the interaction with downstream callers (e.g.
2627        // tauri-specta's `validate_exported_command`) that run unified-mode
2628        // validation on the post-`apply_phases` graph. Before clearing the
2629        // attribute on phase-split fields, this would error with
2630        // "skip_serializing_if requires format_phases because unified mode
2631        // cannot represent conditional omission".
2632        let mut types = specta::Types::default();
2633        let dt = WithSkipIf::definition(&mut types);
2634        let resolved = formatted_phases(types);
2635
2636        let serialize = select_phase_datatype(&dt, &resolved, Phase::Serialize);
2637        let deserialize = select_phase_datatype(&dt, &resolved, Phase::Deserialize);
2638
2639        validate_datatype_for_mode(&serialize, &resolved, ApplyMode::Unified)
2640            .expect("Unified validation should accept phase-split _Serialize variant");
2641        validate_datatype_for_mode(&deserialize, &resolved, ApplyMode::Unified)
2642            .expect("Unified validation should accept phase-split _Deserialize variant");
2643    }
2644
2645    #[test]
2646    fn resolves_explicit_phased_datatypes_without_named_types() {
2647        let mut types = specta::Types::default();
2648        let dt = <Phased<String, Vec<String>>>::definition(&mut types);
2649        let resolved = formatted_phases(types);
2650
2651        let serialize = select_phase_datatype(&dt, &resolved, Phase::Serialize);
2652        let deserialize = select_phase_datatype(&dt, &resolved, Phase::Deserialize);
2653
2654        assert_named_reference(&serialize, &resolved, "String");
2655        assert_named_reference(&deserialize, &resolved, "Vec");
2656    }
2657
2658    fn assert_named_reference(dt: &DataType, types: &Types, expected_name: &str) {
2659        let DataType::Reference(specta::datatype::Reference::Named(reference)) = dt else {
2660            panic!("expected named reference");
2661        };
2662
2663        let actual = types
2664            .get(reference)
2665            .expect("reference should resolve")
2666            .name
2667            .as_ref();
2668
2669        assert_eq!(actual, expected_name);
2670    }
2671
2672    fn named_field_type<'a>(dt: &'a DataType, types: &'a Types, field_name: &str) -> &'a DataType {
2673        let DataType::Reference(specta::datatype::Reference::Named(reference)) = dt else {
2674            panic!("expected named reference");
2675        };
2676
2677        let named = types.get(reference).expect("reference should resolve");
2678        let Some(DataType::Struct(strct)) = &named.ty else {
2679            panic!("expected struct type");
2680        };
2681        let specta::datatype::Fields::Named(fields) = &strct.fields else {
2682            panic!("expected named fields");
2683        };
2684
2685        fields
2686            .fields
2687            .iter()
2688            .find_map(|(name, field)| (name == field_name).then_some(field.ty.as_ref()).flatten())
2689            .expect("field should exist")
2690    }
2691
2692    fn field_has_skip_serializing_if(dt: &DataType, types: &Types, field_name: &str) -> bool {
2693        let DataType::Reference(specta::datatype::Reference::Named(reference)) = dt else {
2694            panic!("expected named reference");
2695        };
2696        let named = types.get(reference).expect("reference should resolve");
2697        let Some(DataType::Struct(strct)) = &named.ty else {
2698            panic!("expected struct type");
2699        };
2700        let specta::datatype::Fields::Named(fields) = &strct.fields else {
2701            panic!("expected named fields");
2702        };
2703        fields
2704            .fields
2705            .iter()
2706            .find(|(name, _)| name == field_name)
2707            .map(|(_, field)| {
2708                field
2709                    .attributes
2710                    .contains_key(parser::FIELD_SKIP_SERIALIZING_IF)
2711            })
2712            .expect("field should exist")
2713    }
2714
2715    fn first_generic_type(dt: &DataType) -> &DataType {
2716        let DataType::Reference(specta::datatype::Reference::Named(reference)) = dt else {
2717            panic!("expected named reference with generics");
2718        };
2719
2720        let specta::datatype::NamedReferenceType::Reference { generics, .. } = &reference.inner
2721        else {
2722            panic!("expected named reference with generics");
2723        };
2724
2725        generics
2726            .first()
2727            .map(|(_, dt)| dt)
2728            .expect("expected first generic type")
2729    }
2730
2731    fn list_item_type(dt: &DataType) -> &DataType {
2732        let DataType::Reference(specta::datatype::Reference::Named(reference)) = dt else {
2733            panic!("expected inline list reference");
2734        };
2735
2736        let specta::datatype::NamedReferenceType::Inline { dt, .. } = &reference.inner else {
2737            return first_generic_type(dt);
2738        };
2739
2740        let DataType::List(list) = dt.as_ref() else {
2741            panic!("expected inline list");
2742        };
2743
2744        &list.ty
2745    }
2746
2747    fn formatted_phases(types: Types) -> Types {
2748        let format = PhasesFormat;
2749        format
2750            .map_types(&types)
2751            .expect("PhasesFormat should succeed")
2752            .into_owned()
2753    }
2754}