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::rng().random();
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 Property = 5,
168 Attribute = 6,
170 Directory = 7,
172 }
173
174 impl Kind {
175 #[inline]
176 pub(super) const fn str_prefix(&self) -> &'static str {
177 match self {
178 Self::Persona => "p",
179 Self::Group => "g",
180 Self::Service => "s",
181 Self::Domain => "d",
182 Self::Property => "prp",
183 Self::Attribute => "atr",
184 Self::Directory => "dir",
185 Self::Policy => "pol",
186 }
187 }
188
189 pub(super) const fn name(&self) -> &'static str {
190 match self {
191 Kind::Persona => "persona ID",
192 Kind::Group => "group ID",
193 Kind::Service => "service ID",
194 Kind::Domain => "domain ID",
195 Kind::Policy => "policy ID",
196 Kind::Property => "property ID",
197 Kind::Attribute => "attribute ID",
198 Kind::Directory => "directory ID",
199 }
200 }
201
202 pub(super) const fn entries() -> &'static [Self] {
203 &[
204 Self::Persona,
205 Self::Group,
206 Self::Service,
207 Self::Domain,
208 Self::Policy,
209 Self::Property,
210 Self::Attribute,
211 Self::Directory,
212 ]
213 }
214 }
215
216 pub trait IdKind {
218 fn kind() -> Kind;
220 }
221
222 pub struct Persona;
224
225 pub struct Group;
227
228 pub struct Service;
230
231 pub struct Domain;
233
234 pub struct Policy;
236
237 pub struct Property;
239
240 pub struct Attrbute;
242
243 pub struct Directory;
245
246 impl IdKind for Persona {
247 fn kind() -> Kind {
248 Kind::Persona
249 }
250 }
251
252 impl IdKind for Group {
253 fn kind() -> Kind {
254 Kind::Group
255 }
256 }
257
258 impl IdKind for Service {
259 fn kind() -> Kind {
260 Kind::Service
261 }
262 }
263
264 impl IdKind for Domain {
265 fn kind() -> Kind {
266 Kind::Domain
267 }
268 }
269
270 impl IdKind for Policy {
271 fn kind() -> Kind {
272 Kind::Policy
273 }
274 }
275
276 impl IdKind for Property {
277 fn kind() -> Kind {
278 Kind::Property
279 }
280 }
281
282 impl IdKind for Attrbute {
283 fn kind() -> Kind {
284 Kind::Attribute
285 }
286 }
287
288 impl IdKind for Directory {
289 fn kind() -> Kind {
290 Kind::Directory
291 }
292 }
293}
294
295pub mod subset {
297 use super::kind::{Group, IdKind, Kind, Persona, Service};
298
299 pub trait IdKindSubset {
301 fn contains(kind: Kind) -> bool;
303
304 fn name() -> &'static str;
306 }
307
308 pub trait IdKindSupersetOf<K> {}
310
311 pub struct Entity;
313
314 pub struct Any;
316
317 impl IdKindSubset for Entity {
318 fn contains(kind: Kind) -> bool {
319 matches!(kind, Kind::Persona | Kind::Group | Kind::Service)
320 }
321
322 fn name() -> &'static str {
323 "Entity ID"
324 }
325 }
326
327 impl IdKindSupersetOf<Persona> for Entity {}
328 impl IdKindSupersetOf<Group> for Entity {}
329 impl IdKindSupersetOf<Service> for Entity {}
330
331 impl IdKindSubset for Any {
332 fn contains(_kind: Kind) -> bool {
333 true
334 }
335
336 fn name() -> &'static str {
337 "Any ID"
338 }
339 }
340
341 impl<K: IdKind> IdKindSupersetOf<K> for Any {}
342 impl IdKindSupersetOf<Entity> for Any {}
343}
344
345pub type PersonaId = Id128<kind::Persona>;
347
348pub type GroupId = Id128<kind::Group>;
350
351pub type ServiceId = Id128<kind::Service>;
353
354pub type PropId = Id128<kind::Property>;
356
357pub type AttrId = Id128<kind::Attrbute>;
359
360pub type PolicyId = Id128<kind::Policy>;
362
363pub type DomainId = Id128<kind::Domain>;
365
366pub type DirectoryId = Id128<kind::Directory>;
368
369pub struct DynamicId<KS: IdKindSubset> {
371 pub(crate) id: [u8; 16],
372 kind: kind::Kind,
373 _subset: PhantomData<KS>,
374}
375
376impl<KS: IdKindSubset> DynamicId<KS> {
377 pub fn new(kind: Kind, id: [u8; 16]) -> Self {
381 if !KS::contains(kind) {
382 panic!("Not in subset");
383 }
384 Self {
385 kind,
386 id,
387 _subset: PhantomData,
388 }
389 }
390
391 pub fn kind(&self) -> Kind {
393 self.kind
394 }
395
396 pub fn upcast<KS2: IdKindSubset + IdKindSupersetOf<KS>>(&self) -> DynamicId<KS2> {
398 DynamicId {
399 id: self.id,
400 kind: self.kind,
401 _subset: PhantomData,
402 }
403 }
404
405 pub const fn to_raw_array(self) -> [u8; 16] {
408 self.id
409 }
410}
411
412impl<KS: IdKindSubset> Clone for DynamicId<KS> {
413 fn clone(&self) -> Self {
414 *self
415 }
416}
417
418impl<KS: IdKindSubset> Copy for DynamicId<KS> {}
419
420impl<KS: IdKindSubset> Debug for DynamicId<KS> {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 write!(f, "{}.{}", self.kind.str_prefix(), hexhex::hex(&self.id))
423 }
424}
425
426impl<KS: IdKindSubset> Display for DynamicId<KS> {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 write!(f, "{}.{}", self.kind.str_prefix(), hexhex::hex(&self.id))
429 }
430}
431
432impl<KS: IdKindSubset> PartialEq for DynamicId<KS> {
433 fn eq(&self, other: &Self) -> bool {
434 self.kind == other.kind && self.id == other.id
435 }
436}
437
438impl<KS: IdKindSubset> Eq for DynamicId<KS> {}
439
440impl<KS: IdKindSubset> Hash for DynamicId<KS> {
441 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
442 self.id.hash(state);
443 self.kind.hash(state);
444 }
445}
446
447impl<KS: IdKindSubset> PartialOrd<DynamicId<KS>> for DynamicId<KS> {
448 fn partial_cmp(&self, other: &DynamicId<KS>) -> Option<std::cmp::Ordering> {
449 Some(self.cmp(other))
450 }
451}
452
453impl<KS: IdKindSubset> Ord for DynamicId<KS> {
454 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
455 self.kind
456 .cmp(&other.kind)
457 .then_with(|| self.id.cmp(&other.id))
458 }
459}
460
461pub type EntityId = DynamicId<subset::Entity>;
463
464pub type AnyId = DynamicId<subset::Any>;
466
467impl<K: IdKind> FromStr for Id128<K> {
468 type Err = anyhow::Error;
469
470 fn from_str(s: &str) -> Result<Self, Self::Err> {
471 let prefix = K::kind().str_prefix();
472 let Some(s) = s.strip_prefix(prefix) else {
473 return Err(anyhow!("unrecognized prefix, expected `{prefix}`"));
474 };
475 let s = s.strip_prefix('.').context("missing `.`")?;
476
477 let hex = hexhex::decode(s).context("invalid format")?;
478 let array: [u8; 16] = hex.try_into().map_err(|_| anyhow!("invalid length"))?;
479
480 let min = 32768_u128.to_be_bytes();
481
482 if array != [0; 16] && array < min {
483 return Err(anyhow!("invalid value, too small"));
484 }
485
486 Ok(Id128(array, PhantomData))
487 }
488}
489
490impl<S: IdKindSubset> FromStr for DynamicId<S> {
491 type Err = anyhow::Error;
492
493 fn from_str(s: &str) -> Result<Self, Self::Err> {
494 let mut segments = s.splitn(2, ".");
495 let prefix = segments.next().context("no prefix")?;
496 let s = segments.next().context("no hex code")?;
497
498 if segments.next().is_some() {
499 return Err(anyhow!("too many dots"));
500 }
501
502 let kind = Kind::entries()
503 .iter()
504 .copied()
505 .find(|kind| kind.str_prefix() == prefix)
506 .context("unrecognized prefix")?;
507
508 if !S::contains(kind) {
509 return Err(anyhow!("invalid subset"));
510 }
511
512 let hex = hexhex::decode(s).context("invalid format")?;
513 let array: [u8; 16] = hex.try_into().map_err(|_| anyhow!("invalid length"))?;
514
515 let min = 32768_u128.to_be_bytes();
516
517 if array != [0; 16] && array < min {
518 return Err(anyhow!("invalid value, too small"));
519 }
520
521 Ok(DynamicId {
522 id: array,
523 kind,
524 _subset: PhantomData,
525 })
526 }
527}
528
529impl<'de, K: IdKind> Deserialize<'de> for Id128<K> {
530 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
531 where
532 D: serde::Deserializer<'de>,
533 {
534 deserializer.deserialize_str(FromStrVisitor::new(K::kind().name()))
535 }
536}
537
538impl<K: IdKind> Serialize for Id128<K> {
539 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
540 where
541 S: serde::Serializer,
542 {
543 serializer.serialize_str(&format!(
544 "{}.{}",
545 K::kind().str_prefix(),
546 hexhex::hex(&self.0)
547 ))
548 }
549}
550
551impl<KS: IdKindSubset> Serialize for DynamicId<KS> {
552 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
553 where
554 S: serde::Serializer,
555 {
556 serializer.serialize_str(&format!(
557 "{}.{}",
558 self.kind.str_prefix(),
559 hexhex::hex(&self.id)
560 ))
561 }
562}
563
564impl<'de, KS: IdKindSubset> Deserialize<'de> for DynamicId<KS> {
565 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
566 where
567 D: serde::Deserializer<'de>,
568 {
569 deserializer.deserialize_str(FromStrVisitor::new(KS::name()))
570 }
571}
572
573impl<K: IdKind> Id128DynamicArrayConv for Id128<K> {
574 fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self> {
575 let kind_byte: u8 = K::kind().into();
576 if array[0] == kind_byte {
577 Self::from_raw_bytes(&array[1..])
578 } else {
579 None
580 }
581 }
582
583 fn to_array_dynamic(&self) -> [u8; 17] {
584 let mut output = [0u8; 17];
585 output[0] = K::kind().into();
586 output[1..].clone_from_slice(&self.to_raw_array());
587 output
588 }
589}
590
591impl<KS: IdKindSubset> Id128DynamicArrayConv for DynamicId<KS> {
592 fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self> {
593 let kind = Kind::try_from(array[0]).ok()?;
594 if !KS::contains(kind) {
595 return None;
596 }
597
598 let id = array[1..].try_into().ok()?;
599
600 Some(Self {
601 kind,
602 id,
603 _subset: PhantomData,
604 })
605 }
606
607 fn to_array_dynamic(&self) -> [u8; 17] {
608 let mut output = [0u8; 17];
609 output[0] = self.kind.into();
610 output[1..].clone_from_slice(&self.id);
611 output
612 }
613}
614
615impl<K: IdKind, KS: IdKindSubset> TryFrom<Id128<K>> for DynamicId<KS> {
616 type Error = ();
617
618 fn try_from(value: Id128<K>) -> Result<Self, Self::Error> {
619 if KS::contains(K::kind()) {
620 Ok(Self {
621 kind: K::kind(),
622 id: value.0,
623 _subset: PhantomData,
624 })
625 } else {
626 Err(())
627 }
628 }
629}
630
631impl<K: IdKind, KS: IdKindSubset> TryFrom<DynamicId<KS>> for Id128<K> {
632 type Error = ();
633
634 fn try_from(value: DynamicId<KS>) -> Result<Self, Self::Error> {
635 if value.kind != K::kind() {
636 return Err(());
637 }
638
639 Ok(Self(value.id, PhantomData))
640 }
641}
642
643#[test]
644fn from_hex_literal() {
645 let _ = PersonaId::from(hexhex::hex_literal!("1234abcd1234abcd1234abcd1234abcd"));
646}
647
648#[test]
649fn from_str() {
650 PersonaId::from_str("p.1234abcd1234abcd1234abcd1234abcd").unwrap();
651 ServiceId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
652 PersonaId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap_err();
653 DomainId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap();
654
655 AnyId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
656 AnyId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap();
657 EntityId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
658 EntityId::from_str("g.1234abcd1234abcd1234abcd1234abcd").unwrap();
659 EntityId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap_err();
660}
661
662#[test]
663fn serde() {
664 let before = PersonaId::from_str("p.1234abcd1234abcd1234abcd1234abcd").unwrap();
665 let json = serde_json::to_string(&before).unwrap();
666
667 assert_eq!("\"p.1234abcd1234abcd1234abcd1234abcd\"", json);
668
669 let after: PersonaId = serde_json::from_str(&json).unwrap();
670
671 assert_eq!(before, after);
672}