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    /// Parse an unprefixed, even-length ASCII hex schema literal.
188    ///
189    /// Empty input represents empty bytes. This authoring boundary caps decoded
190    /// bytes at [`crate::MAX_PROPOSAL_LITERAL_BYTES`]; runtime Blob values retain
191    /// their independent size admission.
192    ///
193    /// # Errors
194    /// Returns [`crate::SchemaContractError::InvalidLiteral`] for malformed hex
195    /// or oversized input, before allocating the decoded buffer.
196    pub fn from_hex_literal(text: &str) -> Result<Self, crate::SchemaContractError> {
197        if text.len() > crate::MAX_PROPOSAL_LITERAL_BYTES * 2
198            || !text.len().is_multiple_of(2)
199            || !text.bytes().all(|byte| byte.is_ascii_hexdigit())
200        {
201            return Err(crate::SchemaContractError::InvalidLiteral);
202        }
203        text.as_bytes()
204            .as_chunks::<2>()
205            .0
206            .iter()
207            .map(|pair| {
208                let pair = std::str::from_utf8(pair)
209                    .map_err(|_| crate::SchemaContractError::InvalidLiteral)?;
210                u8::from_str_radix(pair, 16).map_err(|_| crate::SchemaContractError::InvalidLiteral)
211            })
212            .collect::<Result<Vec<_>, _>>()
213            .map(Self::from)
214    }
215
216    /// Borrow the canonical bytes.
217    #[must_use]
218    pub fn as_bytes(&self) -> &[u8] {
219        &self.0
220    }
221
222    /// Consume the atom and return its bytes.
223    #[must_use]
224    pub fn into_bytes(self) -> Vec<u8> {
225        self.0
226    }
227
228    /// Clone the canonical bytes.
229    #[must_use]
230    pub fn to_vec(&self) -> Vec<u8> {
231        self.0.clone()
232    }
233
234    /// Return the byte length.
235    #[must_use]
236    pub const fn len(&self) -> usize {
237        self.0.len()
238    }
239
240    /// Return whether the value is empty.
241    #[must_use]
242    pub const fn is_empty(&self) -> bool {
243        self.0.is_empty()
244    }
245}
246
247impl Display for Blob {
248    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
249        write!(formatter, "[blob ({} bytes)]", self.len())
250    }
251}
252
253impl From<Vec<u8>> for Blob {
254    fn from(bytes: Vec<u8>) -> Self {
255        Self(bytes)
256    }
257}
258
259impl From<&[u8]> for Blob {
260    fn from(bytes: &[u8]) -> Self {
261        Self(bytes.to_vec())
262    }
263}
264
265impl<const N: usize> From<&[u8; N]> for Blob {
266    fn from(bytes: &[u8; N]) -> Self {
267        Self(bytes.to_vec())
268    }
269}
270
271impl<'de> Deserialize<'de> for Blob {
272    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
273    where
274        D: Deserializer<'de>,
275    {
276        serde_bytes::ByteBuf::deserialize(deserializer).map(|bytes| Self(bytes.into_vec()))
277    }
278}
279
280/// Failure while parsing a canonical ULID.
281#[derive(Clone, Copy, Debug, Eq, PartialEq)]
282pub enum UlidParseError {
283    /// The input is not canonical ULID text.
284    InvalidString,
285}
286
287impl Display for UlidParseError {
288    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
289        formatter.write_str("ULID text is invalid")
290    }
291}
292
293/// Failure while decoding canonical ULID bytes.
294#[derive(Clone, Copy, Debug, Eq, PartialEq)]
295pub enum UlidDecodeError {
296    /// A ULID must contain exactly 16 bytes.
297    InvalidSize {
298        /// Actual byte length.
299        len: usize,
300    },
301}
302
303/// Canonical ULID atom without generation authority.
304///
305/// Wire and binary Serde carry exactly 16 bytes. Human-readable Serde and
306/// display use canonical 26-character text.
307#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
308#[repr(transparent)]
309pub struct Ulid([u8; 16]);
310
311impl Ulid {
312    /// Canonical byte length.
313    pub const STORED_SIZE: u32 = 16;
314
315    /// Minimum ULID.
316    pub const MIN: Self = Self::from_bytes([0x00; 16]);
317
318    /// Maximum ULID.
319    pub const MAX: Self = Self::from_bytes([0xFF; 16]);
320
321    /// Nil ULID.
322    #[must_use]
323    pub const fn nil() -> Self {
324        Self::MIN
325    }
326
327    /// Construct from the canonical 16-byte representation.
328    #[must_use]
329    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
330        Self(bytes)
331    }
332
333    /// Construct deterministically from the canonical unsigned integer form.
334    #[must_use]
335    pub const fn from_u128(value: u128) -> Self {
336        Self::from_bytes(value.to_be_bytes())
337    }
338
339    /// Return the canonical 16-byte representation.
340    #[must_use]
341    pub const fn to_bytes(self) -> [u8; 16] {
342        self.0
343    }
344
345    /// Decode exactly 16 canonical bytes.
346    ///
347    /// # Errors
348    ///
349    /// Returns a typed size error for every other length.
350    pub const fn try_from_bytes(bytes: &[u8]) -> Result<Self, UlidDecodeError> {
351        if bytes.len() != Self::STORED_SIZE as usize {
352            return Err(UlidDecodeError::InvalidSize { len: bytes.len() });
353        }
354        let mut value = [0; 16];
355        value.copy_from_slice(bytes);
356        Ok(Self::from_bytes(value))
357    }
358}
359
360impl Display for Ulid {
361    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
362        Display::fmt(&ulid::Ulid::from_bytes(self.0), formatter)
363    }
364}
365
366impl fmt::Debug for Ulid {
367    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
368        fmt::Debug::fmt(&self.to_string(), formatter)
369    }
370}
371
372impl FromStr for Ulid {
373    type Err = UlidParseError;
374
375    fn from_str(input: &str) -> Result<Self, Self::Err> {
376        let value = input
377            .parse::<ulid::Ulid>()
378            .map_err(|_| UlidParseError::InvalidString)?;
379        if value.to_string() != input {
380            return Err(UlidParseError::InvalidString);
381        }
382        Ok(Self::from_bytes(value.to_bytes()))
383    }
384}
385
386impl<'de> Deserialize<'de> for Ulid {
387    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
388    where
389        D: Deserializer<'de>,
390    {
391        if deserializer.is_human_readable() {
392            return String::deserialize(deserializer)?
393                .parse()
394                .map_err(D::Error::custom);
395        }
396
397        deserialize_fixed_bytes(deserializer).map(Self::from_bytes)
398    }
399}
400
401impl Serialize for Ulid {
402    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
403    where
404        S: serde::Serializer,
405    {
406        if serializer.is_human_readable() {
407            serializer.serialize_str(&self.to_string())
408        } else {
409            serializer.serialize_bytes(&self.0)
410        }
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(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
557/// Failure while decoding one finite 64-bit floating-point atom.
558#[derive(Clone, Copy, Debug, Eq, PartialEq)]
559pub enum Float64DecodeError {
560    /// The input did not contain exactly eight bytes.
561    InvalidSize {
562        /// Actual byte length.
563        len: usize,
564    },
565    /// The bytes represented NaN or infinity.
566    NonFinite,
567}
568
569/// Finite canonical `f64` atom.
570#[derive(Clone, Copy, Debug, Default)]
571#[repr(transparent)]
572pub struct Float64(f64);
573
574impl Float64 {
575    /// Construct a finite float and normalize negative zero.
576    #[must_use]
577    pub fn try_new(value: f64) -> Option<Self> {
578        if !value.is_finite() {
579            return None;
580        }
581        Some(Self(if value == 0.0 { 0.0 } else { value }))
582    }
583
584    /// Return the finite primitive value.
585    #[must_use]
586    pub const fn get(self) -> f64 {
587        self.0
588    }
589
590    /// Return the stable big-endian IEEE-754 representation.
591    #[must_use]
592    pub const fn to_be_bytes(&self) -> [u8; 8] {
593        self.0.to_bits().to_be_bytes()
594    }
595
596    /// Decode one stable big-endian IEEE-754 representation.
597    ///
598    /// # Errors
599    ///
600    /// Returns a typed error for the wrong byte length or a non-finite value.
601    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Float64DecodeError> {
602        let bytes: [u8; 8] = bytes
603            .try_into()
604            .map_err(|_| Float64DecodeError::InvalidSize { len: bytes.len() })?;
605        Self::try_new(f64::from_bits(u64::from_be_bytes(bytes)))
606            .ok_or(Float64DecodeError::NonFinite)
607    }
608}
609
610impl Display for Float64 {
611    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
612        Display::fmt(&self.0, formatter)
613    }
614}
615
616impl<'de> Deserialize<'de> for Float64 {
617    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
618    where
619        D: Deserializer<'de>,
620    {
621        Self::try_new(f64::deserialize(deserializer)?)
622            .ok_or_else(|| D::Error::custom("Float64 must be finite"))
623    }
624}
625
626impl Serialize for Float64 {
627    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
628    where
629        S: serde::Serializer,
630    {
631        serializer.serialize_f64(self.0)
632    }
633}
634
635impl Eq for Float64 {}
636
637impl From<Float64> for f64 {
638    fn from(value: Float64) -> Self {
639        value.0
640    }
641}
642
643impl From<i32> for Float64 {
644    fn from(value: i32) -> Self {
645        Self(f64::from(value))
646    }
647}
648
649impl Hash for Float64 {
650    fn hash<H: Hasher>(&self, state: &mut H) {
651        state.write_u64(self.0.to_bits());
652    }
653}
654
655impl Ord for Float64 {
656    fn cmp(&self, other: &Self) -> Ordering {
657        self.0.total_cmp(&other.0)
658    }
659}
660
661impl PartialEq for Float64 {
662    fn eq(&self, other: &Self) -> bool {
663        self.0 == other.0
664    }
665}
666
667impl PartialOrd for Float64 {
668    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
669        Some(self.cmp(other))
670    }
671}
672
673impl TryFrom<&[u8]> for Float64 {
674    type Error = Float64DecodeError;
675
676    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
677        Self::try_from_bytes(bytes)
678    }
679}
680
681impl Serialize for Blob {
682    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
683        serializer.serialize_bytes(self.as_bytes())
684    }
685}
686
687// Fixed atom widths are checked before copying and without allocating a Vec.
688// Reading one excess sequence byte rejects oversized self-describing arrays.
689pub(crate) fn deserialize_fixed_bytes<'de, D: Deserializer<'de>, const N: usize>(
690    deserializer: D,
691) -> Result<[u8; N], D::Error> {
692    struct Fixed<const N: usize>;
693    impl<'de, const N: usize> serde::de::Visitor<'de> for Fixed<N> {
694        type Value = [u8; N];
695        fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
696            write!(f, "exactly {N} bytes")
697        }
698        fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
699            bytes
700                .try_into()
701                .map_err(|_| E::invalid_length(bytes.len(), &self))
702        }
703        fn visit_seq<A: serde::de::SeqAccess<'de>>(
704            self,
705            mut values: A,
706        ) -> Result<Self::Value, A::Error> {
707            let mut bytes = [0; N];
708            for (index, byte) in bytes.iter_mut().enumerate() {
709                *byte = values
710                    .next_element()?
711                    .ok_or_else(|| A::Error::invalid_length(index, &self))?;
712            }
713            if values.next_element::<u8>()?.is_some() {
714                return Err(A::Error::invalid_length(N + 1, &self));
715            }
716            Ok(bytes)
717        }
718    }
719    deserializer.deserialize_bytes(Fixed::<N>)
720}