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