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 candid::CandidType;
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(CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
53#[repr(transparent)]
54#[serde(transparent)]
55pub struct Principal(candid::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(candid::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        candid::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(candid::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<candid::Principal> for Principal {
147    fn from(value: candid::Principal) -> Self {
148        Self(value)
149    }
150}
151
152impl From<Principal> for candid::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, Serialize)]
185pub struct Blob(Vec<u8>);
186
187impl Blob {
188    /// Borrow the canonical bytes.
189    #[must_use]
190    pub fn as_bytes(&self) -> &[u8] {
191        &self.0
192    }
193
194    /// Consume the atom and return its bytes.
195    #[must_use]
196    pub fn into_bytes(self) -> Vec<u8> {
197        self.0
198    }
199
200    /// Clone the canonical bytes.
201    #[must_use]
202    pub fn to_vec(&self) -> Vec<u8> {
203        self.0.clone()
204    }
205
206    /// Return the byte length.
207    #[must_use]
208    pub const fn len(&self) -> usize {
209        self.0.len()
210    }
211
212    /// Return whether the value is empty.
213    #[must_use]
214    pub const fn is_empty(&self) -> bool {
215        self.0.is_empty()
216    }
217}
218
219impl Display for Blob {
220    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
221        write!(formatter, "[blob ({} bytes)]", self.len())
222    }
223}
224
225impl From<Vec<u8>> for Blob {
226    fn from(bytes: Vec<u8>) -> Self {
227        Self(bytes)
228    }
229}
230
231impl From<&[u8]> for Blob {
232    fn from(bytes: &[u8]) -> Self {
233        Self(bytes.to_vec())
234    }
235}
236
237impl<const N: usize> From<&[u8; N]> for Blob {
238    fn from(bytes: &[u8; N]) -> Self {
239        Self(bytes.to_vec())
240    }
241}
242
243impl<'de> Deserialize<'de> for Blob {
244    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245    where
246        D: Deserializer<'de>,
247    {
248        Vec::<u8>::deserialize(deserializer).map(Self)
249    }
250}
251
252impl CandidType for Blob {
253    fn ty() -> candid::types::Type {
254        <Vec<u8> as CandidType>::ty()
255    }
256
257    fn _ty() -> candid::types::Type {
258        <Vec<u8> as CandidType>::_ty()
259    }
260
261    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
262    where
263        S: candid::types::Serializer,
264    {
265        serializer.serialize_blob(self.as_bytes())
266    }
267}
268
269/// Failure while parsing a canonical ULID.
270#[derive(Clone, Copy, Debug, Eq, PartialEq)]
271pub enum UlidParseError {
272    /// The input is not canonical ULID text.
273    InvalidString,
274}
275
276impl Display for UlidParseError {
277    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
278        formatter.write_str("ULID text is invalid")
279    }
280}
281
282/// Failure while decoding canonical ULID bytes.
283#[derive(Clone, Copy, Debug, Eq, PartialEq)]
284pub enum UlidDecodeError {
285    /// A ULID must contain exactly 16 bytes.
286    InvalidSize {
287        /// Actual byte length.
288        len: usize,
289    },
290}
291
292/// Canonical ULID atom without generation authority.
293///
294/// Candid and binary Serde carry exactly 16 bytes. Human-readable Serde and
295/// display use canonical 26-character text.
296#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
297#[repr(transparent)]
298pub struct Ulid([u8; 16]);
299
300impl Ulid {
301    /// Canonical byte length.
302    pub const STORED_SIZE: u32 = 16;
303
304    /// Minimum ULID.
305    pub const MIN: Self = Self::from_bytes([0x00; 16]);
306
307    /// Maximum ULID.
308    pub const MAX: Self = Self::from_bytes([0xFF; 16]);
309
310    /// Nil ULID.
311    #[must_use]
312    pub const fn nil() -> Self {
313        Self::MIN
314    }
315
316    /// Construct from the canonical 16-byte representation.
317    #[must_use]
318    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
319        Self(bytes)
320    }
321
322    /// Construct deterministically from the canonical unsigned integer form.
323    #[must_use]
324    pub const fn from_u128(value: u128) -> Self {
325        Self::from_bytes(value.to_be_bytes())
326    }
327
328    /// Return the canonical 16-byte representation.
329    #[must_use]
330    pub const fn to_bytes(self) -> [u8; 16] {
331        self.0
332    }
333
334    /// Decode exactly 16 canonical bytes.
335    ///
336    /// # Errors
337    ///
338    /// Returns a typed size error for every other length.
339    pub const fn try_from_bytes(bytes: &[u8]) -> Result<Self, UlidDecodeError> {
340        if bytes.len() != Self::STORED_SIZE as usize {
341            return Err(UlidDecodeError::InvalidSize { len: bytes.len() });
342        }
343        let mut value = [0; 16];
344        value.copy_from_slice(bytes);
345        Ok(Self::from_bytes(value))
346    }
347}
348
349impl Display for Ulid {
350    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
351        Display::fmt(&ulid::Ulid::from_bytes(self.0), formatter)
352    }
353}
354
355impl fmt::Debug for Ulid {
356    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
357        fmt::Debug::fmt(&self.to_string(), formatter)
358    }
359}
360
361impl FromStr for Ulid {
362    type Err = UlidParseError;
363
364    fn from_str(input: &str) -> Result<Self, Self::Err> {
365        let value = input
366            .parse::<ulid::Ulid>()
367            .map_err(|_| UlidParseError::InvalidString)?;
368        if value.to_string() != input {
369            return Err(UlidParseError::InvalidString);
370        }
371        Ok(Self::from_bytes(value.to_bytes()))
372    }
373}
374
375impl CandidType for Ulid {
376    fn ty() -> candid::types::Type {
377        <Vec<u8> as CandidType>::ty()
378    }
379
380    fn _ty() -> candid::types::Type {
381        <Vec<u8> as CandidType>::_ty()
382    }
383
384    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
385    where
386        S: candid::types::Serializer,
387    {
388        serializer.serialize_blob(&self.0)
389    }
390}
391
392impl<'de> Deserialize<'de> for Ulid {
393    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
394    where
395        D: Deserializer<'de>,
396    {
397        // Sequence decoding checks the Candid element type before reading into
398        // a fixed buffer. Consume at most one excess byte to enforce the width.
399        struct UlidBytesVisitor;
400
401        impl<'de> serde::de::Visitor<'de> for UlidBytesVisitor {
402            type Value = Ulid;
403
404            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
405                formatter.write_str("exactly 16 ULID bytes")
406            }
407
408            fn visit_seq<A: serde::de::SeqAccess<'de>>(
409                self,
410                mut sequence: A,
411            ) -> Result<Ulid, A::Error> {
412                let mut bytes = [0; 16];
413                for (index, byte) in bytes.iter_mut().enumerate() {
414                    *byte = sequence
415                        .next_element()?
416                        .ok_or_else(|| A::Error::invalid_length(index, &self))?;
417                }
418                if sequence.next_element::<u8>()?.is_some() {
419                    return Err(A::Error::invalid_length(17, &self));
420                }
421
422                Ok(Ulid::from_bytes(bytes))
423            }
424        }
425
426        if deserializer.is_human_readable() {
427            return String::deserialize(deserializer)?
428                .parse()
429                .map_err(D::Error::custom);
430        }
431
432        deserializer.deserialize_seq(UlidBytesVisitor)
433    }
434}
435
436impl Serialize for Ulid {
437    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
438    where
439        S: serde::Serializer,
440    {
441        if serializer.is_human_readable() {
442            serializer.serialize_str(&self.to_string())
443        } else {
444            serializer.serialize_bytes(&self.0)
445        }
446    }
447}
448
449impl TryFrom<&[u8]> for Ulid {
450    type Error = UlidDecodeError;
451
452    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
453        Self::try_from_bytes(bytes)
454    }
455}
456
457/// Failure while decoding one finite 32-bit floating-point atom.
458#[derive(Clone, Copy, Debug, Eq, PartialEq)]
459pub enum Float32DecodeError {
460    /// The input did not contain exactly four bytes.
461    InvalidSize {
462        /// Actual byte length.
463        len: usize,
464    },
465    /// The bytes represented NaN or infinity.
466    NonFinite,
467}
468
469/// Finite canonical `f32` atom.
470#[derive(CandidType, Clone, Copy, Debug, Default)]
471#[repr(transparent)]
472pub struct Float32(f32);
473
474impl Float32 {
475    /// Construct a finite float and normalize negative zero.
476    #[must_use]
477    pub fn try_new(value: f32) -> Option<Self> {
478        if !value.is_finite() {
479            return None;
480        }
481        Some(Self(if value == 0.0 { 0.0 } else { value }))
482    }
483
484    /// Return the finite primitive value.
485    #[must_use]
486    pub const fn get(self) -> f32 {
487        self.0
488    }
489
490    /// Return the stable big-endian IEEE-754 representation.
491    #[must_use]
492    pub const fn to_be_bytes(&self) -> [u8; 4] {
493        self.0.to_bits().to_be_bytes()
494    }
495
496    /// Decode one stable big-endian IEEE-754 representation.
497    ///
498    /// # Errors
499    ///
500    /// Returns a typed error for the wrong byte length or a non-finite value.
501    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Float32DecodeError> {
502        let bytes: [u8; 4] = bytes
503            .try_into()
504            .map_err(|_| Float32DecodeError::InvalidSize { len: bytes.len() })?;
505        Self::try_new(f32::from_bits(u32::from_be_bytes(bytes)))
506            .ok_or(Float32DecodeError::NonFinite)
507    }
508
509    /// Convert a finite, in-range `f64`.
510    #[must_use]
511    #[expect(clippy::cast_possible_truncation)]
512    pub fn try_from_f64(value: f64) -> Option<Self> {
513        if !value.is_finite() || value < f64::from(f32::MIN) || value > f64::from(f32::MAX) {
514            return None;
515        }
516        Self::try_new(value as f32)
517    }
518}
519
520impl Display for Float32 {
521    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
522        Display::fmt(&self.0, formatter)
523    }
524}
525
526impl<'de> Deserialize<'de> for Float32 {
527    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
528    where
529        D: Deserializer<'de>,
530    {
531        Self::try_new(f32::deserialize(deserializer)?)
532            .ok_or_else(|| D::Error::custom("Float32 must be finite"))
533    }
534}
535
536impl Serialize for Float32 {
537    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
538    where
539        S: serde::Serializer,
540    {
541        serializer.serialize_f32(self.0)
542    }
543}
544
545impl Eq for Float32 {}
546
547impl From<Float32> for f32 {
548    fn from(value: Float32) -> Self {
549        value.0
550    }
551}
552
553#[expect(clippy::cast_precision_loss)]
554impl From<i32> for Float32 {
555    fn from(value: i32) -> Self {
556        Self(value as f32)
557    }
558}
559
560impl Hash for Float32 {
561    fn hash<H: Hasher>(&self, state: &mut H) {
562        state.write_u32(self.0.to_bits());
563    }
564}
565
566impl Ord for Float32 {
567    fn cmp(&self, other: &Self) -> Ordering {
568        self.0.total_cmp(&other.0)
569    }
570}
571
572impl PartialEq for Float32 {
573    fn eq(&self, other: &Self) -> bool {
574        self.0 == other.0
575    }
576}
577
578impl PartialOrd for Float32 {
579    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
580        Some(self.cmp(other))
581    }
582}
583
584impl TryFrom<&[u8]> for Float32 {
585    type Error = Float32DecodeError;
586
587    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
588        Self::try_from_bytes(bytes)
589    }
590}
591
592/// Failure while decoding one finite 64-bit floating-point atom.
593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594pub enum Float64DecodeError {
595    /// The input did not contain exactly eight bytes.
596    InvalidSize {
597        /// Actual byte length.
598        len: usize,
599    },
600    /// The bytes represented NaN or infinity.
601    NonFinite,
602}
603
604/// Finite canonical `f64` atom.
605#[derive(CandidType, Clone, Copy, Debug, Default)]
606#[repr(transparent)]
607pub struct Float64(f64);
608
609impl Float64 {
610    /// Construct a finite float and normalize negative zero.
611    #[must_use]
612    pub fn try_new(value: f64) -> Option<Self> {
613        if !value.is_finite() {
614            return None;
615        }
616        Some(Self(if value == 0.0 { 0.0 } else { value }))
617    }
618
619    /// Return the finite primitive value.
620    #[must_use]
621    pub const fn get(self) -> f64 {
622        self.0
623    }
624
625    /// Return the stable big-endian IEEE-754 representation.
626    #[must_use]
627    pub const fn to_be_bytes(&self) -> [u8; 8] {
628        self.0.to_bits().to_be_bytes()
629    }
630
631    /// Decode one stable big-endian IEEE-754 representation.
632    ///
633    /// # Errors
634    ///
635    /// Returns a typed error for the wrong byte length or a non-finite value.
636    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Float64DecodeError> {
637        let bytes: [u8; 8] = bytes
638            .try_into()
639            .map_err(|_| Float64DecodeError::InvalidSize { len: bytes.len() })?;
640        Self::try_new(f64::from_bits(u64::from_be_bytes(bytes)))
641            .ok_or(Float64DecodeError::NonFinite)
642    }
643}
644
645impl Display for Float64 {
646    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
647        Display::fmt(&self.0, formatter)
648    }
649}
650
651impl<'de> Deserialize<'de> for Float64 {
652    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
653    where
654        D: Deserializer<'de>,
655    {
656        Self::try_new(f64::deserialize(deserializer)?)
657            .ok_or_else(|| D::Error::custom("Float64 must be finite"))
658    }
659}
660
661impl Serialize for Float64 {
662    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
663    where
664        S: serde::Serializer,
665    {
666        serializer.serialize_f64(self.0)
667    }
668}
669
670impl Eq for Float64 {}
671
672impl From<Float64> for f64 {
673    fn from(value: Float64) -> Self {
674        value.0
675    }
676}
677
678impl From<i32> for Float64 {
679    fn from(value: i32) -> Self {
680        Self(f64::from(value))
681    }
682}
683
684impl Hash for Float64 {
685    fn hash<H: Hasher>(&self, state: &mut H) {
686        state.write_u64(self.0.to_bits());
687    }
688}
689
690impl Ord for Float64 {
691    fn cmp(&self, other: &Self) -> Ordering {
692        self.0.total_cmp(&other.0)
693    }
694}
695
696impl PartialEq for Float64 {
697    fn eq(&self, other: &Self) -> bool {
698        self.0 == other.0
699    }
700}
701
702impl PartialOrd for Float64 {
703    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
704        Some(self.cmp(other))
705    }
706}
707
708impl TryFrom<&[u8]> for Float64 {
709    type Error = Float64DecodeError;
710
711    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
712        Self::try_from_bytes(bytes)
713    }
714}