Skip to main content

serde_shape/
lib.rs

1// Copyright 2026 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # serde-shape
16//!
17//! `serde-shape` reflects the data model that a Rust type emits through Serde serialization or
18//! accepts through Serde deserialization. It builds a lightweight graph from type information and
19//! `#[serde(...)]` attributes without serializing or deserializing a value.
20//!
21//! Common uses are generating configuration reference docs, deriving environment-variable maps
22//! from config structs, documenting wire formats, and checking whether two versions of a type
23//! expose compatible Serde shapes.
24//!
25//! ## Getting started
26//!
27//! Enable the `derive` feature when you want `#[derive(SerializeShape)]` and
28//! `#[derive(DeserializeShape)]`:
29//!
30//! ```toml
31//! [dependencies]
32//! serde-shape = { version = "0.1.0", features = ["derive"] }
33//! ```
34//!
35//! Enable `std` when the reflected types use shapes provided only by the Rust standard library:
36//!
37//! ```toml
38//! [dependencies]
39//! serde-shape = { version = "0.1.0", features = ["derive", "std"] }
40//! ```
41//!
42//! The crate is `no_std` by default and requires `alloc`.
43//! The shape derives are independent of Serde's `Serialize` and `Deserialize` derives: they
44//! neither implement nor require those traits. Derive both sets when a type must also perform
45//! actual serialization or deserialization. `serde_shape` attributes affect reflection metadata
46//! only; they do not change Serde's runtime behavior.
47//!
48//! ## Inspecting a graph
49//!
50//! Derive [`trait@DeserializeShape`] for the type you want to inspect, then build a
51//! [`DeserializeShapeGraph`]:
52//!
53//! ```rust
54//! # #[cfg(feature = "derive")]
55//! # {
56//! use serde_shape::DeserializeDefinitionKind;
57//! use serde_shape::DeserializeShape;
58//! use serde_shape::FieldsStyle;
59//!
60//! #[derive(DeserializeShape)]
61//! #[serde(rename_all = "kebab-case", deny_unknown_fields)]
62//! struct Config {
63//!     http_port: u16,
64//!     peers: Vec<String>,
65//!     tls: Option<TlsConfig>,
66//! }
67//!
68//! #[derive(DeserializeShape)]
69//! #[serde(rename_all = "kebab-case")]
70//! struct TlsConfig {
71//!     cert_path: String,
72//!     key_path: String,
73//! }
74//!
75//! let graph = Config::deserialize_shape();
76//! let config = graph.root_definition().unwrap();
77//!
78//! let DeserializeDefinitionKind::Struct(shape) = &config.kind else {
79//!     panic!("Config should produce a struct shape");
80//! };
81//!
82//! assert_eq!(config.type_name.name, "Config");
83//! assert_eq!(shape.style, FieldsStyle::Struct);
84//! assert!(shape.attributes.deny_unknown_fields);
85//! assert_eq!(shape.fields[0].name, "http-port");
86//! assert_eq!(shape.fields[1].name, "peers");
87//! assert_eq!(shape.fields[2].name, "tls");
88//! # }
89//! ```
90//!
91//! Serialization and deserialization are reflected separately because Serde lets the two
92//! directions differ:
93//!
94//! ```rust
95//! # #[cfg(feature = "derive")]
96//! # {
97//! use serde_shape::DeserializeDefinitionKind;
98//! use serde_shape::DeserializeShape;
99//! use serde_shape::SerializeDefinitionKind;
100//! use serde_shape::SerializeShape;
101//!
102//! #[derive(SerializeShape, DeserializeShape)]
103//! #[serde(rename(serialize = "wire-output", deserialize = "wire-input"))]
104//! struct Message {
105//!     #[serde(rename(serialize = "out-id", deserialize = "in-id"))]
106//!     id: u64,
107//! }
108//!
109//! let serialize_graph = Message::serialize_shape();
110//! let deserialize_graph = Message::deserialize_shape();
111//! let serialize_definition = serialize_graph.root_definition().unwrap();
112//! let deserialize_definition = deserialize_graph.root_definition().unwrap();
113//!
114//! assert_eq!(serialize_definition.type_name.name, "wire-output");
115//! assert_eq!(deserialize_definition.type_name.name, "wire-input");
116//!
117//! let SerializeDefinitionKind::Struct(serialize_shape) = &serialize_definition.kind else {
118//!     panic!("Message should produce a struct serialization shape");
119//! };
120//! let DeserializeDefinitionKind::Struct(deserialize_shape) = &deserialize_definition.kind else {
121//!     panic!("Message should produce a struct deserialization shape");
122//! };
123//!
124//! assert_eq!(serialize_shape.fields[0].name, "out-id");
125//! assert_eq!(deserialize_shape.fields[0].name, "in-id");
126//! # }
127//! ```
128//!
129//! ## Shape graphs
130//!
131//! A shape graph has a [`ShapeRef`] root and a list of named definitions. Flat primitive and
132//! compound values are represented directly as [`ShapeRef`] values. Structs and enums are
133//! stored as named definitions and referenced by [`ShapeId`].
134//!
135//! Definition IDs are local to one graph. They contain an index but no graph identity, so callers
136//! must keep each ID paired with the graph that produced it. Use
137//! [`SerializeShapeGraph::definition`] or [`DeserializeShapeGraph::definition`] to resolve them.
138//! Definition ordering and debug output are not stable persistence formats.
139//! Definitions may be recursive, so graph walkers must detect repeated [`ShapeId`] values before
140//! following definition references.
141//!
142//! Types that branch on Serde's human-readable mode may expose a union of their known
143//! representations. Shape graphs describe possible semantic shapes across formats rather than
144//! specializing themselves for one serializer.
145//!
146//! [`ShapeRef`] is not a trace of exact serializer or deserializer method calls. It deliberately
147//! preserves useful Rust distinctions such as fixed arrays and pointer-width integers even when
148//! Serde dispatches them through tuple or fixed-width integer methods.
149//!
150//! ## Derive behavior
151//!
152//! The derive macros use Serde's derive metadata for the selected direction. They reflect
153//! directional names and skips, rename rules, aliases, defaults, enum tagging, flattening,
154//! transparent fields, identifier enums, conversion types, remote definitions, and custom
155//! serializer or deserializer boundaries.
156//!
157//! A custom serializer or deserializer has no inferable inner shape, so the affected field or
158//! variant content is represented by an opaque boundary. Whole-container conversion attributes
159//! use the conversion type's shape. Serde remote derives expose the helper definition's declared
160//! wire shape.
161//! Field-level [`FieldWireShape`] distinguishes ordinary values from flattened fields, inline
162//! transparent fields, and omitted fields. Custom serializer/deserializer boundaries use
163//! [`ShapeRef::Opaque`] and remain composable with those field positions.
164//!
165//! Use `#[serde_shape(serialize_with = "path")]` or
166//! `#[serde_shape(deserialize_with = "path")]` to declare the representation of a container,
167//! variant, or field that cannot be inferred. Each function receives the current graph context and
168//! returns a [`ShapeRef`], so it can delegate to another type or build a custom shape directly.
169//! These extensions supply reflection metadata only: they cannot rename, tag, skip, flatten,
170//! alias, or default a Serde item.
171//!
172//! Rust doc comments on derived containers, variants, and fields are preserved in their
173//! `description` fields for documentation and diagnostic consumers.
174//!
175//! ## Manual implementations
176//!
177//! Implement [`trait@SerializeShape`] or [`trait@DeserializeShape`] manually when a type's Serde
178//! representation is known but cannot be derived. This is common for wrappers that deserialize
179//! from a string or another primitive representation:
180//!
181//! ```rust
182//! use serde_shape::DeserializeShape;
183//! use serde_shape::DeserializeShapeContext;
184//! use serde_shape::ShapeRef;
185//!
186//! struct ByteSize(u64);
187//!
188//! impl DeserializeShape for ByteSize {
189//!     fn deserialize_shape_in(_context: &mut DeserializeShapeContext) -> ShapeRef {
190//!         ShapeRef::union([ShapeRef::String, ShapeRef::U64])
191//!     }
192//! }
193//!
194//! assert_eq!(
195//!     ByteSize::deserialize_shape().root(),
196//!     &ShapeRef::union([ShapeRef::String, ShapeRef::U64])
197//! );
198//! ```
199//!
200//! For recursive or shared named types, use [`SerializeShapeContext::define_named_type`] or
201//! [`DeserializeShapeContext::define_named_type`] so the graph contains one definition and
202//! all recursive edges point back to it.
203
204#![no_std]
205#![cfg_attr(docsrs, feature(doc_cfg))]
206#![deny(missing_docs)]
207
208extern crate alloc;
209extern crate self as serde_shape;
210#[cfg(feature = "std")]
211extern crate std;
212
213use alloc::boxed::Box;
214use alloc::collections::BTreeMap;
215use alloc::vec::Vec;
216use core::any::TypeId;
217use core::fmt;
218
219/// Private exports used by generated derive code.
220#[doc(hidden)]
221#[allow(missing_docs)]
222pub mod __private {
223    pub use alloc::vec;
224}
225
226#[cfg(feature = "derive")]
227#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
228/// Derives [`trait@DeserializeShape`] from Serde deserialization metadata.
229///
230/// Use this macro when a type's accepted input shape should be reflected from the same
231/// metadata that Serde uses for deserialization. The generated implementation records the
232/// deserialization-side names, shape graph, and Serde field/container metadata.
233///
234/// Use `#[serde_shape(deserialize_with = "path")]` on a container, variant, or field to
235/// override an opaque or foreign representation. The function must accept `&mut
236/// DeserializeShapeContext` and return a [`ShapeRef`]. Generic hooks can replace inferred
237/// bounds with `#[serde_shape(bound(deserialize = "T: DeserializeShape"))]` on the container.
238///
239/// # Example
240///
241/// ```rust
242/// use serde_shape::DefaultShape;
243/// use serde_shape::DeserializeDefinitionKind;
244/// use serde_shape::DeserializeShape;
245///
246/// #[derive(DeserializeShape)]
247/// #[serde(rename_all = "kebab-case")]
248/// struct Config {
249///     listen_addr: String,
250///     #[serde(default)]
251///     worker_count: u16,
252/// }
253///
254/// let graph = Config::deserialize_shape();
255/// let definition = graph.root_definition().unwrap();
256///
257/// let DeserializeDefinitionKind::Struct(shape) = &definition.kind else {
258///     panic!("Config should produce a struct shape");
259/// };
260///
261/// assert_eq!(shape.fields[0].name, "listen-addr");
262/// assert_eq!(shape.fields[1].name, "worker-count");
263/// assert_eq!(shape.fields[1].default, DefaultShape::Default);
264/// ```
265pub use serde_shape_derive::DeserializeShape;
266#[cfg(feature = "derive")]
267#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
268/// Derives [`trait@SerializeShape`] from Serde serialization metadata.
269///
270/// Use this macro when a type's emitted output shape should be reflected from the same
271/// metadata that Serde uses for serialization. The generated implementation records the
272/// serialization-side names, shape graph, and Serde field/container metadata.
273///
274/// Use `#[serde_shape(serialize_with = "path")]` on a container, variant, or field to override
275/// an opaque or foreign representation. The function must accept `&mut SerializeShapeContext`
276/// and return a [`ShapeRef`]. Generic hooks can replace inferred bounds with
277/// `#[serde_shape(bound(serialize = "T: SerializeShape"))]` on the container.
278///
279/// # Example
280///
281/// ```rust
282/// use serde_shape::SerializeDefinitionKind;
283/// use serde_shape::SerializeShape;
284///
285/// #[derive(SerializeShape)]
286/// #[serde(rename = "api-response", rename_all = "camelCase")]
287/// struct Response {
288///     request_id: u64,
289///     #[serde(skip_serializing_if = "Option::is_none")]
290///     next_page: Option<String>,
291/// }
292///
293/// let graph = Response::serialize_shape();
294/// let definition = graph.root_definition().unwrap();
295///
296/// let SerializeDefinitionKind::Struct(shape) = &definition.kind else {
297///     panic!("Response should produce a struct shape");
298/// };
299///
300/// assert_eq!(definition.type_name.name, "api-response");
301/// assert_eq!(shape.fields[0].name, "requestId");
302/// assert_eq!(shape.fields[1].name, "nextPage");
303/// assert!(shape.fields[1].skip_if.is_some());
304/// ```
305pub use serde_shape_derive::SerializeShape;
306
307mod impls;
308#[cfg(test)]
309mod tests;
310
311/// A type that can describe the shape emitted by its Serde serializer.
312pub trait SerializeShape {
313    /// Builds this type's serialization shape inside the provided context.
314    fn serialize_shape_in(context: &mut SerializeShapeContext) -> ShapeRef;
315
316    /// Builds a complete serialization shape graph rooted at this type.
317    fn serialize_shape() -> SerializeShapeGraph {
318        SerializeShapeGraph::for_type::<Self>()
319    }
320}
321
322/// A type that can describe the shape accepted by its Serde deserializer.
323pub trait DeserializeShape {
324    /// Builds this type's deserialization shape inside the provided context.
325    fn deserialize_shape_in(context: &mut DeserializeShapeContext) -> ShapeRef;
326
327    /// Builds a complete deserialization shape graph rooted at this type.
328    fn deserialize_shape() -> DeserializeShapeGraph {
329        DeserializeShapeGraph::for_type::<Self>()
330    }
331}
332
333/// A complete serialization shape graph rooted at one type.
334#[derive(Clone, Debug, Eq, PartialEq)]
335pub struct SerializeShapeGraph {
336    /// The root shape reference.
337    root: ShapeRef,
338    /// Named type definitions reachable from the root.
339    definitions: Vec<SerializeDefinitionShape>,
340}
341
342impl SerializeShapeGraph {
343    /// Builds a serialization graph from a function that returns its root shape.
344    ///
345    /// This is useful when the root type is foreign or when no Rust type corresponds to the
346    /// complete wire shape. Use [`Self::for_type`] when the root implements [`SerializeShape`].
347    pub fn from_fn<F>(build_root: F) -> Self
348    where
349        F: FnOnce(&mut SerializeShapeContext) -> ShapeRef,
350    {
351        let mut context = SerializeShapeContext::default();
352        let root = build_root(&mut context);
353        Self {
354            root,
355            definitions: context.finish(),
356        }
357    }
358
359    /// Builds a complete serialization shape graph rooted at `T`.
360    pub fn for_type<T>() -> Self
361    where
362        T: SerializeShape + ?Sized,
363    {
364        Self::from_fn(T::serialize_shape_in)
365    }
366
367    /// Returns the root shape reference.
368    pub fn root(&self) -> &ShapeRef {
369        &self.root
370    }
371
372    /// Returns the root definition when the graph root is a named type.
373    pub fn root_definition(&self) -> Option<&SerializeDefinitionShape> {
374        self.definition_for(self.root())
375    }
376
377    /// Returns the named definitions reachable from the root.
378    pub fn definitions(&self) -> &[SerializeDefinitionShape] {
379        &self.definitions
380    }
381
382    /// Returns the definition at this id's graph-local index.
383    ///
384    /// A [`ShapeId`] does not encode graph ownership. The caller must pass an id produced by this
385    /// graph; an id from another graph with an in-bounds index cannot be distinguished here.
386    pub fn definition(&self, id: ShapeId) -> Option<&SerializeDefinitionShape> {
387        self.definitions.get(id.0)
388    }
389
390    /// Returns the definition directly referenced by `shape`.
391    ///
392    /// Returns `None` for non-definition shapes and out-of-bounds definition indexes.
393    pub fn definition_for(&self, shape: &ShapeRef) -> Option<&SerializeDefinitionShape> {
394        let ShapeRef::Definition(id) = shape else {
395            return None;
396        };
397        self.definition(*id)
398    }
399}
400
401/// A complete deserialization shape graph rooted at one type.
402#[derive(Clone, Debug, Eq, PartialEq)]
403pub struct DeserializeShapeGraph {
404    /// The root shape reference.
405    root: ShapeRef,
406    /// Named type definitions reachable from the root.
407    definitions: Vec<DeserializeDefinitionShape>,
408}
409
410impl DeserializeShapeGraph {
411    /// Builds a deserialization graph from a function that returns its root shape.
412    ///
413    /// This lets a custom shape function describe a foreign root without introducing a wrapper
414    /// type solely to implement [`DeserializeShape`].
415    ///
416    /// ```rust
417    /// use serde_shape::DeserializeShapeContext;
418    /// use serde_shape::DeserializeShapeGraph;
419    /// use serde_shape::ShapeRef;
420    ///
421    /// fn duration_input(_context: &mut DeserializeShapeContext) -> ShapeRef {
422    ///     ShapeRef::union([ShapeRef::String, ShapeRef::U64])
423    /// }
424    ///
425    /// let graph = DeserializeShapeGraph::from_fn(duration_input);
426    /// assert_eq!(
427    ///     graph.root(),
428    ///     &ShapeRef::union([ShapeRef::String, ShapeRef::U64]),
429    /// );
430    /// ```
431    pub fn from_fn<F>(build_root: F) -> Self
432    where
433        F: FnOnce(&mut DeserializeShapeContext) -> ShapeRef,
434    {
435        let mut context = DeserializeShapeContext::default();
436        let root = build_root(&mut context);
437        Self {
438            root,
439            definitions: context.finish(),
440        }
441    }
442
443    /// Builds a complete deserialization shape graph rooted at `T`.
444    pub fn for_type<T>() -> Self
445    where
446        T: DeserializeShape + ?Sized,
447    {
448        Self::from_fn(T::deserialize_shape_in)
449    }
450
451    /// Returns the root shape reference.
452    pub fn root(&self) -> &ShapeRef {
453        &self.root
454    }
455
456    /// Returns the root definition when the graph root is a named type.
457    pub fn root_definition(&self) -> Option<&DeserializeDefinitionShape> {
458        self.definition_for(self.root())
459    }
460
461    /// Returns the named definitions reachable from the root.
462    pub fn definitions(&self) -> &[DeserializeDefinitionShape] {
463        &self.definitions
464    }
465
466    /// Returns the definition at this id's graph-local index.
467    ///
468    /// A [`ShapeId`] does not encode graph ownership. The caller must pass an id produced by this
469    /// graph; an id from another graph with an in-bounds index cannot be distinguished here.
470    pub fn definition(&self, id: ShapeId) -> Option<&DeserializeDefinitionShape> {
471        self.definitions.get(id.0)
472    }
473
474    /// Returns the definition directly referenced by `shape`.
475    ///
476    /// Returns `None` for non-definition shapes and out-of-bounds definition indexes.
477    pub fn definition_for(&self, shape: &ShapeRef) -> Option<&DeserializeDefinitionShape> {
478        let ShapeRef::Definition(id) = shape else {
479            return None;
480        };
481        self.definition(*id)
482    }
483}
484
485/// A context that accumulates named serialization definitions while a graph is built.
486#[derive(Debug, Default)]
487pub struct SerializeShapeContext {
488    definitions: Vec<Option<SerializeDefinitionShape>>,
489    definitions_by_identity: BTreeMap<(TypeId, &'static str), ShapeId>,
490}
491
492impl SerializeShapeContext {
493    /// Defines a named type once and returns a reference to its definition.
494    ///
495    /// The concrete builder type and diagnostic Rust name form the graph-local identity. Call this
496    /// method from one stable closure expression for every occurrence of the same named type.
497    pub fn define_named_type<F>(&mut self, type_name: TypeName, build: F) -> ShapeRef
498    where
499        F: FnOnce(&mut Self) -> SerializeDefinitionKind + 'static,
500    {
501        self.define_named_type_with_description(type_name, None, build)
502    }
503
504    /// Defines a named type with user-facing documentation.
505    ///
506    /// This behaves like [`Self::define_named_type`] and stores `description` on the resulting
507    /// definition.
508    pub fn define_named_type_with_description<F>(
509        &mut self,
510        type_name: TypeName,
511        description: Option<&'static str>,
512        build: F,
513    ) -> ShapeRef
514    where
515        F: FnOnce(&mut Self) -> SerializeDefinitionKind + 'static,
516    {
517        let identity = (TypeId::of::<F>(), type_name.rust_name);
518        if let Some(id) = self.definitions_by_identity.get(&identity) {
519            return ShapeRef::Definition(*id);
520        }
521
522        let id = ShapeId(self.definitions.len());
523        self.definitions_by_identity.insert(identity, id);
524        self.definitions.push(None);
525
526        let kind = build(self);
527        self.definitions[id.0] = Some(SerializeDefinitionShape {
528            id,
529            type_name,
530            description,
531            kind,
532        });
533        ShapeRef::Definition(id)
534    }
535
536    fn finish(self) -> Vec<SerializeDefinitionShape> {
537        self.definitions
538            .into_iter()
539            .map(|definition| definition.expect("shape definition was reserved but not filled"))
540            .collect()
541    }
542}
543
544/// A context that accumulates named deserialization definitions while a graph is built.
545#[derive(Debug, Default)]
546pub struct DeserializeShapeContext {
547    definitions: Vec<Option<DeserializeDefinitionShape>>,
548    definitions_by_identity: BTreeMap<(TypeId, &'static str), ShapeId>,
549}
550
551impl DeserializeShapeContext {
552    /// Defines a named type once and returns a reference to its definition.
553    ///
554    /// The concrete builder type and diagnostic Rust name form the graph-local identity. Call this
555    /// method from one stable closure expression for every occurrence of the same named type.
556    pub fn define_named_type<F>(&mut self, type_name: TypeName, build: F) -> ShapeRef
557    where
558        F: FnOnce(&mut Self) -> DeserializeDefinitionKind + 'static,
559    {
560        self.define_named_type_with_description(type_name, None, build)
561    }
562
563    /// Defines a named type with user-facing documentation.
564    ///
565    /// This behaves like [`Self::define_named_type`] and stores `description` on the resulting
566    /// definition.
567    pub fn define_named_type_with_description<F>(
568        &mut self,
569        type_name: TypeName,
570        description: Option<&'static str>,
571        build: F,
572    ) -> ShapeRef
573    where
574        F: FnOnce(&mut Self) -> DeserializeDefinitionKind + 'static,
575    {
576        let identity = (TypeId::of::<F>(), type_name.rust_name);
577        if let Some(id) = self.definitions_by_identity.get(&identity) {
578            return ShapeRef::Definition(*id);
579        }
580
581        let id = ShapeId(self.definitions.len());
582        self.definitions_by_identity.insert(identity, id);
583        self.definitions.push(None);
584
585        let kind = build(self);
586        self.definitions[id.0] = Some(DeserializeDefinitionShape {
587            id,
588            type_name,
589            description,
590            kind,
591        });
592        ShapeRef::Definition(id)
593    }
594
595    fn finish(self) -> Vec<DeserializeDefinitionShape> {
596        self.definitions
597            .into_iter()
598            .map(|definition| definition.expect("shape definition was reserved but not filled"))
599            .collect()
600    }
601}
602
603/// A graph-local identifier for a named shape definition.
604///
605/// An id does not carry the identity of its originating graph. Keep it paired with the graph that
606/// produced it.
607#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
608pub struct ShapeId(usize);
609
610impl ShapeId {
611    /// Returns this id's graph-local definition index.
612    pub const fn index(self) -> usize {
613        self.0
614    }
615}
616
617/// Names associated with a Rust type and one direction of its Serde representation.
618#[derive(Clone, Debug, Eq, PartialEq)]
619pub struct TypeName {
620    /// The fully qualified Rust type name, including generic arguments.
621    pub rust_name: &'static str,
622    /// The direction-specific Serde name after container rename rules are applied.
623    pub name: &'static str,
624}
625
626impl TypeName {
627    /// Builds names for `T` and one direction-specific Serde container name.
628    pub fn of<T>(name: &'static str) -> Self
629    where
630        T: ?Sized,
631    {
632        Self {
633            rust_name: core::any::type_name::<T>(),
634            name,
635        }
636    }
637}
638
639/// A reference to a shape node.
640#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
641#[non_exhaustive]
642pub enum ShapeRef {
643    /// Unit shape.
644    Unit,
645    /// Boolean shape.
646    Bool,
647    /// Character shape.
648    Char,
649    /// `i8` shape.
650    I8,
651    /// `i16` shape.
652    I16,
653    /// `i32` shape.
654    I32,
655    /// `i64` shape.
656    I64,
657    /// `i128` shape.
658    I128,
659    /// `isize` shape.
660    Isize,
661    /// `u8` shape.
662    U8,
663    /// `u16` shape.
664    U16,
665    /// `u32` shape.
666    U32,
667    /// `u64` shape.
668    U64,
669    /// `u128` shape.
670    U128,
671    /// `usize` shape.
672    Usize,
673    /// `f32` shape.
674    F32,
675    /// `f64` shape.
676    F64,
677    /// UTF-8 string shape.
678    String,
679    /// Serde byte-buffer data-model shape.
680    Bytes,
681    /// Optional value shape.
682    Option(Box<ShapeRef>),
683    /// Sequence shape.
684    Seq(Box<ShapeRef>),
685    /// Fixed-size array shape.
686    ///
687    /// Built-in array implementations follow Serde's supported lengths of 0 through 32. Manual
688    /// implementations may construct other lengths for custom representations.
689    Array {
690        /// The array item shape.
691        item: Box<ShapeRef>,
692        /// The array length.
693        len: usize,
694    },
695    /// Map shape.
696    Map {
697        /// The map key shape.
698        key: Box<ShapeRef>,
699        /// The map value shape.
700        value: Box<ShapeRef>,
701    },
702    /// Tuple shape.
703    Tuple(Vec<ShapeRef>),
704    /// A normalized union of two or more possible value shapes.
705    ///
706    /// Construct unions with [`ShapeRef::union`] or [`ShapeRef::try_union`].
707    Union(UnionShape),
708    /// Named type definition reference.
709    Definition(ShapeId),
710    /// Shape intentionally left opaque.
711    Opaque(OpaqueShape),
712}
713
714impl ShapeRef {
715    /// Builds a normalized union from one or more possible value shapes.
716    ///
717    /// Nested unions are flattened, duplicate alternatives are removed, and alternatives are
718    /// sorted into a canonical order. A single distinct alternative is returned directly.
719    ///
720    /// # Panics
721    ///
722    /// Panics when `alternatives` is empty. Use [`ShapeRef::try_union`] when the input may be
723    /// empty.
724    pub fn union<I>(alternatives: I) -> Self
725    where
726        I: IntoIterator<Item = Self>,
727    {
728        Self::try_union(alternatives).expect("shape union requires at least one alternative")
729    }
730
731    /// Tries to build a normalized union from possible value shapes.
732    ///
733    /// Returns `None` when `alternatives` is empty. Nested unions are flattened, duplicate
734    /// alternatives are removed, and alternatives are sorted into a canonical order. A single
735    /// distinct alternative is returned directly.
736    pub fn try_union<I>(alternatives: I) -> Option<Self>
737    where
738        I: IntoIterator<Item = Self>,
739    {
740        let mut normalized = Vec::new();
741        for alternative in alternatives {
742            match alternative {
743                Self::Union(union) => normalized.extend(union.alternatives),
744                alternative => normalized.push(alternative),
745            }
746        }
747        let mut alternatives = normalized;
748        alternatives.sort();
749        alternatives.dedup();
750
751        match alternatives.len() {
752            0 => None,
753            1 => alternatives.pop(),
754            _ => Some(Self::Union(UnionShape { alternatives })),
755        }
756    }
757
758    /// Returns `true` if this shape contains only signed integers.
759    pub fn is_signed_integer(&self) -> bool {
760        match self {
761            Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 | Self::Isize => true,
762            Self::Union(union) => union.alternatives.iter().all(Self::is_signed_integer),
763            _ => false,
764        }
765    }
766
767    /// Returns `true` if this shape contains only unsigned integers.
768    pub fn is_unsigned_integer(&self) -> bool {
769        match self {
770            Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128 | Self::Usize => true,
771            Self::Union(union) => union.alternatives.iter().all(Self::is_unsigned_integer),
772            _ => false,
773        }
774    }
775
776    /// Returns `true` if this shape contains only integers.
777    pub fn is_integer(&self) -> bool {
778        match self {
779            Self::Union(union) => union.alternatives.iter().all(Self::is_integer),
780            _ => self.is_signed_integer() || self.is_unsigned_integer(),
781        }
782    }
783
784    /// Returns `true` if this shape contains only floating-point values.
785    pub fn is_float(&self) -> bool {
786        match self {
787            Self::F32 | Self::F64 => true,
788            Self::Union(union) => union.alternatives.iter().all(Self::is_float),
789            _ => false,
790        }
791    }
792
793    /// Returns `true` if this shape contains only numeric values.
794    pub fn is_number(&self) -> bool {
795        match self {
796            Self::Union(union) => union.alternatives.iter().all(Self::is_number),
797            _ => self.is_integer() || self.is_float(),
798        }
799    }
800}
801
802/// The normalized alternatives contained by [`ShapeRef::Union`].
803///
804/// A union always contains at least two distinct alternatives in canonical order. Use
805/// [`ShapeRef::union`] or [`ShapeRef::try_union`] to construct one. Alternatives may overlap; a
806/// union means that any alternative is possible, not that exactly one alternative must match.
807#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
808pub struct UnionShape {
809    alternatives: Vec<ShapeRef>,
810}
811
812impl UnionShape {
813    /// Returns the canonical union alternatives.
814    pub fn alternatives(&self) -> &[ShapeRef] {
815        &self.alternatives
816    }
817}
818
819impl fmt::Debug for UnionShape {
820    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
821        self.alternatives.fmt(formatter)
822    }
823}
824
825/// A named type definition in a serialization shape graph.
826#[derive(Clone, Debug, Eq, PartialEq)]
827pub struct SerializeDefinitionShape {
828    /// The stable id of this definition inside its graph.
829    pub id: ShapeId,
830    /// The Rust and Serde names for this definition.
831    pub type_name: TypeName,
832    /// User-facing documentation for this definition, if available.
833    pub description: Option<&'static str>,
834    /// The definition body.
835    pub kind: SerializeDefinitionKind,
836}
837
838/// A named type definition in a deserialization shape graph.
839#[derive(Clone, Debug, Eq, PartialEq)]
840pub struct DeserializeDefinitionShape {
841    /// The stable id of this definition inside its graph.
842    pub id: ShapeId,
843    /// The Rust and Serde names for this definition.
844    pub type_name: TypeName,
845    /// User-facing documentation for this definition, if available.
846    pub description: Option<&'static str>,
847    /// The definition body.
848    pub kind: DeserializeDefinitionKind,
849}
850
851/// The body of a named serialization definition.
852#[derive(Clone, Debug, Eq, PartialEq)]
853pub enum SerializeDefinitionKind {
854    /// Struct-like Serde output.
855    Struct(SerializeStructShape),
856    /// Enum-like Serde output.
857    Enum(SerializeEnumShape),
858    /// Output shape that cannot be inferred faithfully.
859    Opaque(OpaqueShape),
860}
861
862/// The body of a named deserialization definition.
863#[derive(Clone, Debug, Eq, PartialEq)]
864pub enum DeserializeDefinitionKind {
865    /// Struct-like Serde input.
866    Struct(DeserializeStructShape),
867    /// Enum-like Serde input.
868    Enum(DeserializeEnumShape),
869    /// Input shape that cannot be inferred faithfully.
870    Opaque(OpaqueShape),
871}
872
873/// Container metadata relevant to serialization.
874///
875/// [`Default`] represents a container with every optional behavior disabled. Enum tagging is
876/// recorded once in [`SerializeEnumShape::repr`], and flattened fields are identified by
877/// [`FieldWireShape::Flatten`].
878#[derive(Clone, Debug, Default, Eq, PartialEq)]
879pub struct SerializeContainerAttributes {
880    /// Whether the Rust item is marked `#[non_exhaustive]`.
881    pub non_exhaustive: bool,
882}
883
884/// Container metadata relevant to deserialization.
885///
886/// [`Default`] represents a container with no default, expectation, or optional behavior
887/// configured. Enum tagging is recorded once in [`DeserializeEnumShape::repr`], and flattened
888/// fields are identified by [`FieldWireShape::Flatten`].
889#[derive(Clone, Debug, Default, Eq, PartialEq)]
890pub struct DeserializeContainerAttributes {
891    /// Whether unknown fields are rejected.
892    pub deny_unknown_fields: bool,
893    /// The default used for missing fields.
894    pub default: DefaultShape,
895    /// Custom Serde expectation text, if present.
896    pub expecting: Option<&'static str>,
897    /// Whether the Rust item is marked `#[non_exhaustive]`.
898    pub non_exhaustive: bool,
899}
900
901/// Serde container or enum tagging representation.
902///
903/// [`Default`] is [`Tagging::External`].
904#[derive(Clone, Debug, Default, Eq, PartialEq)]
905pub enum Tagging {
906    /// The default externally tagged representation.
907    #[default]
908    External,
909    /// `#[serde(tag = "...")]`.
910    Internal {
911        /// The tag field name.
912        tag: &'static str,
913    },
914    /// `#[serde(tag = "...", content = "...")]`.
915    Adjacent {
916        /// The tag field name.
917        tag: &'static str,
918        /// The content field name.
919        content: &'static str,
920    },
921    /// `#[serde(untagged)]`.
922    Untagged,
923    /// `#[serde(field_identifier)]`, accepted only during deserialization.
924    FieldIdentifier,
925    /// `#[serde(variant_identifier)]`, accepted only during deserialization.
926    VariantIdentifier,
927}
928
929/// Struct-like serialization metadata.
930#[derive(Clone, Debug, Eq, PartialEq)]
931pub struct SerializeStructShape {
932    /// The struct field style.
933    pub style: FieldsStyle,
934    /// The serialized fields.
935    pub fields: Vec<SerializeFieldShape>,
936    /// Container-level Serde serialization attributes.
937    pub attributes: SerializeContainerAttributes,
938}
939
940/// Struct-like deserialization metadata.
941#[derive(Clone, Debug, Eq, PartialEq)]
942pub struct DeserializeStructShape {
943    /// The struct field style.
944    pub style: FieldsStyle,
945    /// The accepted deserialization fields.
946    pub fields: Vec<DeserializeFieldShape>,
947    /// Container-level Serde deserialization attributes.
948    pub attributes: DeserializeContainerAttributes,
949}
950
951/// Enum-like serialization metadata.
952#[derive(Clone, Debug, Eq, PartialEq)]
953pub struct SerializeEnumShape {
954    /// The enum representation.
955    pub repr: Tagging,
956    /// The serialized variants.
957    pub variants: Vec<SerializeVariantShape>,
958    /// Container-level Serde serialization attributes.
959    pub attributes: SerializeContainerAttributes,
960}
961
962/// Enum-like deserialization metadata.
963#[derive(Clone, Debug, Eq, PartialEq)]
964pub struct DeserializeEnumShape {
965    /// The enum representation.
966    pub repr: Tagging,
967    /// The accepted deserialization variants.
968    pub variants: Vec<DeserializeVariantShape>,
969    /// Container-level Serde deserialization attributes.
970    pub attributes: DeserializeContainerAttributes,
971}
972
973/// The style of a struct, variant, or tuple field list.
974#[derive(Clone, Copy, Debug, Eq, PartialEq)]
975pub enum FieldsStyle {
976    /// Named fields.
977    Struct,
978    /// Multiple unnamed fields.
979    Tuple,
980    /// One unnamed field.
981    Newtype,
982    /// No fields.
983    Unit,
984}
985
986/// Field-level serialization metadata.
987#[derive(Clone, Debug, Eq, PartialEq)]
988pub struct SerializeFieldShape {
989    /// The original Rust field member.
990    pub member: FieldMember,
991    /// The primary Serde serialize name.
992    pub name: &'static str,
993    /// User-facing documentation for this field, if available.
994    pub description: Option<&'static str>,
995    /// How this field contributes to the serialized wire shape.
996    pub wire_shape: FieldWireShape,
997    /// The predicate used to skip this field during serialization, rendered as a parseable Rust
998    /// path token stream. Whitespace is not normalized.
999    pub skip_if: Option<&'static str>,
1000}
1001
1002/// Field-level deserialization metadata.
1003#[derive(Clone, Debug, Eq, PartialEq)]
1004pub struct DeserializeFieldShape {
1005    /// The original Rust field member.
1006    pub member: FieldMember,
1007    /// The primary Serde deserialize name.
1008    pub name: &'static str,
1009    /// All accepted Serde deserialize names, including the primary name.
1010    pub aliases: Vec<&'static str>,
1011    /// User-facing documentation for this field, if available.
1012    pub description: Option<&'static str>,
1013    /// How this field contributes to the deserialized wire shape.
1014    pub wire_shape: FieldWireShape,
1015    /// The default used if this field is missing.
1016    pub default: DefaultShape,
1017}
1018
1019/// The Rust member represented by a field.
1020#[derive(Clone, Debug, Eq, PartialEq)]
1021pub enum FieldMember {
1022    /// A named Rust field.
1023    Named(&'static str),
1024    /// An unnamed tuple field index.
1025    Unnamed(usize),
1026}
1027
1028/// How a field contributes to the wire representation in one Serde direction.
1029#[derive(Clone, Debug, Eq, PartialEq)]
1030#[non_exhaustive]
1031pub enum FieldWireShape {
1032    /// The field emits or accepts no value in this direction.
1033    Omitted,
1034    /// The field appears as a regular value at its Serde position.
1035    Value(ShapeRef),
1036    /// The field is flattened into the containing map.
1037    Flatten(ShapeRef),
1038    /// The field is serialized or deserialized directly at the containing type's position.
1039    Inline(ShapeRef),
1040}
1041
1042impl FieldWireShape {
1043    /// Returns the contributed value shape, or `None` when the field is omitted.
1044    ///
1045    /// This intentionally ignores whether the value is regular, flattened, or inline. Match on
1046    /// the enum directly when the field's wire position matters.
1047    pub fn shape(&self) -> Option<&ShapeRef> {
1048        match self {
1049            Self::Value(shape) | Self::Flatten(shape) | Self::Inline(shape) => Some(shape),
1050            Self::Omitted => None,
1051        }
1052    }
1053}
1054
1055/// Variant-level serialization metadata.
1056#[derive(Clone, Debug, Eq, PartialEq)]
1057pub struct SerializeVariantShape {
1058    /// The original Rust variant name.
1059    pub rust_name: &'static str,
1060    /// The primary Serde serialize name.
1061    pub name: &'static str,
1062    /// User-facing documentation for this variant, if available.
1063    pub description: Option<&'static str>,
1064    /// The variant field style.
1065    pub style: FieldsStyle,
1066    /// How the variant contributes its serialized content.
1067    pub content: SerializeVariantContent,
1068    /// Whether this variant is individually marked untagged.
1069    pub untagged: bool,
1070}
1071
1072/// The serialized content controlled by an enum variant.
1073#[derive(Clone, Debug, Eq, PartialEq)]
1074#[non_exhaustive]
1075pub enum SerializeVariantContent {
1076    /// The variant is omitted during serialization.
1077    Omitted,
1078    /// Serde derives the variant content from these fields.
1079    Fields(Vec<SerializeFieldShape>),
1080    /// A `serde_shape` hook supplies the content shape inside the enum's tagging representation.
1081    Shape(ShapeRef),
1082    /// A custom serializer controls the variant content.
1083    Custom(OpaqueShape),
1084}
1085
1086/// Variant-level deserialization metadata.
1087#[derive(Clone, Debug, Eq, PartialEq)]
1088pub struct DeserializeVariantShape {
1089    /// The original Rust variant name.
1090    pub rust_name: &'static str,
1091    /// The primary Serde deserialize name.
1092    pub name: &'static str,
1093    /// All accepted Serde deserialize names, including the primary name.
1094    pub aliases: Vec<&'static str>,
1095    /// User-facing documentation for this variant, if available.
1096    pub description: Option<&'static str>,
1097    /// The variant field style.
1098    pub style: FieldsStyle,
1099    /// How the variant contributes its deserialized content.
1100    pub content: DeserializeVariantContent,
1101    /// Whether this is a Serde `other` catch-all variant.
1102    pub other: bool,
1103    /// Whether this variant is individually marked untagged.
1104    pub untagged: bool,
1105}
1106
1107/// The deserialized content controlled by an enum variant.
1108#[derive(Clone, Debug, Eq, PartialEq)]
1109#[non_exhaustive]
1110pub enum DeserializeVariantContent {
1111    /// The variant is omitted during deserialization.
1112    Omitted,
1113    /// Serde derives the variant content from these fields.
1114    Fields(Vec<DeserializeFieldShape>),
1115    /// A `serde_shape` hook supplies the content shape inside the enum's tagging representation.
1116    Shape(ShapeRef),
1117    /// A custom deserializer controls the variant content.
1118    Custom(OpaqueShape),
1119}
1120
1121/// A Serde default marker.
1122///
1123/// [`Default`] is [`DefaultShape::None`].
1124#[derive(Clone, Debug, Default, Eq, PartialEq)]
1125pub enum DefaultShape {
1126    /// No default is configured.
1127    #[default]
1128    None,
1129    /// `Default::default()` is used.
1130    Default,
1131    /// A custom default function path is used. The value is a parseable Rust path token stream;
1132    /// whitespace is not normalized.
1133    Path(&'static str),
1134}
1135
1136impl DefaultShape {
1137    /// Returns `true` if no default is configured.
1138    pub fn is_none(&self) -> bool {
1139        matches!(self, Self::None)
1140    }
1141}
1142
1143/// An intentionally opaque shape.
1144#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
1145pub struct OpaqueShape {
1146    /// The Rust type or Serde item that is opaque.
1147    pub type_name: &'static str,
1148    /// Why the shape is opaque.
1149    pub reason: OpaqueReason,
1150    /// Additional human-readable detail.
1151    pub detail: Option<&'static str>,
1152}
1153
1154/// The reason a shape cannot be represented precisely.
1155#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
1156pub enum OpaqueReason {
1157    /// A custom serializer controls the output. Derive-generated `detail` is a parseable Rust path
1158    /// token stream whose whitespace is not normalized.
1159    CustomSerializer,
1160    /// A custom deserializer controls the input. Derive-generated `detail` is a parseable Rust
1161    /// path token stream whose whitespace is not normalized.
1162    CustomDeserializer,
1163    /// The type has no built-in shape implementation.
1164    Unsupported,
1165    /// The surrounding representation contains no values of this type.
1166    Unobserved,
1167}