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