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}