Skip to main content

reifydb_core/key/
identity.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::borrow::Cow;
5
6use reifydb_codec::key::encoded::EncodedKey;
7use reifydb_macro::KeyCodec;
8use reifydb_value::value::identity::IdentityId;
9
10use super::KeyTag;
11use crate::{
12	interface::catalog::{
13		authentication::AuthenticationId,
14		identity::{IdentityAttributeId, RoleId},
15		policy::PolicyId,
16		token::TokenId,
17	},
18	key::{
19		any::{ByteEncoding, Field, KeyFields, Width},
20		bound::TaggedKeyBoundRange,
21	},
22};
23
24#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
25#[key(tag = Identity)]
26pub struct IdentityKey {
27	pub identity: IdentityId,
28}
29
30impl IdentityKey {
31	pub fn new(identity: IdentityId) -> Self {
32		Self {
33			identity,
34		}
35	}
36
37	pub fn encoded(identity: IdentityId) -> EncodedKey {
38		Self::new(identity).encode()
39	}
40
41	pub fn full_scan() -> TaggedKeyBoundRange {
42		TaggedKeyBoundRange::kind(Self::TAG)
43	}
44}
45
46#[cfg(test)]
47mod byte_identical_identity_key {
48	use reifydb_codec::key::serializer::KeySerializer;
49	use reifydb_value::value::uuid::Uuid7;
50	use uuid::Uuid;
51
52	use super::*;
53
54	fn legacy_encode(key: &IdentityKey) -> EncodedKey {
55		let mut serializer = KeySerializer::with_capacity(17);
56		serializer.extend_u8(KeyTag::Identity as u8).extend_identity_id(&key.identity);
57		serializer.to_encoded_key()
58	}
59
60	#[test]
61	fn matches_the_flat_key_encoding() {
62		for byte in [0u8, 1, 2] {
63			let identity = IdentityId::from(Uuid7::from(Uuid::from_bytes([byte; 16])));
64			let key = IdentityKey::new(identity);
65			assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
66		}
67	}
68}
69
70#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
71#[key(tag = IdentityAttribute)]
72pub struct IdentityAttributeKey {
73	pub attribute: IdentityAttributeId,
74}
75
76impl IdentityAttributeKey {
77	pub fn new(attribute: IdentityAttributeId) -> Self {
78		Self {
79			attribute,
80		}
81	}
82
83	pub fn encoded(attribute: IdentityAttributeId) -> EncodedKey {
84		Self::new(attribute).encode()
85	}
86
87	pub fn full_scan() -> TaggedKeyBoundRange {
88		TaggedKeyBoundRange::kind(Self::TAG)
89	}
90}
91
92#[cfg(test)]
93mod byte_identical_identity_attribute_key {
94	use reifydb_codec::key::serializer::KeySerializer;
95
96	use super::*;
97
98	fn legacy_encode(key: &IdentityAttributeKey) -> EncodedKey {
99		let mut serializer = KeySerializer::with_capacity(9);
100		serializer.extend_u8(KeyTag::IdentityAttribute as u8).extend_u64(key.attribute);
101		serializer.to_encoded_key()
102	}
103
104	#[test]
105	fn matches_the_flat_key_encoding() {
106		for attribute in [0u64, 1, 42, u64::MAX] {
107			let key = IdentityAttributeKey::new(attribute);
108			assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
109		}
110	}
111}
112
113#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
114#[key(tag = IdentityAttributeValue)]
115pub struct IdentityAttributeValueKey {
116	pub identity: IdentityId,
117	pub attribute: IdentityAttributeId,
118}
119
120impl IdentityAttributeValueKey {
121	pub fn new(identity: IdentityId, attribute: IdentityAttributeId) -> Self {
122		Self {
123			identity,
124			attribute,
125		}
126	}
127
128	pub fn encoded(identity: IdentityId, attribute: IdentityAttributeId) -> EncodedKey {
129		Self::new(identity, attribute).encode()
130	}
131
132	pub fn full_scan() -> TaggedKeyBoundRange {
133		TaggedKeyBoundRange::kind(Self::TAG)
134	}
135
136	pub fn identity_scan(identity: IdentityId) -> TaggedKeyBoundRange {
137		TaggedKeyBoundRange::prefix(
138			Self::TAG,
139			[Field::BytesDesc(ByteEncoding::Escaped, Cow::Owned(identity.as_bytes().to_vec()))],
140		)
141	}
142}
143
144#[cfg(test)]
145mod byte_identical_identity_attribute_value_key {
146	use reifydb_codec::key::serializer::KeySerializer;
147	use reifydb_value::value::uuid::Uuid7;
148	use uuid::Uuid;
149
150	use super::*;
151
152	fn legacy_encode(key: &IdentityAttributeValueKey) -> EncodedKey {
153		let mut serializer = KeySerializer::with_capacity(25);
154		serializer
155			.extend_u8(KeyTag::IdentityAttributeValue as u8)
156			.extend_identity_id(&key.identity)
157			.extend_u64(key.attribute);
158		serializer.to_encoded_key()
159	}
160
161	#[test]
162	fn matches_the_flat_key_encoding() {
163		for byte in [0u8, 1, 2] {
164			let identity = IdentityId::from(Uuid7::from(Uuid::from_bytes([byte; 16])));
165			for attribute in [0u64, 1, u64::MAX] {
166				let key = IdentityAttributeValueKey::new(identity, attribute);
167				assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
168			}
169		}
170	}
171}
172
173#[cfg(test)]
174mod identity_attribute_value_key_tests {
175	use std::ops::RangeBounds;
176
177	use reifydb_value::value::{identity::IdentityId, uuid::Uuid7};
178	use uuid::Uuid;
179
180	use super::*;
181
182	fn identity(byte: u8) -> IdentityId {
183		IdentityId::from(Uuid7::from(Uuid::from_bytes([byte; 16])))
184	}
185
186	#[test]
187	fn identity_scan_holds_every_attribute_of_that_identity() {
188		// Small ids complement to a suffix starting 0xFF, so a prefix+0xFF end bound excludes them all.
189		let alice = identity(1);
190		let range = IdentityAttributeValueKey::identity_scan(alice).encode();
191
192		for attribute in [0u64, 1, 2, u64::MAX] {
193			let key = IdentityAttributeValueKey::encoded(alice, attribute);
194			assert!(range.contains(&key), "attribute {attribute} must fall inside the identity scan");
195		}
196	}
197
198	#[test]
199	fn identity_scan_excludes_a_neighbouring_identity() {
200		// The scan must not widen into the next identity when the bound is carry-incremented.
201		let range = IdentityAttributeValueKey::identity_scan(identity(1)).encode();
202		let other = IdentityAttributeValueKey::encoded(identity(2), 1);
203
204		assert!(!range.contains(&other), "a different identity must stay outside the scan");
205	}
206}
207
208#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
209#[key(tag = Authentication)]
210pub struct AuthenticationKey {
211	pub authentication: AuthenticationId,
212}
213
214impl AuthenticationKey {
215	pub fn new(authentication: AuthenticationId) -> Self {
216		Self {
217			authentication,
218		}
219	}
220
221	pub fn encoded(authentication: AuthenticationId) -> EncodedKey {
222		Self::new(authentication).encode()
223	}
224
225	pub fn full_scan() -> TaggedKeyBoundRange {
226		TaggedKeyBoundRange::kind(Self::TAG)
227	}
228}
229
230#[cfg(test)]
231mod byte_identical_authentication_key {
232	use reifydb_codec::key::serializer::KeySerializer;
233
234	use super::*;
235
236	fn legacy_encode(key: &AuthenticationKey) -> EncodedKey {
237		let mut serializer = KeySerializer::with_capacity(9);
238		serializer.extend_u8(KeyTag::Authentication as u8).extend_u64(key.authentication);
239		serializer.to_encoded_key()
240	}
241
242	#[test]
243	fn matches_the_flat_key_encoding() {
244		for authentication in [0u64, 1, 42, u64::MAX] {
245			let key = AuthenticationKey::new(authentication);
246			assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
247		}
248	}
249}
250
251#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
252#[key(tag = Token)]
253pub struct TokenKey {
254	pub token: TokenId,
255}
256
257impl TokenKey {
258	pub fn new(token: TokenId) -> Self {
259		Self {
260			token,
261		}
262	}
263
264	pub fn encoded(token: TokenId) -> EncodedKey {
265		Self::new(token).encode()
266	}
267
268	pub fn full_scan() -> TaggedKeyBoundRange {
269		TaggedKeyBoundRange::kind(Self::TAG)
270	}
271}
272
273#[cfg(test)]
274mod byte_identical_token_key {
275	use reifydb_codec::key::serializer::KeySerializer;
276
277	use super::*;
278
279	fn legacy_encode(key: &TokenKey) -> EncodedKey {
280		let mut serializer = KeySerializer::with_capacity(9);
281		serializer.extend_u8(KeyTag::Token as u8).extend_u64(key.token);
282		serializer.to_encoded_key()
283	}
284
285	#[test]
286	fn matches_the_flat_key_encoding() {
287		for token in [0u64, 1, 42, u64::MAX] {
288			let key = TokenKey::new(token);
289			assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
290		}
291	}
292}
293
294#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
295#[key(tag = Role)]
296pub struct RoleKey {
297	pub role: RoleId,
298}
299
300impl RoleKey {
301	pub fn new(role: RoleId) -> Self {
302		Self {
303			role,
304		}
305	}
306
307	pub fn encoded(role: RoleId) -> EncodedKey {
308		Self::new(role).encode()
309	}
310
311	pub fn full_scan() -> TaggedKeyBoundRange {
312		TaggedKeyBoundRange::kind(Self::TAG)
313	}
314}
315
316#[cfg(test)]
317mod byte_identical_role_key {
318	use reifydb_codec::key::serializer::KeySerializer;
319
320	use super::*;
321
322	fn legacy_encode(key: &RoleKey) -> EncodedKey {
323		let mut serializer = KeySerializer::with_capacity(9);
324		serializer.extend_u8(KeyTag::Role as u8).extend_u64(key.role);
325		serializer.to_encoded_key()
326	}
327
328	#[test]
329	fn matches_the_flat_key_encoding() {
330		for role in [0u64, 1, 42, u64::MAX] {
331			let key = RoleKey::new(role);
332			assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
333		}
334	}
335}
336
337#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
338#[key(tag = GrantedRole)]
339pub struct GrantedRoleKey {
340	pub identity: IdentityId,
341	pub role: RoleId,
342}
343
344impl GrantedRoleKey {
345	pub fn new(identity: IdentityId, role: RoleId) -> Self {
346		Self {
347			identity,
348			role,
349		}
350	}
351
352	pub fn encoded(identity: IdentityId, role: RoleId) -> EncodedKey {
353		Self::new(identity, role).encode()
354	}
355
356	pub fn full_scan() -> TaggedKeyBoundRange {
357		TaggedKeyBoundRange::kind(Self::TAG)
358	}
359
360	pub fn identity_scan(identity: IdentityId) -> TaggedKeyBoundRange {
361		TaggedKeyBoundRange::prefix(
362			Self::TAG,
363			[Field::BytesDesc(ByteEncoding::Escaped, Cow::Owned(identity.as_bytes().to_vec()))],
364		)
365	}
366}
367
368#[cfg(test)]
369mod byte_identical_granted_role_key {
370	use reifydb_codec::key::serializer::KeySerializer;
371	use reifydb_value::value::uuid::Uuid7;
372	use uuid::Uuid;
373
374	use super::*;
375
376	fn legacy_encode(key: &GrantedRoleKey) -> EncodedKey {
377		let mut serializer = KeySerializer::with_capacity(25);
378		serializer.extend_u8(KeyTag::GrantedRole as u8).extend_identity_id(&key.identity).extend_u64(key.role);
379		serializer.to_encoded_key()
380	}
381
382	#[test]
383	fn matches_the_flat_key_encoding() {
384		for byte in [0u8, 1, 2] {
385			let identity = IdentityId::from(Uuid7::from(Uuid::from_bytes([byte; 16])));
386			for role in [0u64, 1, u64::MAX] {
387				let key = GrantedRoleKey::new(identity, role);
388				assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
389			}
390		}
391	}
392}
393
394#[cfg(test)]
395mod granted_role_key_tests {
396	use std::ops::RangeBounds;
397
398	use reifydb_value::value::{identity::IdentityId, uuid::Uuid7};
399	use uuid::Uuid;
400
401	use super::*;
402
403	fn identity(byte: u8) -> IdentityId {
404		IdentityId::from(Uuid7::from(Uuid::from_bytes([byte; 16])))
405	}
406
407	#[test]
408	fn identity_scan_holds_every_role_of_that_identity() {
409		// Small ids complement to a suffix starting 0xFF, so a prefix+0xFF end bound excludes them all.
410		let alice = identity(1);
411		let range = GrantedRoleKey::identity_scan(alice).encode();
412
413		for role in [0u64, 1, 2, u64::MAX] {
414			let key = GrantedRoleKey::encoded(alice, role);
415			assert!(range.contains(&key), "role {role} must fall inside the identity scan");
416		}
417	}
418
419	#[test]
420	fn identity_scan_excludes_a_neighbouring_identity() {
421		// The scan must not widen into the next identity when the bound is carry-incremented.
422		let range = GrantedRoleKey::identity_scan(identity(1)).encode();
423
424		assert!(!range.contains(&GrantedRoleKey::encoded(identity(2), 1)));
425	}
426}
427
428#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
429#[key(tag = Policy)]
430pub struct PolicyKey {
431	pub policy: PolicyId,
432}
433
434impl PolicyKey {
435	pub fn new(policy: PolicyId) -> Self {
436		Self {
437			policy,
438		}
439	}
440
441	pub fn encoded(policy: PolicyId) -> EncodedKey {
442		Self::new(policy).encode()
443	}
444
445	pub fn full_scan() -> TaggedKeyBoundRange {
446		TaggedKeyBoundRange::kind(Self::TAG)
447	}
448}
449
450#[cfg(test)]
451mod byte_identical_policy_key {
452	use reifydb_codec::key::serializer::KeySerializer;
453
454	use super::*;
455
456	fn legacy_encode(key: &PolicyKey) -> EncodedKey {
457		let mut serializer = KeySerializer::with_capacity(9);
458		serializer.extend_u8(KeyTag::Policy as u8).extend_u64(key.policy);
459		serializer.to_encoded_key()
460	}
461
462	#[test]
463	fn matches_the_flat_key_encoding() {
464		for policy in [0u64, 1, 42, u64::MAX] {
465			let key = PolicyKey::new(policy);
466			assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
467		}
468	}
469}
470
471#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
472#[key(tag = PolicyOp)]
473pub struct PolicyOpKey {
474	pub policy: PolicyId,
475	pub op_index: u64,
476}
477
478impl PolicyOpKey {
479	pub fn new(policy: PolicyId, op_index: u64) -> Self {
480		Self {
481			policy,
482			op_index,
483		}
484	}
485
486	pub fn encoded(policy: PolicyId, op_index: u64) -> EncodedKey {
487		Self::new(policy, op_index).encode()
488	}
489
490	pub fn full_scan() -> TaggedKeyBoundRange {
491		TaggedKeyBoundRange::kind(Self::TAG)
492	}
493
494	pub fn policy_scan(policy: PolicyId) -> TaggedKeyBoundRange {
495		TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, policy as u128)])
496	}
497}
498
499#[cfg(test)]
500mod byte_identical_policy_op_key {
501	use reifydb_codec::key::serializer::KeySerializer;
502
503	use super::*;
504
505	fn legacy_encode(key: &PolicyOpKey) -> EncodedKey {
506		let mut serializer = KeySerializer::with_capacity(17);
507		serializer.extend_u8(KeyTag::PolicyOp as u8).extend_u64(key.policy).extend_u64(key.op_index);
508		serializer.to_encoded_key()
509	}
510
511	#[test]
512	fn matches_the_flat_key_encoding() {
513		for policy in [0u64, 1, u64::MAX] {
514			for op_index in [0u64, 1, u64::MAX] {
515				let key = PolicyOpKey::new(policy, op_index);
516				assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
517			}
518		}
519	}
520}
521
522#[cfg(test)]
523mod policy_op_key_tests {
524	use std::ops::RangeBounds;
525
526	use super::*;
527
528	#[test]
529	fn policy_scan_holds_every_op_index_of_that_policy() {
530		// A fixed 0xFF-padded end bound only covers suffixes of exactly its own width.
531		let range = PolicyOpKey::policy_scan(7).encode();
532
533		for op_index in [0u64, 1, 2, u64::MAX] {
534			let key = PolicyOpKey::encoded(7, op_index);
535			assert!(range.contains(&key), "op index {op_index} must fall inside the policy scan");
536		}
537	}
538
539	#[test]
540	fn policy_scan_excludes_a_neighbouring_policy() {
541		// The scan must not widen into the next policy when the bound is carry-incremented.
542		let range = PolicyOpKey::policy_scan(7).encode();
543
544		assert!(!range.contains(&PolicyOpKey::encoded(6, 1)));
545		assert!(!range.contains(&PolicyOpKey::encoded(8, 1)));
546	}
547}