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_memory::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    /// Sentinel key representing the maximum storable account value.
37    pub fn max_storable() -> Self {
38        Self::Account(Account::max_storable())
39    }
40
41    #[must_use]
42    pub const fn lower_bound() -> Self {
43        Self::Int(i64::MIN)
44    }
45
46    #[must_use]
47    pub const fn upper_bound() -> Self {
48        Self::Unit
49    }
50
51    const fn variant_rank(&self) -> u8 {
52        match self {
53            Self::Account(_) => 0,
54            Self::Int(_) => 1,
55            Self::Principal(_) => 2,
56            Self::Subaccount(_) => 3,
57            Self::Timestamp(_) => 4,
58            Self::Uint(_) => 5,
59            Self::Ulid(_) => 6,
60            Self::Unit => 7,
61        }
62    }
63}
64
65impl FieldValue for Key {
66    fn to_value(&self) -> Value {
67        match self {
68            Self::Account(v) => Value::Account(*v),
69            Self::Int(v) => Value::Int(*v),
70            Self::Principal(v) => Value::Principal(*v),
71            Self::Subaccount(v) => Value::Subaccount(*v),
72            Self::Timestamp(v) => Value::Timestamp(*v),
73            Self::Uint(v) => Value::Uint(*v),
74            Self::Ulid(v) => Value::Ulid(*v),
75            Self::Unit => Value::Unit,
76        }
77    }
78}
79
80impl From<()> for Key {
81    fn from((): ()) -> Self {
82        Self::Unit
83    }
84}
85
86impl From<Unit> for Key {
87    fn from(_: Unit) -> Self {
88        Self::Unit
89    }
90}
91
92impl PartialEq<()> for Key {
93    fn eq(&self, (): &()) -> bool {
94        matches!(self, Self::Unit)
95    }
96}
97
98impl PartialEq<Key> for () {
99    fn eq(&self, other: &Key) -> bool {
100        other == self
101    }
102}
103
104/// Implements `From<T> for Key` for simple conversions
105macro_rules! impl_from_key {
106    ( $( $ty:ty => $variant:ident ),* $(,)? ) => {
107        $(
108            impl From<$ty> for Key {
109                fn from(v: $ty) -> Self {
110                    Self::$variant(v.into())
111                }
112            }
113        )*
114    }
115}
116
117/// Implements symmetric PartialEq between Key and another type
118macro_rules! impl_eq_key {
119    ( $( $ty:ty => $variant:ident ),* $(,)? ) => {
120        $(
121            impl PartialEq<$ty> for Key {
122                fn eq(&self, other: &$ty) -> bool {
123                    matches!(self, Self::$variant(val) if val == other)
124                }
125            }
126
127            impl PartialEq<Key> for $ty {
128                fn eq(&self, other: &Key) -> bool {
129                    other == self
130                }
131            }
132        )*
133    }
134}
135
136impl_from_key! {
137    Account => Account,
138    i8  => Int,
139    i16 => Int,
140    i32 => Int,
141    i64 => Int,
142    Principal => Principal,
143    WrappedPrincipal => Principal,
144    Subaccount => Subaccount,
145    Timestamp => Timestamp,
146    u8  => Uint,
147    u16 => Uint,
148    u32 => Uint,
149    u64 => Uint,
150    Ulid => Ulid,
151}
152
153impl_eq_key! {
154    Account => Account,
155    i64 => Int,
156    Principal => Principal,
157    Subaccount => Subaccount,
158    Timestamp => Timestamp,
159    u64  => Uint,
160    Ulid => Ulid,
161}
162
163impl Ord for Key {
164    fn cmp(&self, other: &Self) -> Ordering {
165        match (self, other) {
166            (Self::Account(a), Self::Account(b)) => Ord::cmp(a, b),
167            (Self::Int(a), Self::Int(b)) => Ord::cmp(a, b),
168            (Self::Principal(a), Self::Principal(b)) => Ord::cmp(a, b),
169            (Self::Uint(a), Self::Uint(b)) => Ord::cmp(a, b),
170            (Self::Ulid(a), Self::Ulid(b)) => Ord::cmp(a, b),
171            (Self::Subaccount(a), Self::Subaccount(b)) => Ord::cmp(a, b),
172            (Self::Timestamp(a), Self::Timestamp(b)) => Ord::cmp(a, b),
173
174            _ => Ord::cmp(&self.variant_rank(), &other.variant_rank()), // fallback for cross-type comparison
175        }
176    }
177}
178
179impl PartialOrd for Key {
180    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
181        Some(Ord::cmp(self, other))
182    }
183}
184
185impl_storable_bounded!(Key, Self::STORABLE_MAX_SIZE, false);
186
187///
188/// TESTS
189///
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use crate::traits::Storable;
195
196    #[test]
197    fn key_max_size_is_bounded() {
198        let key = Key::max_storable();
199        let size = Storable::to_bytes(&key).len();
200
201        assert!(
202            size <= Key::STORABLE_MAX_SIZE as usize,
203            "serialized Key too large: got {size} bytes (limit {})",
204            Key::STORABLE_MAX_SIZE
205        );
206    }
207}