Skip to main content

icydb_schema/
atom.rs

1//! Engine-neutral scalar atoms used by proposal literals and facade re-exports.
2
3use std::{
4    cmp::Ordering,
5    fmt::{self, Display, Formatter},
6    hash::{Hash, Hasher},
7    str::FromStr,
8};
9
10use ic_wire::ByteBuf;
11use serde::{Deserialize, Deserializer, Serialize, de::Error as DeError};
12
13#[cfg(test)]
14mod tests;
15
16/// Failure while parsing a canonical principal.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum PrincipalError {
19    /// The textual principal is invalid.
20    InvalidText,
21}
22
23impl Display for PrincipalError {
24    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
25        formatter.write_str("principal text is invalid")
26    }
27}
28
29/// Failure while decoding canonical principal bytes.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum PrincipalDecodeError {
32    /// A principal cannot exceed 29 bytes.
33    TooLarge {
34        /// Actual byte length.
35        len: usize,
36    },
37}
38
39/// Failure while exposing canonical principal bytes.
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub enum PrincipalEncodeError {
42    /// A principal cannot exceed its canonical maximum.
43    TooLarge {
44        /// Actual byte length.
45        len: usize,
46        /// Maximum canonical byte length.
47        max: usize,
48    },
49}
50
51/// Canonical principal atom.
52#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
53#[repr(transparent)]
54#[serde(transparent)]
55pub struct Principal(ic_platform::Principal);
56
57impl Principal {
58    /// Maximum canonical principal byte length.
59    pub const MAX_LENGTH_IN_BYTES: u32 = 29;
60
61    /// Minimum byte-ordered principal.
62    pub const MIN: Self = Self::from_slice(&[0x00; 29]);
63
64    /// Maximum byte-ordered principal.
65    pub const MAX: Self = Self::from_slice(&[0xFF; 29]);
66
67    /// Return the anonymous principal.
68    #[must_use]
69    pub const fn anonymous() -> Self {
70        Self(ic_platform::Principal::anonymous())
71    }
72
73    /// Parse canonical textual principal form.
74    ///
75    /// # Errors
76    ///
77    /// Returns [`PrincipalError::InvalidText`] for invalid text.
78    pub fn from_text(text: &str) -> Result<Self, PrincipalError> {
79        ic_platform::Principal::from_text(text)
80            .map(Self)
81            .map_err(|_| PrincipalError::InvalidText)
82    }
83
84    /// Construct from canonical principal bytes.
85    ///
86    /// # Panics
87    ///
88    /// Panics when `bytes` exceeds the canonical 29-byte principal limit.
89    /// Use [`Principal::try_from_bytes`] for untrusted input.
90    #[must_use]
91    pub const fn from_slice(bytes: &[u8]) -> Self {
92        Self(ic_platform::Principal::from_slice(bytes))
93    }
94
95    /// Borrow canonical principal bytes.
96    #[must_use]
97    pub const fn as_slice(&self) -> &[u8] {
98        self.0.as_slice()
99    }
100
101    /// Borrow bounded canonical bytes.
102    ///
103    /// # Errors
104    ///
105    /// Returns a typed error if an upstream value violates the canonical
106    /// principal length.
107    pub const fn stored_bytes(&self) -> Result<&[u8], PrincipalEncodeError> {
108        let bytes = self.as_slice();
109        if bytes.len() > Self::MAX_LENGTH_IN_BYTES as usize {
110            return Err(PrincipalEncodeError::TooLarge {
111                len: bytes.len(),
112                max: Self::MAX_LENGTH_IN_BYTES as usize,
113            });
114        }
115        Ok(bytes)
116    }
117
118    /// Copy bounded canonical bytes.
119    ///
120    /// # Errors
121    ///
122    /// Returns a typed error for an invalid upstream representation.
123    pub fn to_bytes(self) -> Result<Vec<u8>, PrincipalEncodeError> {
124        Ok(self.stored_bytes()?.to_vec())
125    }
126
127    /// Decode bounded canonical bytes.
128    ///
129    /// # Errors
130    ///
131    /// Returns a typed error when `bytes` exceeds the principal bound.
132    pub const fn try_from_bytes(bytes: &[u8]) -> Result<Self, PrincipalDecodeError> {
133        if bytes.len() > Self::MAX_LENGTH_IN_BYTES as usize {
134            return Err(PrincipalDecodeError::TooLarge { len: bytes.len() });
135        }
136        Ok(Self::from_slice(bytes))
137    }
138}
139
140impl Display for Principal {
141    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
142        Display::fmt(&self.0, formatter)
143    }
144}
145
146impl From<ic_platform::Principal> for Principal {
147    fn from(value: ic_platform::Principal) -> Self {
148        Self(value)
149    }
150}
151
152impl From<Principal> for ic_platform::Principal {
153    fn from(value: Principal) -> Self {
154        value.0
155    }
156}
157
158impl FromStr for Principal {
159    type Err = PrincipalError;
160
161    fn from_str(input: &str) -> Result<Self, Self::Err> {
162        Self::from_text(input)
163    }
164}
165
166impl Serialize for Principal {
167    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168    where
169        S: serde::Serializer,
170    {
171        self.0.serialize(serializer)
172    }
173}
174
175impl TryFrom<&[u8]> for Principal {
176    type Error = PrincipalDecodeError;
177
178    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
179        Self::try_from_bytes(bytes)
180    }
181}
182
183/// Engine-neutral binary scalar.
184#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
185pub struct Blob(Vec<u8>);
186
187impl Blob {
188    /// Parse an unprefixed, even-length ASCII hex schema literal.
189    ///
190    /// Empty input represents empty bytes. This authoring boundary caps decoded
191    /// bytes at [`crate::MAX_PROPOSAL_LITERAL_BYTES`]; runtime Blob values retain
192    /// their independent size admission.
193    ///
194    /// # Errors
195    /// Returns [`crate::SchemaContractError::InvalidLiteral`] for malformed hex
196    /// or oversized input, before allocating the decoded buffer.
197    pub fn from_hex_literal(text: &str) -> Result<Self, crate::SchemaContractError> {
198        if text.len() > crate::MAX_PROPOSAL_LITERAL_BYTES * 2
199            || !text.len().is_multiple_of(2)
200            || !text.bytes().all(|byte| byte.is_ascii_hexdigit())
201        {
202            return Err(crate::SchemaContractError::InvalidLiteral);
203        }
204        text.as_bytes()
205            .as_chunks::<2>()
206            .0
207            .iter()
208            .map(|pair| {
209                let pair = std::str::from_utf8(pair)
210                    .map_err(|_| crate::SchemaContractError::InvalidLiteral)?;
211                u8::from_str_radix(pair, 16).map_err(|_| crate::SchemaContractError::InvalidLiteral)
212            })
213            .collect::<Result<Vec<_>, _>>()
214            .map(Self::from)
215    }
216
217    /// Borrow the canonical bytes.
218    #[must_use]
219    pub fn as_bytes(&self) -> &[u8] {
220        &self.0
221    }
222
223    /// Consume the atom and return its bytes.
224    #[must_use]
225    pub fn into_bytes(self) -> Vec<u8> {
226        self.0
227    }
228
229    /// Clone the canonical bytes.
230    #[must_use]
231    pub fn to_vec(&self) -> Vec<u8> {
232        self.0.clone()
233    }
234
235    /// Return the byte length.
236    #[must_use]
237    pub const fn len(&self) -> usize {
238        self.0.len()
239    }
240
241    /// Return whether the value is empty.
242    #[must_use]
243    pub const fn is_empty(&self) -> bool {
244        self.0.is_empty()
245    }
246}
247
248impl Display for Blob {
249    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
250        write!(formatter, "[blob ({} bytes)]", self.len())
251    }
252}
253
254impl From<Vec<u8>> for Blob {
255    fn from(bytes: Vec<u8>) -> Self {
256        Self(bytes)
257    }
258}
259
260impl From<&[u8]> for Blob {
261    fn from(bytes: &[u8]) -> Self {
262        Self(bytes.to_vec())
263    }
264}
265
266impl<const N: usize> From<&[u8; N]> for Blob {
267    fn from(bytes: &[u8; N]) -> Self {
268        Self(bytes.to_vec())
269    }
270}
271
272impl<'de> Deserialize<'de> for Blob {
273    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
274    where
275        D: Deserializer<'de>,
276    {
277        ByteBuf::deserialize(deserializer).map(|bytes| Self(bytes.into_vec()))
278    }
279}
280
281/// Failure while parsing a canonical ULID.
282#[derive(Clone, Copy, Debug, Eq, PartialEq)]
283pub enum UlidParseError {
284    /// The input is not canonical ULID text.
285    InvalidString,
286}
287
288impl Display for UlidParseError {
289    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
290        formatter.write_str("ULID text is invalid")
291    }
292}
293
294/// Failure while decoding canonical ULID bytes.
295#[derive(Clone, Copy, Debug, Eq, PartialEq)]
296pub enum UlidDecodeError {
297    /// A ULID must contain exactly 16 bytes.
298    InvalidSize {
299        /// Actual byte length.
300        len: usize,
301    },
302}
303
304/// Canonical ULID atom without generation authority.
305///
306/// Wire and binary Serde carry exactly 16 bytes. Human-readable Serde and
307/// display use canonical 26-character text.
308#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
309#[repr(transparent)]
310pub struct Ulid([u8; 16]);
311
312impl Ulid {
313    /// Canonical byte length.
314    pub const STORED_SIZE: u32 = 16;
315
316    /// Minimum ULID.
317    pub const MIN: Self = Self::from_bytes([0x00; 16]);
318
319    /// Maximum ULID.
320    pub const MAX: Self = Self::from_bytes([0xFF; 16]);
321
322    /// Nil ULID.
323    #[must_use]
324    pub const fn nil() -> Self {
325        Self::MIN
326    }
327
328    /// Construct from the canonical 16-byte representation.
329    #[must_use]
330    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
331        Self(bytes)
332    }
333
334    /// Construct deterministically from the canonical unsigned integer form.
335    #[must_use]
336    pub const fn from_u128(value: u128) -> Self {
337        Self::from_bytes(value.to_be_bytes())
338    }
339
340    /// Return the canonical 16-byte representation.
341    #[must_use]
342    pub const fn to_bytes(self) -> [u8; 16] {
343        self.0
344    }
345
346    /// Decode exactly 16 canonical bytes.
347    ///
348    /// # Errors
349    ///
350    /// Returns a typed size error for every other length.
351    pub const fn try_from_bytes(bytes: &[u8]) -> Result<Self, UlidDecodeError> {
352        if bytes.len() != Self::STORED_SIZE as usize {
353            return Err(UlidDecodeError::InvalidSize { len: bytes.len() });
354        }
355        let mut value = [0; 16];
356        value.copy_from_slice(bytes);
357        Ok(Self::from_bytes(value))
358    }
359}
360
361impl Display for Ulid {
362    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
363        Display::fmt(&ulid::Ulid::from_bytes(self.0), formatter)
364    }
365}
366
367impl fmt::Debug for Ulid {
368    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
369        fmt::Debug::fmt(&self.to_string(), formatter)
370    }
371}
372
373impl FromStr for Ulid {
374    type Err = UlidParseError;
375
376    fn from_str(input: &str) -> Result<Self, Self::Err> {
377        let value = input
378            .parse::<ulid::Ulid>()
379            .map_err(|_| UlidParseError::InvalidString)?;
380        if value.to_string() != input {
381            return Err(UlidParseError::InvalidString);
382        }
383        Ok(Self::from_bytes(value.to_bytes()))
384    }
385}
386
387impl<'de> Deserialize<'de> for Ulid {
388    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389    where
390        D: Deserializer<'de>,
391    {
392        if deserializer.is_human_readable() {
393            return String::deserialize(deserializer)?
394                .parse()
395                .map_err(D::Error::custom);
396        }
397
398        deserialize_fixed_bytes(deserializer).map(Self::from_bytes)
399    }
400}
401
402impl Serialize for Ulid {
403    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
404    where
405        S: serde::Serializer,
406    {
407        if serializer.is_human_readable() {
408            serializer.serialize_str(&self.to_string())
409        } else {
410            serializer.serialize_bytes(&self.0)
411        }
412    }
413}
414
415impl TryFrom<&[u8]> for Ulid {
416    type Error = UlidDecodeError;
417
418    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
419        Self::try_from_bytes(bytes)
420    }
421}
422
423/// Failure while decoding one finite 32-bit floating-point atom.
424#[derive(Clone, Copy, Debug, Eq, PartialEq)]
425pub enum Float32DecodeError {
426    /// The input did not contain exactly four bytes.
427    InvalidSize {
428        /// Actual byte length.
429        len: usize,
430    },
431    /// The bytes represented NaN or infinity.
432    NonFinite,
433}
434
435/// Finite canonical `f32` atom.
436#[derive(Clone, Copy, Debug, Default)]
437#[repr(transparent)]
438pub struct Float32(f32);
439
440impl Float32 {
441    /// Construct a finite float and normalize negative zero.
442    #[must_use]
443    pub fn try_new(value: f32) -> Option<Self> {
444        if !value.is_finite() {
445            return None;
446        }
447        Some(Self(if value == 0.0 { 0.0 } else { value }))
448    }
449
450    /// Return the finite primitive value.
451    #[must_use]
452    pub const fn get(self) -> f32 {
453        self.0
454    }
455
456    /// Return the stable big-endian IEEE-754 representation.
457    #[must_use]
458    pub const fn to_be_bytes(&self) -> [u8; 4] {
459        self.0.to_bits().to_be_bytes()
460    }
461
462    /// Decode one stable big-endian IEEE-754 representation.
463    ///
464    /// # Errors
465    ///
466    /// Returns a typed error for the wrong byte length or a non-finite value.
467    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Float32DecodeError> {
468        let bytes: [u8; 4] = bytes
469            .try_into()
470            .map_err(|_| Float32DecodeError::InvalidSize { len: bytes.len() })?;
471        Self::try_new(f32::from_bits(u32::from_be_bytes(bytes)))
472            .ok_or(Float32DecodeError::NonFinite)
473    }
474
475    /// Convert a finite, in-range `f64`.
476    #[must_use]
477    #[expect(clippy::cast_possible_truncation)]
478    pub fn try_from_f64(value: f64) -> Option<Self> {
479        if !value.is_finite() || value < f64::from(f32::MIN) || value > f64::from(f32::MAX) {
480            return None;
481        }
482        Self::try_new(value as f32)
483    }
484}
485
486impl Display for Float32 {
487    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
488        Display::fmt(&self.0, formatter)
489    }
490}
491
492impl<'de> Deserialize<'de> for Float32 {
493    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
494    where
495        D: Deserializer<'de>,
496    {
497        Self::try_new(f32::deserialize(deserializer)?)
498            .ok_or_else(|| D::Error::custom("Float32 must be finite"))
499    }
500}
501
502impl Serialize for Float32 {
503    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
504    where
505        S: serde::Serializer,
506    {
507        serializer.serialize_f32(self.0)
508    }
509}
510
511impl Eq for Float32 {}
512
513impl From<Float32> for f32 {
514    fn from(value: Float32) -> Self {
515        value.0
516    }
517}
518
519#[expect(clippy::cast_precision_loss)]
520impl From<i32> for Float32 {
521    fn from(value: i32) -> Self {
522        Self(value as f32)
523    }
524}
525
526impl Hash for Float32 {
527    fn hash<H: Hasher>(&self, state: &mut H) {
528        state.write_u32(self.0.to_bits());
529    }
530}
531
532impl Ord for Float32 {
533    fn cmp(&self, other: &Self) -> Ordering {
534        self.0.total_cmp(&other.0)
535    }
536}
537
538impl PartialEq for Float32 {
539    fn eq(&self, other: &Self) -> bool {
540        self.0 == other.0
541    }
542}
543
544impl PartialOrd for Float32 {
545    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
546        Some(self.cmp(other))
547    }
548}
549
550impl TryFrom<&[u8]> for Float32 {
551    type Error = Float32DecodeError;
552
553    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
554        Self::try_from_bytes(bytes)
555    }
556}
557
558/// Failure while decoding one finite 64-bit floating-point atom.
559#[derive(Clone, Copy, Debug, Eq, PartialEq)]
560pub enum Float64DecodeError {
561    /// The input did not contain exactly eight bytes.
562    InvalidSize {
563        /// Actual byte length.
564        len: usize,
565    },
566    /// The bytes represented NaN or infinity.
567    NonFinite,
568}
569
570/// Finite canonical `f64` atom.
571#[derive(Clone, Copy, Debug, Default)]
572#[repr(transparent)]
573pub struct Float64(f64);
574
575impl Float64 {
576    /// Construct a finite float and normalize negative zero.
577    #[must_use]
578    pub fn try_new(value: f64) -> Option<Self> {
579        if !value.is_finite() {
580            return None;
581        }
582        Some(Self(if value == 0.0 { 0.0 } else { value }))
583    }
584
585    /// Return the finite primitive value.
586    #[must_use]
587    pub const fn get(self) -> f64 {
588        self.0
589    }
590
591    /// Return the stable big-endian IEEE-754 representation.
592    #[must_use]
593    pub const fn to_be_bytes(&self) -> [u8; 8] {
594        self.0.to_bits().to_be_bytes()
595    }
596
597    /// Decode one stable big-endian IEEE-754 representation.
598    ///
599    /// # Errors
600    ///
601    /// Returns a typed error for the wrong byte length or a non-finite value.
602    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Float64DecodeError> {
603        let bytes: [u8; 8] = bytes
604            .try_into()
605            .map_err(|_| Float64DecodeError::InvalidSize { len: bytes.len() })?;
606        Self::try_new(f64::from_bits(u64::from_be_bytes(bytes)))
607            .ok_or(Float64DecodeError::NonFinite)
608    }
609}
610
611impl Display for Float64 {
612    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
613        Display::fmt(&self.0, formatter)
614    }
615}
616
617impl<'de> Deserialize<'de> for Float64 {
618    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
619    where
620        D: Deserializer<'de>,
621    {
622        Self::try_new(f64::deserialize(deserializer)?)
623            .ok_or_else(|| D::Error::custom("Float64 must be finite"))
624    }
625}
626
627impl Serialize for Float64 {
628    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
629    where
630        S: serde::Serializer,
631    {
632        serializer.serialize_f64(self.0)
633    }
634}
635
636impl Eq for Float64 {}
637
638impl From<Float64> for f64 {
639    fn from(value: Float64) -> Self {
640        value.0
641    }
642}
643
644impl From<i32> for Float64 {
645    fn from(value: i32) -> Self {
646        Self(f64::from(value))
647    }
648}
649
650impl Hash for Float64 {
651    fn hash<H: Hasher>(&self, state: &mut H) {
652        state.write_u64(self.0.to_bits());
653    }
654}
655
656impl Ord for Float64 {
657    fn cmp(&self, other: &Self) -> Ordering {
658        self.0.total_cmp(&other.0)
659    }
660}
661
662impl PartialEq for Float64 {
663    fn eq(&self, other: &Self) -> bool {
664        self.0 == other.0
665    }
666}
667
668impl PartialOrd for Float64 {
669    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
670        Some(self.cmp(other))
671    }
672}
673
674impl TryFrom<&[u8]> for Float64 {
675    type Error = Float64DecodeError;
676
677    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
678        Self::try_from_bytes(bytes)
679    }
680}
681
682impl Serialize for Blob {
683    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
684        serializer.serialize_bytes(self.as_bytes())
685    }
686}
687
688// Fixed atom widths are checked before copying and without allocating a Vec.
689// Reading one excess sequence byte rejects oversized self-describing arrays.
690pub(crate) fn deserialize_fixed_bytes<'de, D: Deserializer<'de>, const N: usize>(
691    deserializer: D,
692) -> Result<[u8; N], D::Error> {
693    struct Fixed<const N: usize>;
694    impl<'de, const N: usize> serde::de::Visitor<'de> for Fixed<N> {
695        type Value = [u8; N];
696        fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
697            write!(f, "exactly {N} bytes")
698        }
699        fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
700            bytes
701                .try_into()
702                .map_err(|_| E::invalid_length(bytes.len(), &self))
703        }
704        fn visit_seq<A: serde::de::SeqAccess<'de>>(
705            self,
706            mut values: A,
707        ) -> Result<Self::Value, A::Error> {
708            let mut bytes = [0; N];
709            for (index, byte) in bytes.iter_mut().enumerate() {
710                *byte = values
711                    .next_element()?
712                    .ok_or_else(|| A::Error::invalid_length(index, &self))?;
713            }
714            if values.next_element::<u8>()?.is_some() {
715                return Err(A::Error::invalid_length(N + 1, &self));
716            }
717            Ok(bytes)
718        }
719    }
720    deserializer.deserialize_bytes(Fixed::<N>)
721}