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#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
294#[repr(transparent)]
295pub struct Ulid([u8; 16]);
296
297impl Ulid {
298    /// Canonical byte length.
299    pub const STORED_SIZE: u32 = 16;
300
301    /// Minimum ULID.
302    pub const MIN: Self = Self::from_bytes([0x00; 16]);
303
304    /// Maximum ULID.
305    pub const MAX: Self = Self::from_bytes([0xFF; 16]);
306
307    /// Nil ULID.
308    #[must_use]
309    pub const fn nil() -> Self {
310        Self::MIN
311    }
312
313    /// Construct from the canonical 16-byte representation.
314    #[must_use]
315    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
316        Self(bytes)
317    }
318
319    /// Construct deterministically from the canonical unsigned integer form.
320    #[must_use]
321    pub const fn from_u128(value: u128) -> Self {
322        Self::from_bytes(value.to_be_bytes())
323    }
324
325    /// Return the canonical 16-byte representation.
326    #[must_use]
327    pub const fn to_bytes(self) -> [u8; 16] {
328        self.0
329    }
330
331    /// Decode exactly 16 canonical bytes.
332    ///
333    /// # Errors
334    ///
335    /// Returns a typed size error for every other length.
336    pub const fn try_from_bytes(bytes: &[u8]) -> Result<Self, UlidDecodeError> {
337        if bytes.len() != Self::STORED_SIZE as usize {
338            return Err(UlidDecodeError::InvalidSize { len: bytes.len() });
339        }
340        let mut value = [0; 16];
341        value.copy_from_slice(bytes);
342        Ok(Self::from_bytes(value))
343    }
344}
345
346impl Display for Ulid {
347    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
348        Display::fmt(&ulid::Ulid::from_bytes(self.0), formatter)
349    }
350}
351
352impl fmt::Debug for Ulid {
353    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
354        fmt::Debug::fmt(&self.to_string(), formatter)
355    }
356}
357
358impl FromStr for Ulid {
359    type Err = UlidParseError;
360
361    fn from_str(input: &str) -> Result<Self, Self::Err> {
362        let value = input
363            .parse::<ulid::Ulid>()
364            .map_err(|_| UlidParseError::InvalidString)?;
365        if value.to_string() != input {
366            return Err(UlidParseError::InvalidString);
367        }
368        Ok(Self::from_bytes(value.to_bytes()))
369    }
370}
371
372impl CandidType for Ulid {
373    fn ty() -> candid::types::Type {
374        <String as CandidType>::ty()
375    }
376
377    fn _ty() -> candid::types::Type {
378        <String as CandidType>::_ty()
379    }
380
381    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
382    where
383        S: candid::types::Serializer,
384    {
385        serializer.serialize_text(&self.to_string())
386    }
387}
388
389impl<'de> Deserialize<'de> for Ulid {
390    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
391    where
392        D: Deserializer<'de>,
393    {
394        String::deserialize(deserializer)?
395            .parse()
396            .map_err(D::Error::custom)
397    }
398}
399
400impl Serialize for Ulid {
401    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
402    where
403        S: serde::Serializer,
404    {
405        serializer.serialize_str(&self.to_string())
406    }
407}
408
409impl TryFrom<&[u8]> for Ulid {
410    type Error = UlidDecodeError;
411
412    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
413        Self::try_from_bytes(bytes)
414    }
415}
416
417/// Failure while decoding one finite 32-bit floating-point atom.
418#[derive(Clone, Copy, Debug, Eq, PartialEq)]
419pub enum Float32DecodeError {
420    /// The input did not contain exactly four bytes.
421    InvalidSize {
422        /// Actual byte length.
423        len: usize,
424    },
425    /// The bytes represented NaN or infinity.
426    NonFinite,
427}
428
429/// Finite canonical `f32` atom.
430#[derive(CandidType, Clone, Copy, Debug, Default)]
431#[repr(transparent)]
432pub struct Float32(f32);
433
434impl Float32 {
435    /// Construct a finite float and normalize negative zero.
436    #[must_use]
437    pub fn try_new(value: f32) -> Option<Self> {
438        if !value.is_finite() {
439            return None;
440        }
441        Some(Self(if value == 0.0 { 0.0 } else { value }))
442    }
443
444    /// Return the finite primitive value.
445    #[must_use]
446    pub const fn get(self) -> f32 {
447        self.0
448    }
449
450    /// Return the stable big-endian IEEE-754 representation.
451    #[must_use]
452    pub const fn to_be_bytes(&self) -> [u8; 4] {
453        self.0.to_bits().to_be_bytes()
454    }
455
456    /// Decode one stable big-endian IEEE-754 representation.
457    ///
458    /// # Errors
459    ///
460    /// Returns a typed error for the wrong byte length or a non-finite value.
461    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Float32DecodeError> {
462        let bytes: [u8; 4] = bytes
463            .try_into()
464            .map_err(|_| Float32DecodeError::InvalidSize { len: bytes.len() })?;
465        Self::try_new(f32::from_bits(u32::from_be_bytes(bytes)))
466            .ok_or(Float32DecodeError::NonFinite)
467    }
468
469    /// Convert a finite, in-range `f64`.
470    #[must_use]
471    #[expect(clippy::cast_possible_truncation)]
472    pub fn try_from_f64(value: f64) -> Option<Self> {
473        if !value.is_finite() || value < f64::from(f32::MIN) || value > f64::from(f32::MAX) {
474            return None;
475        }
476        Self::try_new(value as f32)
477    }
478}
479
480impl Display for Float32 {
481    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
482        Display::fmt(&self.0, formatter)
483    }
484}
485
486impl<'de> Deserialize<'de> for Float32 {
487    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
488    where
489        D: Deserializer<'de>,
490    {
491        Self::try_new(f32::deserialize(deserializer)?)
492            .ok_or_else(|| D::Error::custom("Float32 must be finite"))
493    }
494}
495
496impl Serialize for Float32 {
497    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
498    where
499        S: serde::Serializer,
500    {
501        serializer.serialize_f32(self.0)
502    }
503}
504
505impl Eq for Float32 {}
506
507impl From<Float32> for f32 {
508    fn from(value: Float32) -> Self {
509        value.0
510    }
511}
512
513#[expect(clippy::cast_precision_loss)]
514impl From<i32> for Float32 {
515    fn from(value: i32) -> Self {
516        Self(value as f32)
517    }
518}
519
520impl Hash for Float32 {
521    fn hash<H: Hasher>(&self, state: &mut H) {
522        state.write_u32(self.0.to_bits());
523    }
524}
525
526impl Ord for Float32 {
527    fn cmp(&self, other: &Self) -> Ordering {
528        self.0.total_cmp(&other.0)
529    }
530}
531
532impl PartialEq for Float32 {
533    fn eq(&self, other: &Self) -> bool {
534        self.0 == other.0
535    }
536}
537
538impl PartialOrd for Float32 {
539    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
540        Some(self.cmp(other))
541    }
542}
543
544impl TryFrom<&[u8]> for Float32 {
545    type Error = Float32DecodeError;
546
547    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
548        Self::try_from_bytes(bytes)
549    }
550}
551
552/// Failure while decoding one finite 64-bit floating-point atom.
553#[derive(Clone, Copy, Debug, Eq, PartialEq)]
554pub enum Float64DecodeError {
555    /// The input did not contain exactly eight bytes.
556    InvalidSize {
557        /// Actual byte length.
558        len: usize,
559    },
560    /// The bytes represented NaN or infinity.
561    NonFinite,
562}
563
564/// Finite canonical `f64` atom.
565#[derive(CandidType, Clone, Copy, Debug, Default)]
566#[repr(transparent)]
567pub struct Float64(f64);
568
569impl Float64 {
570    /// Construct a finite float and normalize negative zero.
571    #[must_use]
572    pub fn try_new(value: f64) -> Option<Self> {
573        if !value.is_finite() {
574            return None;
575        }
576        Some(Self(if value == 0.0 { 0.0 } else { value }))
577    }
578
579    /// Return the finite primitive value.
580    #[must_use]
581    pub const fn get(self) -> f64 {
582        self.0
583    }
584
585    /// Return the stable big-endian IEEE-754 representation.
586    #[must_use]
587    pub const fn to_be_bytes(&self) -> [u8; 8] {
588        self.0.to_bits().to_be_bytes()
589    }
590
591    /// Decode one stable big-endian IEEE-754 representation.
592    ///
593    /// # Errors
594    ///
595    /// Returns a typed error for the wrong byte length or a non-finite value.
596    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Float64DecodeError> {
597        let bytes: [u8; 8] = bytes
598            .try_into()
599            .map_err(|_| Float64DecodeError::InvalidSize { len: bytes.len() })?;
600        Self::try_new(f64::from_bits(u64::from_be_bytes(bytes)))
601            .ok_or(Float64DecodeError::NonFinite)
602    }
603}
604
605impl Display for Float64 {
606    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
607        Display::fmt(&self.0, formatter)
608    }
609}
610
611impl<'de> Deserialize<'de> for Float64 {
612    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
613    where
614        D: Deserializer<'de>,
615    {
616        Self::try_new(f64::deserialize(deserializer)?)
617            .ok_or_else(|| D::Error::custom("Float64 must be finite"))
618    }
619}
620
621impl Serialize for Float64 {
622    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
623    where
624        S: serde::Serializer,
625    {
626        serializer.serialize_f64(self.0)
627    }
628}
629
630impl Eq for Float64 {}
631
632impl From<Float64> for f64 {
633    fn from(value: Float64) -> Self {
634        value.0
635    }
636}
637
638impl From<i32> for Float64 {
639    fn from(value: i32) -> Self {
640        Self(f64::from(value))
641    }
642}
643
644impl Hash for Float64 {
645    fn hash<H: Hasher>(&self, state: &mut H) {
646        state.write_u64(self.0.to_bits());
647    }
648}
649
650impl Ord for Float64 {
651    fn cmp(&self, other: &Self) -> Ordering {
652        self.0.total_cmp(&other.0)
653    }
654}
655
656impl PartialEq for Float64 {
657    fn eq(&self, other: &Self) -> bool {
658        self.0 == other.0
659    }
660}
661
662impl PartialOrd for Float64 {
663    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
664        Some(self.cmp(other))
665    }
666}
667
668impl TryFrom<&[u8]> for Float64 {
669    type Error = Float64DecodeError;
670
671    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
672        Self::try_from_bytes(bytes)
673    }
674}