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}