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