icydb_core/
key.rs

1use crate::{
2    traits::FieldValue,
3    types::{Account, Principal, Subaccount, Timestamp, Ulid, Unit},
4    value::Value,
5};
6use candid::{CandidType, Principal as WrappedPrincipal};
7use canic::impl_storable_bounded;
8use derive_more::Display;
9use serde::{Deserialize, Serialize};
10use std::cmp::Ordering;
11
12///
13/// Key
14///
15/// Treating IndexKey as the atomic, normalized unit of the keyspace
16/// Backing primary keys and secondary indexes with the same value representation
17/// Planning to enforce Copy semantics (i.e., fast, clean, safe)
18///
19
20#[derive(CandidType, Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize)]
21pub enum Key {
22    Account(Account),
23    Int(i64),
24    Principal(Principal),
25    Subaccount(Subaccount),
26    Timestamp(Timestamp),
27    Uint(u64),
28    Ulid(Ulid),
29    Unit,
30}
31
32impl Key {
33    pub const STORABLE_MAX_SIZE: u32 = 128;
34
35    #[must_use]
36    pub fn max_storable() -> Self {
37        Self::Account(Account::max_storable())
38    }
39
40    #[must_use]
41    pub const fn lower_bound() -> Self {
42        Self::Int(i64::MIN)
43    }
44
45    #[must_use]
46    pub const fn upper_bound() -> Self {
47        Self::Unit
48    }
49
50    const fn variant_rank(&self) -> u8 {
51        match self {
52            Self::Account(_) => 0,
53            Self::Int(_) => 1,
54            Self::Principal(_) => 2,
55            Self::Subaccount(_) => 3,
56            Self::Timestamp(_) => 4,
57            Self::Uint(_) => 5,
58            Self::Ulid(_) => 6,
59            Self::Unit => 7,
60        }
61    }
62}
63
64impl FieldValue for Key {
65    fn to_value(&self) -> Value {
66        match self {
67            Self::Account(v) => Value::Account(*v),
68            Self::Int(v) => Value::Int(*v),
69            Self::Principal(v) => Value::Principal(*v),
70            Self::Subaccount(v) => Value::Subaccount(*v),
71            Self::Timestamp(v) => Value::Timestamp(*v),
72            Self::Uint(v) => Value::Uint(*v),
73            Self::Ulid(v) => Value::Ulid(*v),
74            Self::Unit => Value::Unit,
75        }
76    }
77}
78
79impl From<()> for Key {
80    fn from((): ()) -> Self {
81        Self::Unit
82    }
83}
84
85impl From<Unit> for Key {
86    fn from(_: Unit) -> Self {
87        Self::Unit
88    }
89}
90
91impl PartialEq<()> for Key {
92    fn eq(&self, (): &()) -> bool {
93        matches!(self, Self::Unit)
94    }
95}
96
97impl PartialEq<Key> for () {
98    fn eq(&self, other: &Key) -> bool {
99        other == self
100    }
101}
102
103/// Implements `From<T> for Key` for simple conversions
104macro_rules! impl_from_key {
105    ( $( $ty:ty => $variant:ident ),* $(,)? ) => {
106        $(
107            impl From<$ty> for Key {
108                fn from(v: $ty) -> Self {
109                    Self::$variant(v.into())
110                }
111            }
112        )*
113    }
114}
115
116/// Implements symmetric PartialEq between Key and another type
117macro_rules! impl_eq_key {
118    ( $( $ty:ty => $variant:ident ),* $(,)? ) => {
119        $(
120            impl PartialEq<$ty> for Key {
121                fn eq(&self, other: &$ty) -> bool {
122                    matches!(self, Self::$variant(val) if val == other)
123                }
124            }
125
126            impl PartialEq<Key> for $ty {
127                fn eq(&self, other: &Key) -> bool {
128                    other == self
129                }
130            }
131        )*
132    }
133}
134
135impl_from_key! {
136    Account => Account,
137    i8  => Int,
138    i16 => Int,
139    i32 => Int,
140    i64 => Int,
141    Principal => Principal,
142    WrappedPrincipal => Principal,
143    Subaccount => Subaccount,
144    Timestamp => Timestamp,
145    u8  => Uint,
146    u16 => Uint,
147    u32 => Uint,
148    u64 => Uint,
149    Ulid => Ulid,
150}
151
152impl_eq_key! {
153    Account => Account,
154    i64 => Int,
155    Principal => Principal,
156    Subaccount => Subaccount,
157    Timestamp => Timestamp,
158    u64  => Uint,
159    Ulid => Ulid,
160}
161
162impl Ord for Key {
163    fn cmp(&self, other: &Self) -> Ordering {
164        match (self, other) {
165            (Self::Account(a), Self::Account(b)) => Ord::cmp(a, b),
166            (Self::Int(a), Self::Int(b)) => Ord::cmp(a, b),
167            (Self::Principal(a), Self::Principal(b)) => Ord::cmp(a, b),
168            (Self::Uint(a), Self::Uint(b)) => Ord::cmp(a, b),
169            (Self::Ulid(a), Self::Ulid(b)) => Ord::cmp(a, b),
170            (Self::Subaccount(a), Self::Subaccount(b)) => Ord::cmp(a, b),
171            (Self::Timestamp(a), Self::Timestamp(b)) => Ord::cmp(a, b),
172
173            _ => Ord::cmp(&self.variant_rank(), &other.variant_rank()), // fallback for cross-type comparison
174        }
175    }
176}
177
178impl PartialOrd for Key {
179    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
180        Some(Ord::cmp(self, other))
181    }
182}
183
184impl_storable_bounded!(Key, Self::STORABLE_MAX_SIZE, false);
185
186///
187/// TESTS
188///
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use crate::traits::Storable;
194
195    #[test]
196    fn key_max_size_is_bounded() {
197        let key = Key::max_storable();
198        let size = Storable::to_bytes(&key).len();
199
200        assert!(
201            size <= Key::STORABLE_MAX_SIZE as usize,
202            "serialized Key too large: got {size} bytes (limit {})",
203            Key::STORABLE_MAX_SIZE
204        );
205    }
206}