Skip to main content

pinapod/
traits.rs

1//! The representation contracts that a `PinaPod` schema implements.
2//!
3//! [`PinaPod`] marks a generated schema. [`PinaPodFixed`] and [`PinaPodCompact`] add the
4//! read, validation, and update operations for the two layouts, and [`PinaPodPatch`]
5//! carries one preflighted compact update.
6//!
7//! The remaining traits describe storage: [`ZcValidate`] checks that initialized bytes
8//! hold a semantic value, [`ZcElem`] states the unsafe representation contract for a
9//! stored type, and [`ZcField`] maps a native schema field to its stored pod.
10//!
11//! Implementing [`PinaPodFixed`], [`PinaPodCompact`], [`ZcElem`], or [`ZcField`] by hand
12//! is unsafe, because each carries a contract that safe readers rely on. Prefer
13//! `#[derive(PinaPod)]`.
14//!
15//! <!-- {=podMdtManagedDocNote|trim|linePrefix:"//! ":true} -->
16//! This section is synchronized by `mdt` and expands from `api-docs.t.md`. Edit the provider, then run `devenv shell docs:sync`.<!-- {/podMdtManagedDocNote} -->
17
18use crate::error::PinaPodError;
19use crate::pod::*;
20
21/// Validation trait for stored (pod) types.
22/// Each pod type knows how to validate itself.
23pub trait ZcValidate: Copy {
24	/// Validate that this value's bytes represent a valid state.
25	fn validate_ref(value: &Self) -> Result<(), PinaPodError>;
26
27	/// Validate every element of a stored array of this type.
28	///
29	/// An array's stored validity is exactly its elements' validity, so this
30	/// defaults to walking the array and calling
31	/// [`validate_ref`](Self::validate_ref) on each item. That walk is
32	/// load-bearing for any element with a restricted domain: it is what
33	/// rejects a non-canonical `PodBool` byte, an out-of-range container prefix,
34	/// or non-UTF-8 string bytes. An element type whose every initialized bit
35	/// pattern is valid overrides this with a no-op and loses no coverage.
36	///
37	/// The override exists because a `[T; N]` validation loop that merely
38	/// happens to be dead after inlining is not reliably removed at `-C
39	/// opt-level=3` on SBF. Stating the trivial case as a separate
40	/// implementation removes the loop from that instantiation outright, which
41	/// keeps byte arrays on the same per-element cost as a hand-written
42	/// `Ok(())`.
43	#[inline(always)]
44	fn validate_array<const N: usize>(value: &[Self; N]) -> Result<(), PinaPodError> {
45		for item in value {
46			Self::validate_ref(item)?;
47		}
48
49		Ok(())
50	}
51
52	/// Validate every element of a slice of this type.
53	///
54	/// A slice's stored validity is exactly its elements' validity, so this
55	/// defaults to walking the slice and calling
56	/// [`validate_ref`](Self::validate_ref) on each item. The same rationale as
57	/// [`validate_array`](Self::validate_array) applies: the loop is
58	/// load-bearing for a restricted-domain element and dead for a
59	/// trivially-valid one, and SBF `-C opt-level=3` does not reliably remove
60	/// the dead case. Stating the trivial case as a separate implementation
61	/// removes the loop from that instantiation outright.
62	///
63	/// Generated code walks a stored tail through this method rather than
64	/// spelling the loop at every emission site.
65	#[inline(always)]
66	fn validate_slice(value: &[Self]) -> Result<(), PinaPodError> {
67		for item in value {
68			Self::validate_ref(item)?;
69		}
70
71		Ok(())
72	}
73}
74
75// --- ZcValidate: trivially valid types (all bit patterns valid) ---
76
77impl ZcValidate for u8 {
78	#[inline(always)]
79	fn validate_ref(_: &Self) -> Result<(), PinaPodError> {
80		Ok(())
81	}
82
83	#[inline(always)]
84	fn validate_array<const N: usize>(_: &[Self; N]) -> Result<(), PinaPodError> {
85		Ok(())
86	}
87
88	#[inline(always)]
89	fn validate_slice(_: &[Self]) -> Result<(), PinaPodError> {
90		Ok(())
91	}
92}
93
94impl ZcValidate for i8 {
95	#[inline(always)]
96	fn validate_ref(_: &Self) -> Result<(), PinaPodError> {
97		Ok(())
98	}
99
100	#[inline(always)]
101	fn validate_array<const N: usize>(_: &[Self; N]) -> Result<(), PinaPodError> {
102		Ok(())
103	}
104
105	#[inline(always)]
106	fn validate_slice(_: &[Self]) -> Result<(), PinaPodError> {
107		Ok(())
108	}
109}
110
111macro_rules! impl_zc_validate_trivial {
112    ($($ty:ty),*) => {
113        $(
114            impl ZcValidate for $ty {
115                #[inline(always)]
116                fn validate_ref(_: &Self) -> Result<(), PinaPodError> { Ok(()) }
117
118                #[inline(always)]
119                fn validate_array<const N: usize>(_: &[Self; N]) -> Result<(), PinaPodError> {
120                    Ok(())
121                }
122
123                #[inline(always)]
124                fn validate_slice(_: &[Self]) -> Result<(), PinaPodError> { Ok(()) }
125            }
126        )*
127    };
128}
129
130impl_zc_validate_trivial!(
131	PodU16, PodU32, PodU64, PodU128, PodI16, PodI32, PodI64, PodI128
132);
133
134// Arrays validate per element. The element type decides how: a trivially valid
135// element answers with a no-op that contains no loop, while a restricted-domain
136// element walks every item. See `ZcValidate::validate_array`.
137impl<T: ZcValidate, const N: usize> ZcValidate for [T; N] {
138	#[inline(always)]
139	fn validate_ref(value: &Self) -> Result<(), PinaPodError> {
140		<T as ZcValidate>::validate_array(value)
141	}
142}
143
144// --- ZcValidate: PodBool (byte must be 0 or 1) ---
145
146impl ZcValidate for PodBool {
147	#[inline(always)]
148	fn validate_ref(value: &Self) -> Result<(), PinaPodError> {
149		// SAFETY: PodBool is #[repr(transparent)] over [u8; 1], alignment 1.
150		// Dereferencing as *const u8 reads the single stored byte.
151		let byte = unsafe { *(value as *const PodBool as *const u8) };
152		if byte > 1 {
153			Err(PinaPodError::InvalidBool)
154		} else {
155			Ok(())
156		}
157	}
158}
159
160// --- ZcValidate: PodString (len <= N, active bytes valid UTF-8) ---
161
162impl<const N: usize, const PFX: usize> ZcValidate for PodString<N, PFX> {
163	#[inline(always)]
164	fn validate_ref(value: &Self) -> Result<(), PinaPodError> {
165		let raw_len = value.try_decode_len()?;
166		if raw_len > N {
167			return Err(PinaPodError::InvalidLength);
168		}
169		// SAFETY: raw_len <= N, and data is a [MaybeUninit<u8>; N] array.
170		// The bytes come from account data (initialized memory), not
171		// MaybeUninit::uninit().
172		let bytes =
173			unsafe { core::slice::from_raw_parts(value.data.as_ptr() as *const u8, raw_len) };
174		if core::str::from_utf8(bytes).is_err() {
175			return Err(PinaPodError::InvalidUtf8);
176		}
177		Ok(())
178	}
179}
180
181// --- ZcValidate: PodVec (len <= N) ---
182
183impl<T: ZcElem, const N: usize, const PFX: usize> ZcValidate for PodVecRepr<T, N, PFX> {
184	#[inline(always)]
185	fn validate_ref(value: &Self) -> Result<(), PinaPodError> {
186		if value.try_decode_len()? > N {
187			return Err(PinaPodError::InvalidLength);
188		}
189		// Through `validate_slice` rather than a local loop, so a trivially
190		// valid element removes the loop here too.
191		T::validate_slice(value.as_slice())
192	}
193}
194
195// --- ZcValidate: PodOption (tag 0 or 1, inner valid if Some) ---
196
197impl<T: ZcElem, const PFX: usize> ZcValidate for PodOption<T, PFX> {
198	#[inline(always)]
199	fn validate_ref(value: &Self) -> Result<(), PinaPodError> {
200		match value.raw_tag() {
201			0 => Ok(()),
202			1 => {
203				// SAFETY: Tag validated as == 1 above, so the MaybeUninit value was
204				// initialized by PodOption::some() or deserialization.
205				let inner = unsafe { value.assume_init_ref() };
206				T::validate_ref(inner)
207			}
208			_ => Err(PinaPodError::InvalidTag),
209		}
210	}
211}
212
213/// # Safety
214///
215/// Implementors MUST guarantee all five of the following. Any violation
216/// makes the zero-copy pointer cast `&*(ptr as *const Self)` performed by
217/// the deserialization path undefined behavior.
218///
219/// 1. **Alignment == 1.** `core::mem::align_of::<Self>() == 1`, so casts
220///    from `*const u8` are well-defined at any byte offset.
221///
222/// 2. **No padding.** `Self` contains no padding bytes. Use
223///    `#[repr(transparent)]` over a single wrapped type, or `#[repr(C)]` /
224///    `#[repr(packed)]` composed only of `ZcElem` fields.
225///
226/// 3. **Validity invariant holds for every bit pattern.** It must be sound
227///    to form `&Self` from any `size_of::<Self>()` bytes of *initialized*
228///    memory, *before* `validate_ref` has been consulted. Forming an
229///    invalid reference is UB under Rust's aliasing rules regardless of
230///    whether the bytes are ever read. This rules out types whose validity
231///    is bit-pattern-restricted at the Rust type level — e.g., bare `bool`,
232///    `char`, `NonZero*`, or enums with fewer than `2^N` discriminants.
233///    Wrappers that store `u8` / `[u8; N]` and only *interpret* bytes at
234///    access time (like `PodBool`) are fine; a field of type `bool` is not.
235///
236/// 4. **`ZcValidate::validate_ref` is load-bearing.** It must reject every
237///    bit pattern whose reading through a safe accessor could cause UB or
238///    violate the type's documented invariants. For types where every
239///    initialized bit pattern is a semantically valid value (Pod integers,
240///    `[u8; N]`), `validate_ref` may trivially return `Ok(())`. For types
241///    with a restricted domain (`PodBool`, enums with fewer than `2^N`
242///    discriminants, length-prefix-bearing containers), `validate_ref` is
243///    the sole gate and MUST NOT short-circuit.
244///
245///    A type that overrides [`ZcValidate::validate_array`] takes on the same
246///    obligation for its array form. Overriding it with a no-op asserts that
247///    the element's whole domain is valid, exactly as a trivial
248///    `validate_ref` does, and an array of a restricted-domain type MUST
249///    keep the default per-element walk.
250///
251/// 5. **The all-zero representation is safe to inspect while initializing.**
252///    It does not need to be semantically valid, but safe accessors called on
253///    it must not cause undefined behavior. [`PinaPodFixed::initialize`] uses
254///    this state so callers can set enum fields whose valid discriminants do
255///    not include zero before the completed value is validated.
256pub unsafe trait ZcElem: Copy + ZcValidate {}
257
258// SAFETY: u8 and i8 are single bytes, trivially align 1, all bit patterns
259// valid.
260unsafe impl ZcElem for u8 {}
261unsafe impl ZcElem for i8 {}
262
263// SAFETY: All Pod integer types are #[repr(transparent)] over [u8; N], align 1.
264unsafe impl ZcElem for PodU16 {}
265unsafe impl ZcElem for PodU32 {}
266unsafe impl ZcElem for PodU64 {}
267unsafe impl ZcElem for PodU128 {}
268unsafe impl ZcElem for PodI16 {}
269unsafe impl ZcElem for PodI32 {}
270unsafe impl ZcElem for PodI64 {}
271unsafe impl ZcElem for PodI128 {}
272
273// SAFETY: PodBool is #[repr(transparent)] over [u8; 1], align 1.
274unsafe impl ZcElem for PodBool {}
275
276// SAFETY: `[T; N]` inherits alignment 1 from `T: ZcElem` —
277// `align_of::<[T; N]>() == align_of::<T>() == 1` — and its size is exactly
278// `N * size_of::<T>()`, so no padding bytes can exist between elements and
279// every element boundary falls on a valid byte offset. Element bit validity
280// composes: forming `&[T; N]` from any `N * size_of::<T>()` initialized bytes
281// is sound before `validate_ref` runs, and validation recurses per element.
282unsafe impl<T: ZcElem, const N: usize> ZcElem for [T; N] {}
283
284// SAFETY: PodOption<T: ZcElem, PFX> is #[repr(C)] with tag: [u8; PFX] + MaybeUninit<T>.
285// T: ZcElem guarantees T is align 1, so PodOption<T, PFX> is also align 1.
286unsafe impl<T: ZcElem, const PFX: usize> ZcElem for PodOption<T, PFX> {}
287
288// SAFETY: PodString<N, PFX> is #[repr(C)] over [u8; PFX] + [MaybeUninit<u8>; N],
289// both align 1 with no padding. Every initialized bit pattern is a valid
290// reference (raw bytes, no restricted-domain fields); validate_ref gates
291// len <= N and UTF-8 of the active bytes.
292unsafe impl<const N: usize, const PFX: usize> ZcElem for PodString<N, PFX> {}
293
294// SAFETY: PodVecRepr<T: ZcElem, N, PFX> is #[repr(C)] over [u8; PFX] +
295// [MaybeUninit<T>; N]. T: ZcElem guarantees T is align 1, so the struct is
296// align 1 with no padding. Every initialized bit pattern is a valid reference
297// (T's own validity holds for any bit pattern); validate_ref gates len <= N
298// and recurses into each element.
299unsafe impl<T: ZcElem, const N: usize, const PFX: usize> ZcElem for PodVecRepr<T, N, PFX> {}
300
301// --- Feature-gated impls for external types ---
302
303#[cfg(feature = "solana-address")]
304mod solana_address_impls {
305	use super::*;
306
307	const _: () = assert!(core::mem::align_of::<solana_address::Address>() == 1);
308
309	// SAFETY: solana_address::Address is #[repr(transparent)] over [u8; 32],
310	// align 1, all bit patterns valid.
311	impl ZcValidate for solana_address::Address {
312		#[inline(always)]
313		fn validate_ref(_: &Self) -> Result<(), PinaPodError> {
314			Ok(())
315		}
316
317		#[inline(always)]
318		fn validate_array<const N: usize>(_: &[Self; N]) -> Result<(), PinaPodError> {
319			Ok(())
320		}
321
322		#[inline(always)]
323		fn validate_slice(_: &[Self]) -> Result<(), PinaPodError> {
324			Ok(())
325		}
326	}
327
328	// SAFETY: Address is Copy, align 1, all bit patterns valid.
329	unsafe impl ZcElem for solana_address::Address {}
330
331	unsafe impl ZcField for solana_address::Address {
332		type Pod = solana_address::Address;
333	}
334}
335
336/// Describes the byte layout generated for a schema type.
337///
338/// `PinaPod` is the common public contract implemented by the derive. Fixed
339/// and compact layouts provide their specialized operations through
340/// [`PinaPodFixed`] and [`PinaPodCompact`].
341///
342/// Type names alone never grant a built-in representation. A caller-local
343/// lookalike must provide the unsafe representation contract explicitly:
344///
345/// ```compile_fail
346/// use pinapod::PinaPod;
347///
348/// #[allow(non_camel_case_types)]
349/// struct i8(bool);
350///
351/// #[derive(PinaPod)]
352/// struct Invalid {
353///     value: i8,
354/// }
355/// ```
356pub trait PinaPod: Sized {}
357
358/// Zero-copy access for a schema whose complete representation has one size.
359///
360/// # Safety
361///
362/// Implementors must ensure `Zc` is the complete fixed representation of
363/// `Self`. Its byte size and validation rules must not depend on runtime state.
364/// Prefer `#[derive(PinaPod)]`; manual implementations are an advanced raw API.
365pub unsafe trait PinaPodFixed: PinaPod {
366	/// The complete fixed representation of this schema.
367	///
368	/// `size_of::<Zc>()` is the schema's size, so the size must not depend on runtime
369	/// state. A derive generates a companion struct whose fields are the mapped pods in
370	/// declaration order.
371	type Zc: ZcElem;
372
373	/// Read one fixed value and reject both truncated and trailing bytes.
374	fn read_exact(data: &[u8]) -> Result<&Self::Zc, PinaPodError> {
375		Self::validate_exact(data)?;
376
377		// SAFETY: validate_exact proves the slice has exactly one complete
378		// representation and ZcElem guarantees alignment one and bit validity.
379		Ok(unsafe { &*data.as_ptr().cast::<Self::Zc>() })
380	}
381
382	/// Mutably read one fixed value and reject truncated and trailing bytes.
383	fn read_exact_mut(data: &mut [u8]) -> Result<&mut Self::Zc, PinaPodError> {
384		Self::validate_exact(data)?;
385
386		// SAFETY: validate_exact proves the slice has exactly one complete
387		// representation and ZcElem guarantees alignment one and bit validity.
388		Ok(unsafe { &mut *data.as_mut_ptr().cast::<Self::Zc>() })
389	}
390
391	/// Read the first fixed value from a larger containing byte sequence.
392	fn read_prefix(data: &[u8]) -> Result<&Self::Zc, PinaPodError> {
393		Self::validate_prefix(data)?;
394
395		// SAFETY: validate_prefix proves that the first representation-sized
396		// prefix is valid. ZcElem guarantees alignment one.
397		Ok(unsafe { &*data.as_ptr().cast::<Self::Zc>() })
398	}
399
400	/// Mutably read the first fixed value from a larger containing sequence.
401	fn read_prefix_mut(data: &mut [u8]) -> Result<&mut Self::Zc, PinaPodError> {
402		Self::validate_prefix(data)?;
403
404		// SAFETY: validate_prefix proves that the first SIZE bytes contain a
405		// valid representation. ZcElem guarantees alignment one.
406		Ok(unsafe { &mut *data.as_mut_ptr().cast::<Self::Zc>() })
407	}
408
409	/// Validate one complete fixed value with no trailing bytes.
410	fn validate_exact(data: &[u8]) -> Result<(), PinaPodError> {
411		let size = core::mem::size_of::<Self::Zc>();
412
413		if data.len() != size {
414			if data.len() < size {
415				return Err(PinaPodError::BufferTooSmall);
416			}
417			return Err(PinaPodError::InvalidLength);
418		}
419
420		// SAFETY: the length check proves a complete representation is present
421		// and ZcElem permits forming a reference from initialized bytes before
422		// semantic validation.
423		let value = unsafe { &*data.as_ptr().cast::<Self::Zc>() };
424		<Self::Zc as ZcValidate>::validate_ref(value)
425	}
426
427	/// Validate the first fixed value in a larger containing byte sequence.
428	fn validate_prefix(data: &[u8]) -> Result<(), PinaPodError> {
429		let size = core::mem::size_of::<Self::Zc>();
430
431		if data.len() < size {
432			return Err(PinaPodError::BufferTooSmall);
433		}
434
435		// SAFETY: the length check proves a complete representation is present
436		// and ZcElem permits forming a reference from initialized bytes before
437		// semantic validation.
438		let value = unsafe { &*data.as_ptr().cast::<Self::Zc>() };
439		<Self::Zc as ZcValidate>::validate_ref(value)
440	}
441
442	/// Initialize exactly one fixed value and validate it after configuration.
443	///
444	/// The destination is zeroed before `initialize` is called, so the closure
445	/// can set fields such as enums whose valid discriminants exclude zero.
446	/// Validation runs once, after the closure returns successfully.
447	///
448	/// If the closure or validation returns an error, the complete destination
449	/// is zeroed again. This deterministic failure state is fully initialized,
450	/// but it is not necessarily a semantically valid value for the schema.
451	fn initialize(
452		data: &mut [u8],
453		initialize: impl FnOnce(&mut Self::Zc) -> Result<(), PinaPodError>,
454	) -> Result<&mut Self::Zc, PinaPodError> {
455		let size = core::mem::size_of::<Self::Zc>();
456
457		if data.len() < size {
458			return Err(PinaPodError::BufferTooSmall);
459		}
460
461		if data.len() != size {
462			return Err(PinaPodError::InvalidLength);
463		}
464
465		data.fill(0);
466
467		let pointer = data.as_mut_ptr().cast::<Self::Zc>();
468		let result = {
469			// SAFETY: the exact length is checked above, ZcElem has alignment
470			// one, and its unsafe contract makes the all-zero initialization
471			// state safe to inspect and mutate before semantic validation.
472			let value = unsafe { &mut *pointer };
473
474			initialize(value).and_then(|()| <Self::Zc as ZcValidate>::validate_ref(value))
475		};
476
477		if let Err(error) = result {
478			data.fill(0);
479
480			return Err(error);
481		}
482
483		// SAFETY: the closure completed and validate_ref accepted the same
484		// representation. The mutable borrow of `data` remains exclusive.
485		Ok(unsafe { &mut *pointer })
486	}
487}
488
489/// Zero-copy access for compact schemas with a fixed header and dynamic tails.
490///
491/// # Safety
492///
493/// Implementors must ensure `Header`, `HEADER_SIZE`, and `validate` describe
494/// the same representation. Dynamic length metadata must not be exposed for
495/// direct mutable access.
496pub unsafe trait PinaPodCompact: PinaPod {
497	/// The fixed header that precedes the dynamic tails.
498	///
499	/// Its size must equal [`HEADER_SIZE`](Self::HEADER_SIZE).
500	type Header: ZcElem;
501
502	/// Smallest valid allocation for this compact schema.
503	const MIN_SIZE: usize;
504
505	/// Largest valid allocation for this compact schema.
506	const MAX_SIZE: usize;
507
508	/// Byte granularity of valid allocation growth beyond [`Self::MIN_SIZE`].
509	const TAIL_ALIGNMENT: usize;
510
511	/// Byte size of [`Header`](Self::Header).
512	///
513	/// Implementations must keep this equal to `size_of::<Self::Header>()`;
514	/// [`validate`](Self::validate) relies on it to locate the first tail.
515	const HEADER_SIZE: usize;
516
517	/// Validate the physical allocation independently of its active contents.
518	fn validate_storage_len(size: usize) -> Result<(), PinaPodError> {
519		if Self::TAIL_ALIGNMENT == 0
520			|| size < Self::MIN_SIZE
521			|| size > Self::MAX_SIZE
522			|| !(size - Self::MIN_SIZE).is_multiple_of(Self::TAIL_ALIGNMENT)
523		{
524			return Err(PinaPodError::InvalidLength);
525		}
526
527		Ok(())
528	}
529
530	/// Validate one complete compact representation.
531	///
532	/// Implementations must check the allocation through
533	/// [`validate_storage_len`](Self::validate_storage_len) first, then walk the header
534	/// and every tail: option tags, length prefixes, field capacities, offsets, and
535	/// UTF-8. A caller may form references only after this returns `Ok`.
536	///
537	/// # Errors
538	///
539	/// Returns [`PinaPodError::InvalidLength`] for an allocation outside the schema's
540	/// size bounds or tail granularity, and another [`PinaPodError`] variant when a
541	/// stored value is not a valid representation.
542	fn validate(data: &[u8]) -> Result<(), PinaPodError>;
543
544	/// Validate only what a tail relocation needs: the allocation, the header's
545	/// length prefixes and tags, and the chained tail bounds.
546	///
547	/// Relocating a tail reads exactly three things from the buffer —
548	/// `data.len()`, the header size, and the stored length prefixes — then
549	/// performs checked adds and moves bytes between the offsets they imply.
550	/// Copying arbitrary bytes is safe, so the only hazard is an offset or end
551	/// computed past the buffer. This method proves that hazard absent without
552	/// the per-element semantic walk [`validate`](Self::validate) also performs:
553	/// no element iteration, no UTF-8 check, and no header field validation
554	/// beyond what the bounds require.
555	///
556	/// Use this where the bytes are about to be relocated but not interpreted.
557	/// Every boundary that exposes or persists a *value* — a reader's
558	/// constructor, an update's preflight, and initialization's post-commit
559	/// check — must keep calling [`validate`](Self::validate).
560	///
561	/// The default falls back to [`validate`](Self::validate), so a hand-written
562	/// implementation keeps the full check and cannot weaken itself by
563	/// accident. Returns the same error variants as
564	/// [`validate`](Self::validate) for the conditions it checks.
565	///
566	/// # Errors
567	///
568	/// Returns [`PinaPodError::InvalidLength`] for an allocation outside the
569	/// schema's size bounds or tail granularity, a prefix above its field
570	/// capacity, or an unreadable prefix width; [`PinaPodError::BufferTooSmall`]
571	/// when a chained tail bound leaves the buffer; and
572	/// [`PinaPodError::Overflow`] when an offset sum overflows.
573	fn validate_layout(data: &[u8]) -> Result<(), PinaPodError> {
574		Self::validate(data)
575	}
576}
577
578/// Run the commit-entry check at the depth the current build selects.
579///
580/// Generated `commit` bodies call this rather than naming a depth directly,
581/// because of how cargo features resolve. A `#[cfg(feature = "...")]` is
582/// evaluated in the crate being compiled — the *consumer* of this crate — so a
583/// cfg written into generated code would test the downstream crate's feature
584/// namespace instead of this one. Where the name is undefined, the generated
585/// code would silently take the disabled branch and the feature would mean
586/// nothing. Keeping the dispatch here makes the feature mean what it says.
587///
588/// The default is [`PinaPodCompact::validate_layout`]. Relocation consumes
589/// only the buffer length, the header size, and the stored prefixes, so layout
590/// is the complete precondition for it, and this fails closed on a corrupt
591/// prefix or a short buffer exactly as a full walk would. Enabling
592/// `compact-commit-full-validation` widens the check to
593/// [`PinaPodCompact::validate`]: one full semantic walk per commit, which is
594/// what 0.4.2 did. That is a strictly stronger commit, not a weaker one, so
595/// the feature can only add coverage.
596///
597/// # Errors
598///
599/// Returns whatever the selected depth returns; both reject a corrupt prefix,
600/// an allocation outside the schema's bounds, and a tail bound that leaves the
601/// buffer.
602#[doc(hidden)]
603#[inline(always)]
604pub fn commit_entry_validate<T: PinaPodCompact>(data: &[u8]) -> Result<(), PinaPodError> {
605	#[cfg(feature = "compact-commit-full-validation")]
606	{
607		T::validate(data)
608	}
609
610	#[cfg(not(feature = "compact-commit-full-validation"))]
611	{
612		T::validate_layout(data)
613	}
614}
615
616/// An atomic, preflighted update for one compact schema.
617///
618/// Patches borrow semantic input values but never expose raw offsets, length
619/// prefixes, or partially committed mutation state. Frameworks can use this
620/// trait to plan a resize, release the old borrow, and apply the same patch to
621/// the resized allocation.
622pub trait PinaPodPatch<T: PinaPodCompact> {
623	/// The allocation size [`update`](Self::update) would produce, without changing `data`.
624	///
625	/// Frameworks call this to plan a resize, release the old borrow, and then apply the
626	/// same patch to the resized allocation.
627	///
628	/// # Errors
629	///
630	/// Returns [`PinaPodError`] when the supplied values cannot be encoded or `data` is
631	/// not a valid existing representation.
632	fn updated_len(&self, data: &[u8]) -> Result<usize, PinaPodError>;
633	/// Applies the patch to an existing representation and returns the new length.
634	///
635	/// Capacity, arithmetic, and supplied-value checks all run before the first byte
636	/// changes, so a rejected patch leaves `data` untouched.
637	///
638	/// # Errors
639	///
640	/// Returns [`PinaPodError::BufferTooSmall`] when the resized value does not fit
641	/// `data`, and another [`PinaPodError`] variant when `data` is not a valid existing
642	/// representation or a supplied value cannot be encoded. A derived patch also
643	/// returns [`PinaPodError::InvalidLength`] if its preflighted length and its
644	/// committed length ever disagree, which can only indicate a defect in the
645	/// generated code rather than an input condition.
646	fn update(&self, data: &mut [u8]) -> Result<usize, PinaPodError>;
647	/// Writes the patch into a destination without reading a previous representation.
648	///
649	/// Use this for a fresh allocation or for a destination left zeroed by a failed
650	/// update. The destination is zeroed before configuration and validated after it.
651	///
652	/// # Errors
653	///
654	/// Returns [`PinaPodError`] when the allocation size is invalid, the encoded value
655	/// does not fit `data`, or a supplied value cannot be encoded. A failure leaves the
656	/// destination zeroed rather than partially patched.
657	fn initialize(&self, data: &mut [u8]) -> Result<usize, PinaPodError>;
658}
659
660impl<T, P> PinaPodPatch<T> for &P
661where
662	T: PinaPodCompact,
663	P: PinaPodPatch<T> + ?Sized,
664{
665	fn updated_len(&self, data: &[u8]) -> Result<usize, PinaPodError> {
666		<P as PinaPodPatch<T>>::updated_len(*self, data)
667	}
668
669	fn update(&self, data: &mut [u8]) -> Result<usize, PinaPodError> {
670		<P as PinaPodPatch<T>>::update(*self, data)
671	}
672
673	fn initialize(&self, data: &mut [u8]) -> Result<usize, PinaPodError> {
674		<P as PinaPodPatch<T>>::initialize(*self, data)
675	}
676}
677
678/// Maps a native Rust type to its pod (zero-copy) companion.
679///
680/// # Safety
681///
682/// The associated pod type carries the alignment, padding, bit-validity, and
683/// validation requirements through [`ZcElem`]. Its size is always derived with
684/// `size_of::<Self::Pod>()`; implementors cannot provide conflicting metadata.
685pub unsafe trait ZcField: Sized {
686	/// The alignment-one pod that stores this type in a schema.
687	///
688	/// A field declared as `Self` is stored as `Pod`. The mapping must not depend on
689	/// runtime state, because a representation's layout is fixed at compile time and its
690	/// size is always derived from `size_of::<Self::Pod>()`.
691	type Pod: ZcElem;
692}
693
694/// Converts a compact patch argument for a native [`Option<T>`] field into
695/// its stored representation.
696///
697/// <!-- {=podDeriveSupportTraitContract|trim|linePrefix:"/// ":true} -->
698/// This trait is public only because generated code expands in downstream crates.
699///
700/// It is not part of the hand-written PinaPod API. Its shape follows the generated output and changes only in breaking releases, in lockstep with the derive.<!-- {/podDeriveSupportTraitContract} -->
701#[doc(hidden)]
702pub trait IntoPodOption<T: ZcField> {
703	/// Converts `Option<T>` or an already-stored [`PodOption`] into the stored form.
704	fn into_pod_option(self) -> PodOption<T::Pod>;
705}
706
707impl<T> IntoPodOption<T> for Option<T>
708where
709	T: ZcField,
710	T::Pod: From<T>,
711{
712	#[inline(always)]
713	fn into_pod_option(self) -> PodOption<T::Pod> {
714		match self {
715			Some(value) => PodOption::some(value.into()),
716			None => PodOption::none(),
717		}
718	}
719}
720
721impl<T> IntoPodOption<T> for PodOption<T::Pod>
722where
723	T: ZcField,
724{
725	#[inline(always)]
726	fn into_pod_option(self) -> PodOption<T::Pod> {
727		self
728	}
729}
730
731// Built-in ZcField impls
732macro_rules! impl_zc_field {
733	($native:ty, $pod:ty) => {
734		unsafe impl ZcField for $native {
735			type Pod = $pod;
736		}
737	};
738}
739
740impl_zc_field!(u8, u8);
741impl_zc_field!(u16, PodU16);
742impl_zc_field!(u32, PodU32);
743impl_zc_field!(u64, PodU64);
744impl_zc_field!(u128, PodU128);
745impl_zc_field!(i8, i8);
746impl_zc_field!(i16, PodI16);
747impl_zc_field!(i32, PodI32);
748impl_zc_field!(i64, PodI64);
749impl_zc_field!(i128, PodI128);
750impl_zc_field!(bool, PodBool);
751
752#[cfg(feature = "fixed")]
753mod fixed_impls {
754	use super::*;
755
756	macro_rules! impl_fixed_zc_field {
757		($fixed:ident, $pod:ty) => {
758			// SAFETY: `fixed::$fixed<Frac>` is a schema type whose complete
759			// bit pattern is stored in the matching little-endian integer pod.
760			// The pod type is an alignment-one `ZcElem`; its size is derived
761			// directly wherever it is used.
762			unsafe impl<Frac> ZcField for fixed::$fixed<Frac> {
763				type Pod = $pod;
764			}
765		};
766	}
767
768	impl_fixed_zc_field!(FixedI8, i8);
769	impl_fixed_zc_field!(FixedI16, PodI16);
770	impl_fixed_zc_field!(FixedI32, PodI32);
771	impl_fixed_zc_field!(FixedI64, PodI64);
772	impl_fixed_zc_field!(FixedI128, PodI128);
773	impl_fixed_zc_field!(FixedU8, u8);
774	impl_fixed_zc_field!(FixedU16, PodU16);
775	impl_fixed_zc_field!(FixedU32, PodU32);
776	impl_fixed_zc_field!(FixedU64, PodU64);
777	impl_fixed_zc_field!(FixedU128, PodU128);
778}
779
780// SAFETY: The pod of an array is the array of its element pods. `[T::Pod; N]`
781// is an alignment-one `ZcElem` whose validation recurses into `T`'s, so the
782// representation contract carries through unchanged. `[u8; N]` keeps its
783// identity mapping via `<u8 as ZcField>::Pod = u8`.
784unsafe impl<T: ZcField, const N: usize> ZcField for [T; N] {
785	type Pod = [<T as ZcField>::Pod; N];
786}
787
788/// Converts a native or pod-spelled array into its stored representation,
789/// element-wise.
790///
791/// <!-- {=podDeriveSupportTraitContract|trim|linePrefix:"/// ":true} -->
792/// This trait is public only because generated code expands in downstream crates.
793///
794/// It is not part of the hand-written PinaPod API. Its shape follows the generated output and changes only in breaking releases, in lockstep with the derive.<!-- {/podDeriveSupportTraitContract} -->
795///
796/// The blanket impl covers both spellings: `[u64; N]` resolves through
797/// `PodU64: From<u64>`, while `[PodU64; N]` resolves through the reflexive
798/// `From`.
799#[doc(hidden)]
800pub trait IntoPodArray<T: ZcField, const N: usize> {
801	/// Converts each element through its [`ZcField`] mapping.
802	fn into_pod_array(self) -> [<T as ZcField>::Pod; N];
803}
804
805impl<T, U, const N: usize> IntoPodArray<T, N> for [U; N]
806where
807	T: ZcField,
808	T::Pod: From<U>,
809{
810	#[inline(always)]
811	fn into_pod_array(self) -> [T::Pod; N] {
812		self.map(<T::Pod as From<U>>::from)
813	}
814}
815
816macro_rules! impl_zc_field_identity {
817    ($($ty:ty),*) => {
818        $(
819            unsafe impl ZcField for $ty {
820                type Pod = Self;
821            }
822        )*
823    };
824}
825
826impl_zc_field_identity!(
827	PodU16, PodU32, PodU64, PodU128, PodI16, PodI32, PodI64, PodI128, PodBool
828);
829
830unsafe impl<const N: usize, const PFX: usize> ZcField for PodString<N, PFX> {
831	type Pod = Self;
832}
833
834unsafe impl<T: ZcElem, const N: usize, const PFX: usize> ZcField for PodVecRepr<T, N, PFX> {
835	type Pod = Self;
836}
837
838unsafe impl<T: ZcElem, const PFX: usize> ZcField for PodOption<T, PFX> {
839	type Pod = Self;
840}
841
842// Option<T> maps to PodOption<T::Pod, 1> (PFX=1 only, unchanged).
843unsafe impl<T> ZcField for Option<T>
844where
845	T: ZcField,
846{
847	type Pod = PodOption<T::Pod, 1>;
848}