Skip to main content

ic_dbms_api/
principal.rs

1use std::fmt;
2
3use candid::CandidType;
4use serde::{Deserialize, Serialize};
5use wasm_dbms_api::prelude::{
6    DEFAULT_ALIGNMENT, DataSize, DataType, DecodeError, Encode, MSize, MemoryError, MemoryResult,
7    PageOffset,
8};
9use wasm_dbms_macros::CustomDataType;
10
11/// Principal data type for the IC DBMS.
12///
13/// This is an IC-specific custom data type that wraps [`candid::Principal`].
14/// Use with `#[custom_type]` annotation in table definitions.
15#[derive(
16    Clone, Debug, CustomDataType, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
17)]
18#[type_tag = "principal"]
19pub struct Principal(pub candid::Principal);
20
21impl Default for Principal {
22    fn default() -> Self {
23        Self(candid::Principal::anonymous())
24    }
25}
26
27impl CandidType for Principal {
28    fn _ty() -> candid::types::Type {
29        candid::types::Type(std::rc::Rc::new(candid::types::TypeInner::Principal))
30    }
31
32    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
33    where
34        S: candid::types::Serializer,
35    {
36        candid::Principal::idl_serialize(&self.0, serializer)
37    }
38}
39
40impl Encode for Principal {
41    const SIZE: DataSize = DataSize::Dynamic;
42
43    const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
44
45    fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
46        let principal_bytes = self.0.as_slice();
47        let mut bytes = Vec::with_capacity(1 + principal_bytes.len());
48        let len = principal_bytes.len() as u8;
49        bytes.push(len);
50        bytes.extend_from_slice(principal_bytes);
51        std::borrow::Cow::Owned(bytes)
52    }
53
54    fn decode(data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
55    where
56        Self: Sized,
57    {
58        if data.is_empty() {
59            return Err(MemoryError::DecodeError(DecodeError::TooShort));
60        }
61
62        let buf_len = data[0] as usize;
63
64        if data.len() < 1 + buf_len {
65            return Err(MemoryError::DecodeError(DecodeError::TooShort));
66        }
67
68        let principal = candid::Principal::try_from_slice(&data[1..1 + buf_len]).map_err(|e| {
69            MemoryError::DecodeError(DecodeError::IdentityDecodeError(e.to_string()))
70        })?;
71
72        Ok(Self(principal))
73    }
74
75    fn size(&self) -> MSize {
76        1 + self.0.as_slice().len() as MSize
77    }
78}
79
80impl fmt::Display for Principal {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{}", self.0)
83    }
84}
85
86impl DataType for Principal {}
87
88#[cfg(test)]
89mod tests {
90
91    use wasm_dbms_api::prelude::{CustomDataType as _, Value};
92
93    use super::*;
94
95    #[test]
96    fn test_principal_encode_decode() {
97        let original = Principal(
98            candid::Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").expect("invalid principal"),
99        );
100        let encoded = original.encode();
101        let decoded = Principal::decode(encoded).unwrap();
102        assert_eq!(original, decoded);
103    }
104
105    #[test]
106    fn test_principal_encode_decode_anonymous() {
107        let original = Principal(candid::Principal::anonymous());
108        let encoded = original.encode();
109        let decoded = Principal::decode(encoded).unwrap();
110        assert_eq!(original, decoded);
111    }
112
113    #[test]
114    fn test_should_candid_encode_decode() {
115        let src = Principal(
116            candid::Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").expect("invalid principal"),
117        );
118        let buf = candid::encode_one(&src).expect("Candid encoding failed");
119        let decoded: Principal = candid::decode_one(&buf).expect("Candid decoding failed");
120        assert_eq!(src, decoded);
121    }
122
123    #[test]
124    fn test_decode_empty_data_returns_too_short() {
125        let result = Principal::decode(std::borrow::Cow::Borrowed(&[]));
126        assert!(result.is_err());
127        assert!(matches!(
128            result.unwrap_err(),
129            MemoryError::DecodeError(DecodeError::TooShort)
130        ));
131    }
132
133    #[test]
134    fn test_decode_truncated_data_returns_too_short() {
135        // Length prefix says 10 bytes, but only 2 bytes follow
136        let data = vec![10, 0x01, 0x02];
137        let result = Principal::decode(std::borrow::Cow::Owned(data));
138        assert!(result.is_err());
139        assert!(matches!(
140            result.unwrap_err(),
141            MemoryError::DecodeError(DecodeError::TooShort)
142        ));
143    }
144
145    #[test]
146    fn test_size_returns_correct_value() {
147        let principal = Principal(
148            candid::Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").expect("invalid principal"),
149        );
150        let size = principal.size();
151        // 1 byte for length prefix + principal bytes length
152        assert_eq!(size, 1 + principal.0.as_slice().len() as MSize);
153    }
154
155    #[test]
156    fn test_size_anonymous_principal() {
157        let principal = Principal(candid::Principal::anonymous());
158        let size = principal.size();
159        assert_eq!(size, 1 + principal.0.as_slice().len() as MSize);
160    }
161
162    #[test]
163    fn test_display() {
164        let inner =
165            candid::Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").expect("invalid principal");
166        let principal = Principal(inner);
167        let display = format!("{principal}");
168        assert_eq!(display, "ryjl3-tyaaa-aaaaa-aaaba-cai");
169    }
170
171    #[test]
172    fn test_default_is_anonymous() {
173        let principal = Principal::default();
174        assert_eq!(principal.0, candid::Principal::anonymous());
175    }
176
177    #[test]
178    fn test_from_principal_to_value() {
179        let principal = Principal(
180            candid::Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").expect("invalid principal"),
181        );
182        let value: Value = principal.into();
183        assert!(matches!(value, Value::Custom(_)));
184    }
185
186    #[test]
187    fn test_custom_data_type_tag() {
188        assert_eq!(Principal::TYPE_TAG, "principal");
189    }
190}