1use std::{
3 fmt::{Debug, Display},
4 hash::Hash,
5 marker::PhantomData,
6 str::FromStr,
7};
8
9use anyhow::{anyhow, Context};
10use kind::{IdKind, Kind};
11use rand::Rng;
12use serde::{Deserialize, Serialize};
13use subset::{IdKindSubset, IdKindSupersetOf};
14
15use crate::FromStrVisitor;
16
17pub struct Id128<K>([u8; 16], PhantomData<K>);
19
20impl<K> Id128<K> {
21 pub const fn from_uint(val: u128) -> Self {
23 Self(val.to_be_bytes(), PhantomData)
24 }
25
26 pub const fn from_raw_array(array: &[u8; 16]) -> Self {
28 Self(*array, PhantomData)
29 }
30
31 pub const fn to_raw_array(self) -> [u8; 16] {
33 self.0
34 }
35
36 pub fn from_raw_bytes(bytes: &[u8]) -> Option<Self> {
38 Some(Self(bytes.try_into().ok()?, PhantomData))
39 }
40
41 pub fn random() -> Self {
43 loop {
44 let id: u128 = rand::thread_rng().gen();
45 if id > u16::MAX as u128 {
47 return Self(id.to_be_bytes(), PhantomData);
48 }
49 }
50 }
51
52 pub fn to_uint(&self) -> u128 {
54 u128::from_be_bytes(self.0)
55 }
56}
57
58impl<K: IdKind> Id128<K> {
59 pub fn upcast<KS: IdKindSubset + IdKindSupersetOf<K>>(self) -> DynamicId<KS> {
61 DynamicId {
62 id: self.0,
63 kind: K::kind(),
64 _subset: PhantomData,
65 }
66 }
67}
68
69impl<K> Clone for Id128<K> {
70 fn clone(&self) -> Self {
71 *self
72 }
73}
74
75impl<K> Copy for Id128<K> {}
76
77impl<K> PartialEq for Id128<K> {
78 fn eq(&self, other: &Self) -> bool {
79 self.0 == other.0
80 }
81}
82
83impl<K> Eq for Id128<K> {}
84
85impl<K> PartialOrd<Id128<K>> for Id128<K> {
86 fn partial_cmp(&self, other: &Id128<K>) -> Option<std::cmp::Ordering> {
87 Some(self.0.cmp(&other.0))
88 }
89}
90
91impl<K> Ord for Id128<K> {
92 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
93 self.0.cmp(&other.0)
94 }
95}
96
97impl<K> Hash for Id128<K> {
98 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
99 self.0.hash(state);
100 }
101}
102
103impl<K> From<[u8; 16]> for Id128<K> {
104 fn from(value: [u8; 16]) -> Self {
105 Self(value, PhantomData)
106 }
107}
108
109impl<K> From<&[u8; 16]> for Id128<K> {
110 fn from(value: &[u8; 16]) -> Self {
111 Self(*value, PhantomData)
112 }
113}
114
115impl<K> Debug for Id128<K> {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 write!(f, "{}", hexhex::hex(&self.0))
118 }
119}
120
121impl<K: IdKind> Display for Id128<K> {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 write!(f, "{}.{}", K::kind().str_prefix(), hexhex::hex(&self.0))
124 }
125}
126
127pub trait Id128DynamicArrayConv: Sized {
129 fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self>;
131
132 fn try_from_bytes_dynamic(bytes: &[u8]) -> Option<Self> {
134 Self::try_from_array_dynamic(bytes.try_into().ok()?)
135 }
136
137 fn to_array_dynamic(&self) -> [u8; 17];
139}
140
141pub mod kind {
143 use int_enum::IntEnum;
144 use serde::{Deserialize, Serialize};
145
146 #[derive(
152 Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntEnum, Serialize, Deserialize, Debug,
153 )]
154 #[repr(u8)]
155 pub enum Kind {
156 Persona = 0,
158 Group = 1,
160 Service = 2,
162 Domain = 3,
164 Policy = 4,
166 PolicyBinding = 5,
168 Property = 6,
170 Attribute = 7,
172 Directory = 8,
174 }
175
176 impl Kind {
177 #[inline]
178 pub(super) const fn str_prefix(&self) -> &'static str {
179 match self {
180 Self::Persona => "p",
181 Self::Group => "g",
182 Self::Service => "s",
183 Self::Domain => "d",
184 Self::Property => "prp",
185 Self::Attribute => "atr",
186 Self::Directory => "dir",
187 Self::Policy => "pol",
188 Self::PolicyBinding => "plb",
189 }
190 }
191
192 pub(super) const fn name(&self) -> &'static str {
193 match self {
194 Kind::Persona => "persona ID",
195 Kind::Group => "group ID",
196 Kind::Service => "service ID",
197 Kind::Domain => "domain ID",
198 Kind::Policy => "policy ID",
199 Kind::PolicyBinding => "policy binding ID",
200 Kind::Property => "property ID",
201 Kind::Attribute => "attribute ID",
202 Kind::Directory => "directory ID",
203 }
204 }
205
206 pub(super) const fn entries() -> &'static [Self] {
207 &[
208 Self::Persona,
209 Self::Group,
210 Self::Service,
211 Self::Domain,
212 Self::Policy,
213 Self::PolicyBinding,
214 Self::Property,
215 Self::Attribute,
216 Self::Directory,
217 ]
218 }
219 }
220
221 pub trait IdKind {
223 fn kind() -> Kind;
225 }
226
227 pub struct Persona;
229
230 pub struct Group;
232
233 pub struct Service;
235
236 pub struct Domain;
238
239 pub struct Policy;
241
242 pub struct PolicyBinding;
244
245 pub struct Property;
247
248 pub struct Attrbute;
250
251 pub struct Directory;
253
254 impl IdKind for Persona {
255 fn kind() -> Kind {
256 Kind::Persona
257 }
258 }
259
260 impl IdKind for Group {
261 fn kind() -> Kind {
262 Kind::Group
263 }
264 }
265
266 impl IdKind for Service {
267 fn kind() -> Kind {
268 Kind::Service
269 }
270 }
271
272 impl IdKind for Domain {
273 fn kind() -> Kind {
274 Kind::Domain
275 }
276 }
277
278 impl IdKind for Policy {
279 fn kind() -> Kind {
280 Kind::Policy
281 }
282 }
283
284 impl IdKind for PolicyBinding {
285 fn kind() -> Kind {
286 Kind::PolicyBinding
287 }
288 }
289
290 impl IdKind for Property {
291 fn kind() -> Kind {
292 Kind::Property
293 }
294 }
295
296 impl IdKind for Attrbute {
297 fn kind() -> Kind {
298 Kind::Attribute
299 }
300 }
301
302 impl IdKind for Directory {
303 fn kind() -> Kind {
304 Kind::Directory
305 }
306 }
307}
308
309pub mod subset {
311 use super::kind::{Group, IdKind, Kind, Persona, Service};
312
313 pub trait IdKindSubset {
315 fn contains(kind: Kind) -> bool;
317
318 fn name() -> &'static str;
320 }
321
322 pub trait IdKindSupersetOf<K> {}
324
325 pub struct Entity;
327
328 pub struct Any;
330
331 impl IdKindSubset for Entity {
332 fn contains(kind: Kind) -> bool {
333 matches!(kind, Kind::Persona | Kind::Group | Kind::Service)
334 }
335
336 fn name() -> &'static str {
337 "Entity ID"
338 }
339 }
340
341 impl IdKindSupersetOf<Persona> for Entity {}
342 impl IdKindSupersetOf<Group> for Entity {}
343 impl IdKindSupersetOf<Service> for Entity {}
344
345 impl IdKindSubset for Any {
346 fn contains(_kind: Kind) -> bool {
347 true
348 }
349
350 fn name() -> &'static str {
351 "Any ID"
352 }
353 }
354
355 impl<K: IdKind> IdKindSupersetOf<K> for Any {}
356 impl IdKindSupersetOf<Entity> for Any {}
357}
358
359pub type PersonaId = Id128<kind::Persona>;
361
362pub type GroupId = Id128<kind::Group>;
364
365pub type ServiceId = Id128<kind::Service>;
367
368pub type PropId = Id128<kind::Property>;
370
371pub type AttrId = Id128<kind::Attrbute>;
373
374pub type PolicyId = Id128<kind::Policy>;
376
377pub type PolicyBindingId = Id128<kind::PolicyBinding>;
379
380pub type DomainId = Id128<kind::Domain>;
382
383pub type DirectoryId = Id128<kind::Directory>;
385
386pub struct DynamicId<KS: IdKindSubset> {
388 pub(crate) id: [u8; 16],
389 kind: kind::Kind,
390 _subset: PhantomData<KS>,
391}
392
393impl<KS: IdKindSubset> DynamicId<KS> {
394 pub fn new(kind: Kind, id: [u8; 16]) -> Self {
398 if !KS::contains(kind) {
399 panic!("Not in subset");
400 }
401 Self {
402 kind,
403 id,
404 _subset: PhantomData,
405 }
406 }
407
408 pub fn kind(&self) -> Kind {
410 self.kind
411 }
412
413 pub fn upcast<KS2: IdKindSubset + IdKindSupersetOf<KS>>(&self) -> DynamicId<KS2> {
415 DynamicId {
416 id: self.id,
417 kind: self.kind,
418 _subset: PhantomData,
419 }
420 }
421
422 pub const fn to_raw_array(self) -> [u8; 16] {
425 self.id
426 }
427}
428
429impl<KS: IdKindSubset> Clone for DynamicId<KS> {
430 fn clone(&self) -> Self {
431 *self
432 }
433}
434
435impl<KS: IdKindSubset> Copy for DynamicId<KS> {}
436
437impl<KS: IdKindSubset> Debug for DynamicId<KS> {
438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 write!(f, "{}.{}", self.kind.str_prefix(), hexhex::hex(&self.id))
440 }
441}
442
443impl<KS: IdKindSubset> Display for DynamicId<KS> {
444 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445 write!(f, "{}.{}", self.kind.str_prefix(), hexhex::hex(&self.id))
446 }
447}
448
449impl<KS: IdKindSubset> PartialEq for DynamicId<KS> {
450 fn eq(&self, other: &Self) -> bool {
451 self.kind == other.kind && self.id == other.id
452 }
453}
454
455impl<KS: IdKindSubset> Eq for DynamicId<KS> {}
456
457impl<KS: IdKindSubset> Hash for DynamicId<KS> {
458 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
459 self.id.hash(state);
460 self.kind.hash(state);
461 }
462}
463
464impl<KS: IdKindSubset> PartialOrd<DynamicId<KS>> for DynamicId<KS> {
465 fn partial_cmp(&self, other: &DynamicId<KS>) -> Option<std::cmp::Ordering> {
466 Some(self.cmp(other))
467 }
468}
469
470impl<KS: IdKindSubset> Ord for DynamicId<KS> {
471 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
472 self.kind
473 .cmp(&other.kind)
474 .then_with(|| self.id.cmp(&other.id))
475 }
476}
477
478pub type EntityId = DynamicId<subset::Entity>;
480
481pub type AnyId = DynamicId<subset::Any>;
483
484impl<K: IdKind> FromStr for Id128<K> {
485 type Err = anyhow::Error;
486
487 fn from_str(s: &str) -> Result<Self, Self::Err> {
488 let prefix = K::kind().str_prefix();
489 let Some(s) = s.strip_prefix(prefix) else {
490 return Err(anyhow!("unrecognized prefix, expected `{prefix}`"));
491 };
492 let s = s.strip_prefix('.').context("missing `.`")?;
493
494 let hex = hexhex::decode(s).context("invalid format")?;
495 let array: [u8; 16] = hex.try_into().map_err(|_| anyhow!("invalid length"))?;
496
497 let min = 32768_u128.to_be_bytes();
498
499 if array != [0; 16] && array < min {
500 return Err(anyhow!("invalid value, too small"));
501 }
502
503 Ok(Id128(array, PhantomData))
504 }
505}
506
507impl<S: IdKindSubset> FromStr for DynamicId<S> {
508 type Err = anyhow::Error;
509
510 fn from_str(s: &str) -> Result<Self, Self::Err> {
511 let mut segments = s.splitn(2, ".");
512 let prefix = segments.next().context("no prefix")?;
513 let s = segments.next().context("no hex code")?;
514
515 if segments.next().is_some() {
516 return Err(anyhow!("too many dots"));
517 }
518
519 let kind = Kind::entries()
520 .iter()
521 .copied()
522 .find(|kind| kind.str_prefix() == prefix)
523 .context("unrecognized prefix")?;
524
525 if !S::contains(kind) {
526 return Err(anyhow!("invalid subset"));
527 }
528
529 let hex = hexhex::decode(s).context("invalid format")?;
530 let array: [u8; 16] = hex.try_into().map_err(|_| anyhow!("invalid length"))?;
531
532 let min = 32768_u128.to_be_bytes();
533
534 if array != [0; 16] && array < min {
535 return Err(anyhow!("invalid value, too small"));
536 }
537
538 Ok(DynamicId {
539 id: array,
540 kind,
541 _subset: PhantomData,
542 })
543 }
544}
545
546impl<'de, K: IdKind> Deserialize<'de> for Id128<K> {
547 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
548 where
549 D: serde::Deserializer<'de>,
550 {
551 deserializer.deserialize_str(FromStrVisitor::new(K::kind().name()))
552 }
553}
554
555impl<K: IdKind> Serialize for Id128<K> {
556 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
557 where
558 S: serde::Serializer,
559 {
560 serializer.serialize_str(&format!(
561 "{}.{}",
562 K::kind().str_prefix(),
563 hexhex::hex(&self.0)
564 ))
565 }
566}
567
568impl<KS: IdKindSubset> Serialize for DynamicId<KS> {
569 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
570 where
571 S: serde::Serializer,
572 {
573 serializer.serialize_str(&format!(
574 "{}.{}",
575 self.kind.str_prefix(),
576 hexhex::hex(&self.id)
577 ))
578 }
579}
580
581impl<'de, KS: IdKindSubset> Deserialize<'de> for DynamicId<KS> {
582 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
583 where
584 D: serde::Deserializer<'de>,
585 {
586 deserializer.deserialize_str(FromStrVisitor::new(KS::name()))
587 }
588}
589
590impl<K: IdKind> Id128DynamicArrayConv for Id128<K> {
591 fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self> {
592 let kind_byte: u8 = K::kind().into();
593 if array[0] == kind_byte {
594 Self::from_raw_bytes(&array[1..])
595 } else {
596 None
597 }
598 }
599
600 fn to_array_dynamic(&self) -> [u8; 17] {
601 let mut output = [0u8; 17];
602 output[0] = K::kind().into();
603 output[1..].clone_from_slice(&self.to_raw_array());
604 output
605 }
606}
607
608impl<KS: IdKindSubset> Id128DynamicArrayConv for DynamicId<KS> {
609 fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self> {
610 let kind = Kind::try_from(array[0]).ok()?;
611 if !KS::contains(kind) {
612 return None;
613 }
614
615 let id = array[1..].try_into().ok()?;
616
617 Some(Self {
618 kind,
619 id,
620 _subset: PhantomData,
621 })
622 }
623
624 fn to_array_dynamic(&self) -> [u8; 17] {
625 let mut output = [0u8; 17];
626 output[0] = self.kind.into();
627 output[1..].clone_from_slice(&self.id);
628 output
629 }
630}
631
632impl<K: IdKind, KS: IdKindSubset> TryFrom<Id128<K>> for DynamicId<KS> {
633 type Error = ();
634
635 fn try_from(value: Id128<K>) -> Result<Self, Self::Error> {
636 if KS::contains(K::kind()) {
637 Ok(Self {
638 kind: K::kind(),
639 id: value.0,
640 _subset: PhantomData,
641 })
642 } else {
643 Err(())
644 }
645 }
646}
647
648impl<K: IdKind, KS: IdKindSubset> TryFrom<DynamicId<KS>> for Id128<K> {
649 type Error = ();
650
651 fn try_from(value: DynamicId<KS>) -> Result<Self, Self::Error> {
652 if value.kind != K::kind() {
653 return Err(());
654 }
655
656 Ok(Self(value.id, PhantomData))
657 }
658}
659
660#[test]
661fn from_hex_literal() {
662 let _ = PersonaId::from(hexhex::hex_literal!("1234abcd1234abcd1234abcd1234abcd"));
663}
664
665#[test]
666fn from_str() {
667 PersonaId::from_str("p.1234abcd1234abcd1234abcd1234abcd").unwrap();
668 ServiceId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
669 PersonaId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap_err();
670 DomainId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap();
671
672 AnyId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
673 AnyId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap();
674 EntityId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
675 EntityId::from_str("g.1234abcd1234abcd1234abcd1234abcd").unwrap();
676 EntityId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap_err();
677}
678
679#[test]
680fn serde() {
681 let before = PersonaId::from_str("p.1234abcd1234abcd1234abcd1234abcd").unwrap();
682 let json = serde_json::to_string(&before).unwrap();
683
684 assert_eq!("\"p.1234abcd1234abcd1234abcd1234abcd\"", json);
685
686 let after: PersonaId = serde_json::from_str(&json).unwrap();
687
688 assert_eq!(before, after);
689}