qm_entity/ids/
infra.rs

1//! ID Implementations for multiple scenarios of infrastructure and ownership.
2//!
3//! The smallest unit has a min length of 3 characters. The biggest Resource ID can go up to 76 characters.
4//!
5//! |Prefix|                            Structure                                  |           Type            | min length | max length | real size  |
6//! |------|-----------------------------------------------------------------------|---------------------------|------------|------------|------------|
7//! |  V   | CustomerId                                                            | CustomerId                |     3      |     18     |     8      |
8//! |  U   | CustomerId + ID (24 Characters)                                       | CustomerResourceId        |     27     |     42     |     20     |
9//! |  T   | CustomerId + OrganizationId                                           | OrganizationId            |     5      |     35     |     16     |
10//! |  S   | CustomerId + OrganizationId + ID (24 Characters)                      | OrganizationResourceId    |     29     |     59     |     28     |
11//! |  R   | CustomerId + OrganizationId + InstitutionId                           | InstitutionId             |     7      |     52     |     24     |
12//! |  Q   | CustomerId + OrganizationId + InstitutionId + ID (24 Characters)      | InstitutionResourceId     |     31     |     76     |     36     |
13
14use std::{fmt::Write, str::FromStr};
15
16use async_graphql::OneofObject;
17use sqlx::{postgres::PgArgumentBuffer, Encode, Postgres};
18
19use super::ID;
20
21pub const CUSTOMER_ID_PREFIX: char = 'V';
22pub const CUSTOMER_RESOURCE_ID_PREFIX: char = 'U';
23pub const ORGANIZATION_ID_PREFIX: char = 'T';
24pub const ORGANIZATION_RESOURCE_ID_PREFIX: char = 'S';
25pub const INSTITUTION_ID_PREFIX: char = 'R';
26pub const INSTITUTION_RESOURCE_ID_PREFIX: char = 'Q';
27pub const ID_LENGTH: usize = 24;
28
29#[derive(
30    Debug,
31    Clone,
32    Copy,
33    PartialEq,
34    Eq,
35    PartialOrd,
36    Ord,
37    Hash,
38    Default,
39    serde::Serialize,
40    serde::Deserialize,
41)]
42#[repr(C)]
43#[serde(transparent)]
44pub struct InfraId(i64);
45impl AsRef<i64> for InfraId {
46    fn as_ref(&self) -> &i64 {
47        &self.0
48    }
49}
50impl std::ops::Deref for InfraId {
51    type Target = i64;
52    fn deref(&self) -> &Self::Target {
53        &self.0
54    }
55}
56impl From<i64> for InfraId {
57    fn from(value: i64) -> Self {
58        Self(value)
59    }
60}
61impl From<InfraId> for i64 {
62    fn from(val: InfraId) -> Self {
63        val.0
64    }
65}
66
67impl Encode<'_, Postgres> for InfraId {
68    fn encode_by_ref(
69        &self,
70        buf: &mut PgArgumentBuffer,
71    ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + std::marker::Send + Sync + 'static>>
72    {
73        buf.extend(&self.0.to_be_bytes());
74
75        Ok(sqlx::encode::IsNull::No)
76    }
77}
78
79trait Prefixed {
80    const PREFIX: char;
81}
82
83macro_rules! impl_id {
84    ($t:ty, $p:expr) => {
85        impl $t {
86            pub fn parse(value: &str) -> anyhow::Result<Self> {
87                Self::from_str(value)
88            }
89        }
90
91        impl Prefixed for $t {
92            const PREFIX: char = $p;
93        }
94    };
95}
96
97macro_rules! impl_display_for_id {
98    ($t:ty) => {
99        impl std::fmt::Display for $t {
100            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101                f.write_char(Self::PREFIX)?;
102                f.write_str(&self.to_hex())
103            }
104        }
105    };
106}
107
108macro_rules! impl_display_for_resource_id {
109    ($t:ty) => {
110        impl std::fmt::Display for $t {
111            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112                f.write_char(Self::PREFIX)?;
113                f.write_str(&self.parent().to_hex())?;
114                f.write_str(&self.id.to_hex())
115            }
116        }
117    };
118}
119
120macro_rules! impl_customer_id_from_ty {
121    ($n:ty) => {
122        impl From<$n> for CustomerId {
123            fn from(value: $n) -> Self {
124                CustomerId { cid: value as i64 }
125            }
126        }
127    };
128}
129
130macro_rules! impl_organization_id_from_ty_tuple {
131    ($n:ty) => {
132        impl From<($n, $n)> for OrganizationId {
133            fn from(value: ($n, $n)) -> Self {
134                OrganizationId {
135                    cid: value.0 as i64,
136                    oid: value.1 as i64,
137                }
138            }
139        }
140    };
141}
142
143macro_rules! impl_institution_id_from_ty_tuple {
144    ($n:ty) => {
145        impl From<($n, $n, $n)> for InstitutionId {
146            fn from(value: ($n, $n, $n)) -> Self {
147                InstitutionId {
148                    cid: value.0 as i64,
149                    oid: value.1 as i64,
150                    iid: value.2 as i64,
151                }
152            }
153        }
154        impl From<(($n, $n), $n)> for InstitutionId {
155            fn from(value: (($n, $n), $n)) -> Self {
156                InstitutionId {
157                    cid: value.0 .0 as i64,
158                    oid: value.0 .1 as i64,
159                    iid: value.1 as i64,
160                }
161            }
162        }
163    };
164}
165
166macro_rules! impl_customer_resource_id_from_ty_tuple {
167    ($n:ty) => {
168        impl From<($n, ID)> for CustomerResourceId {
169            fn from(value: ($n, ID)) -> Self {
170                CustomerResourceId {
171                    cid: value.0 as i64,
172                    id: value.1,
173                }
174            }
175        }
176    };
177}
178
179macro_rules! impl_organization_resource_id_from_ty_tuple {
180    ($n:ty) => {
181        impl From<($n, $n, ID)> for OrganizationResourceId {
182            fn from(value: ($n, $n, ID)) -> Self {
183                OrganizationResourceId {
184                    cid: value.0 as i64,
185                    oid: value.1 as i64,
186                    id: value.2,
187                }
188            }
189        }
190    };
191}
192
193macro_rules! impl_institution_resource_id_from_ty_tuple {
194    ($n:ty) => {
195        impl From<($n, $n, $n, ID)> for InstitutionResourceId {
196            fn from(value: ($n, $n, $n, ID)) -> Self {
197                InstitutionResourceId {
198                    cid: value.0 as i64,
199                    oid: value.1 as i64,
200                    iid: value.2 as i64,
201                    id: value.3,
202                }
203            }
204        }
205    };
206}
207
208/// Customer Id
209///
210/// - Prefix: V
211/// - Min Length: 3
212/// - Max Length: 18
213/// - Real size: 8
214///
215/// # Examples
216///
217/// ```rust
218/// use qm_entity::ids::CustomerId;
219///
220/// let id1 = CustomerId::parse("V01").expect("Customer Id");
221/// let id2 = CustomerId::parse("V120").expect("Customer Id");
222/// let id3 = CustomerId::parse("V2500").expect("Customer Id");
223///
224/// assert_eq!(1, id1.unzip());
225/// assert_eq!(0x20, id2.unzip());
226/// assert_eq!(0x500, id3.unzip());
227/// ```
228#[derive(
229    Debug,
230    Default,
231    Clone,
232    Copy,
233    PartialEq,
234    Eq,
235    PartialOrd,
236    Ord,
237    Hash,
238    serde::Serialize,
239    serde::Deserialize,
240    async_graphql::Description,
241)]
242pub struct CustomerId {
243    cid: i64,
244}
245
246impl CustomerId {
247    fn to_hex(self) -> String {
248        StringWriter::from(self.cid).into_inner()
249    }
250
251    pub fn unzip(&self) -> i64 {
252        self.cid
253    }
254}
255
256impl FromStr for CustomerId {
257    type Err = anyhow::Error;
258
259    fn from_str(s: &str) -> Result<Self, Self::Err> {
260        if !s.starts_with(Self::PREFIX) {
261            anyhow::bail!("Invalid CustomerId")
262        }
263        StringParser::<1>::new(&s[1..])
264            .next()
265            .map(From::from)
266            .ok_or(anyhow::anyhow!("unable to parse '{s}' into CustomerId"))
267    }
268}
269
270impl From<CustomerId> for i64 {
271    fn from(value: CustomerId) -> Self {
272        value.unzip()
273    }
274}
275
276impl<'a> From<&'a CustomerId> for InfraId {
277    fn from(value: &'a CustomerId) -> Self {
278        InfraId(value.cid)
279    }
280}
281
282impl From<CustomerId> for InfraId {
283    fn from(value: CustomerId) -> Self {
284        InfraId(value.cid)
285    }
286}
287
288impl_id!(CustomerId, CUSTOMER_ID_PREFIX);
289impl_display_for_id!(CustomerId);
290impl_customer_id_from_ty!(i64);
291impl_customer_id_from_ty!(u64);
292impl_customer_id_from_ty!(i32);
293impl_customer_id_from_ty!(u32);
294impl_customer_id_from_ty!(u16);
295impl_customer_id_from_ty!(i16);
296impl_customer_id_from_ty!(u8);
297impl_customer_id_from_ty!(i8);
298
299/// Customer Resource Id
300///
301/// - Prefix: U
302/// - Min Length: 27
303/// - Max Length: 42
304/// - Real size: 20
305///
306/// # Examples
307///
308/// ```rust
309/// use std::str::FromStr;
310/// use qm_entity::ids::{CustomerResourceId, ID};
311///
312/// let id1 = CustomerResourceId::parse("U016603f7b32b1753f84a719e01").expect("Customer Resource Id");
313/// let id2 = CustomerResourceId::parse("U1206603f7b32b1753f84a719e02").expect("Customer Resource Id");
314/// let id3 = CustomerResourceId::parse("U25006603f7b32b1753f84a719e03").expect("Customer Resource Id");
315///
316/// assert_eq!((1, ID::from_str("6603f7b32b1753f84a719e01").expect("Object ID")), id1.unzip());
317/// assert_eq!((0x20, ID::from_str("6603f7b32b1753f84a719e02").expect("Object ID")), id2.unzip());
318/// assert_eq!((0x500, ID::from_str("6603f7b32b1753f84a719e03").expect("Object ID")), id3.unzip());
319/// ```
320#[derive(
321    Debug,
322    Default,
323    Clone,
324    Copy,
325    PartialEq,
326    Eq,
327    PartialOrd,
328    Ord,
329    Hash,
330    serde::Serialize,
331    serde::Deserialize,
332    async_graphql::Description,
333)]
334pub struct CustomerResourceId {
335    cid: i64,
336    id: ID,
337}
338
339impl CustomerResourceId {
340    pub fn root(&self) -> CustomerId {
341        CustomerId::from(self.cid)
342    }
343
344    pub fn parent(&self) -> CustomerId {
345        CustomerId::from(self.cid)
346    }
347
348    pub fn unzip(&self) -> (i64, ID) {
349        (self.cid, self.id)
350    }
351}
352
353impl FromStr for CustomerResourceId {
354    type Err = anyhow::Error;
355
356    fn from_str(s: &str) -> Result<Self, Self::Err> {
357        if !s.starts_with(Self::PREFIX) {
358            anyhow::bail!("Invalid CustomerResourceId")
359        }
360        let mut parser = StringParser::<1>::new(&s[1..]).with_object_id();
361        let CustomerId { cid }: CustomerId = parser.next().map(From::from).ok_or(
362            anyhow::anyhow!("unable to parse '{s}' into CustomerResourceId"),
363        )?;
364        let start = parser.end();
365        let end = start + ID_LENGTH;
366        if end > s.len() {
367            anyhow::bail!("Invalid length for CustomerResourceId");
368        }
369        let id = ID::from_str(&s[start..end])?;
370        Ok(Self { cid, id })
371    }
372}
373
374impl_id!(CustomerResourceId, CUSTOMER_RESOURCE_ID_PREFIX);
375impl_display_for_resource_id!(CustomerResourceId);
376impl_customer_resource_id_from_ty_tuple!(i64);
377impl_customer_resource_id_from_ty_tuple!(u64);
378impl_customer_resource_id_from_ty_tuple!(i32);
379impl_customer_resource_id_from_ty_tuple!(u32);
380impl_customer_resource_id_from_ty_tuple!(u16);
381impl_customer_resource_id_from_ty_tuple!(i16);
382impl_customer_resource_id_from_ty_tuple!(u8);
383impl_customer_resource_id_from_ty_tuple!(i8);
384
385/// Organization Id
386///
387/// - Prefix: T
388/// - Min Length: 5
389/// - Max Length: 35
390/// - Real size: 16
391///
392/// # Examples
393///
394/// ```rust
395/// use std::str::FromStr;
396/// use qm_entity::ids::OrganizationId;
397///
398/// let id1 = OrganizationId::parse("T0102").expect("Organization Id");
399/// let id2 = OrganizationId::parse("T120121").expect("Organization Id");
400/// let id3 = OrganizationId::parse("T25002501").expect("Organization Id");
401///
402/// assert_eq!((1, 2), id1.unzip());
403/// assert_eq!((0x20, 0x21), id2.unzip());
404/// assert_eq!((0x500, 0x501), id3.unzip());
405/// ```
406#[derive(
407    Debug,
408    Default,
409    Clone,
410    Copy,
411    PartialEq,
412    Eq,
413    PartialOrd,
414    Ord,
415    Hash,
416    serde::Serialize,
417    serde::Deserialize,
418    async_graphql::Description,
419)]
420pub struct OrganizationId {
421    cid: i64,
422    oid: i64,
423}
424
425impl OrganizationId {
426    pub fn id(&self) -> i64 {
427        self.oid
428    }
429
430    pub fn root(&self) -> CustomerId {
431        CustomerId::from(self.cid)
432    }
433
434    pub fn parent(&self) -> CustomerId {
435        CustomerId::from(self.cid)
436    }
437
438    fn to_hex(self) -> String {
439        StringWriter::from((self.cid, self.oid)).into_inner()
440    }
441
442    pub fn unzip(&self) -> (i64, i64) {
443        (self.cid, self.oid)
444    }
445
446    pub fn resource(&self, id: ID) -> OrganizationResourceId {
447        OrganizationResourceId::from((self.cid, self.oid, id))
448    }
449}
450
451impl FromStr for OrganizationId {
452    type Err = anyhow::Error;
453
454    fn from_str(s: &str) -> Result<Self, Self::Err> {
455        if !s.starts_with(Self::PREFIX) {
456            anyhow::bail!("Invalid OrganizationId")
457        }
458        let mut parser = StringParser::<2>::new(&s[1..]);
459        parser
460            .next()
461            .zip(parser.next())
462            .map(From::from)
463            .ok_or(anyhow::anyhow!("unable to get OrganizationId from '{s}'"))
464    }
465}
466
467impl From<OrganizationId> for i64 {
468    fn from(value: OrganizationId) -> Self {
469        value.id()
470    }
471}
472
473impl<'a> From<&'a OrganizationId> for InfraId {
474    fn from(value: &'a OrganizationId) -> Self {
475        InfraId(value.oid)
476    }
477}
478
479impl From<OrganizationId> for InfraId {
480    fn from(value: OrganizationId) -> Self {
481        InfraId(value.oid)
482    }
483}
484
485impl_id!(OrganizationId, ORGANIZATION_ID_PREFIX);
486impl_display_for_id!(OrganizationId);
487impl_organization_id_from_ty_tuple!(i64);
488impl_organization_id_from_ty_tuple!(u64);
489impl_organization_id_from_ty_tuple!(i32);
490impl_organization_id_from_ty_tuple!(u32);
491impl_organization_id_from_ty_tuple!(u16);
492impl_organization_id_from_ty_tuple!(i16);
493impl_organization_id_from_ty_tuple!(u8);
494impl_organization_id_from_ty_tuple!(i8);
495
496/// Organization Resource Id
497///
498/// - Prefix: S
499/// - Min Length: 29
500/// - Max Length: 59
501/// - Real size: 28
502///
503/// # Examples
504///
505/// ```rust
506/// use std::str::FromStr;
507/// use qm_entity::ids::{OrganizationResourceId, ID};
508///
509/// let id1 = OrganizationResourceId::parse("S01026603f7b32b1753f84a719e01").expect("Organization Resource Id");
510/// let id2 = OrganizationResourceId::parse("S1201216603f7b32b1753f84a719e02").expect("Organization Resource Id");
511/// let id3 = OrganizationResourceId::parse("S250025016603f7b32b1753f84a719e03").expect("Organization Resource Id");
512///
513/// assert_eq!((1, 2, ID::from_str("6603f7b32b1753f84a719e01").expect("Object ID")), id1.unzip());
514/// assert_eq!((0x20, 0x21, ID::from_str("6603f7b32b1753f84a719e02").expect("Object ID")), id2.unzip());
515/// assert_eq!((0x500, 0x501, ID::from_str("6603f7b32b1753f84a719e03").expect("Object ID")), id3.unzip());
516/// ```
517#[derive(
518    Debug,
519    Default,
520    Clone,
521    Copy,
522    PartialEq,
523    Eq,
524    PartialOrd,
525    Ord,
526    Hash,
527    serde::Serialize,
528    serde::Deserialize,
529    async_graphql::Description,
530)]
531pub struct OrganizationResourceId {
532    cid: i64,
533    oid: i64,
534    id: ID,
535}
536
537impl OrganizationResourceId {
538    pub fn root(&self) -> CustomerId {
539        CustomerId::from(self.cid)
540    }
541
542    pub fn parent(&self) -> OrganizationId {
543        OrganizationId::from((self.cid, self.oid))
544    }
545
546    pub fn id(&self) -> &ID {
547        &self.id
548    }
549
550    pub fn unzip(&self) -> (i64, i64, ID) {
551        (self.cid, self.oid, self.id)
552    }
553}
554
555impl FromStr for OrganizationResourceId {
556    type Err = anyhow::Error;
557
558    fn from_str(s: &str) -> Result<Self, Self::Err> {
559        if !s.starts_with(Self::PREFIX) {
560            anyhow::bail!("Invalid OrganizationResourceId")
561        }
562        let mut parser = StringParser::<2>::new(&s[1..]).with_object_id();
563        let OrganizationId { cid, oid }: OrganizationId = parser
564            .next()
565            .zip(parser.next())
566            .map(From::from)
567            .ok_or(anyhow::anyhow!(
568                "unable to parse '{s}' into OrganizationResourceId"
569            ))?;
570        let start = parser.end();
571        let end = start + ID_LENGTH;
572        if end > s.len() {
573            anyhow::bail!("Invalid length for OrganizationResourceId");
574        }
575        let id = ID::from_str(&s[start..end])?;
576        Ok(Self { cid, oid, id })
577    }
578}
579
580impl_id!(OrganizationResourceId, ORGANIZATION_RESOURCE_ID_PREFIX);
581impl_display_for_resource_id!(OrganizationResourceId);
582impl_organization_resource_id_from_ty_tuple!(i64);
583impl_organization_resource_id_from_ty_tuple!(u64);
584impl_organization_resource_id_from_ty_tuple!(i32);
585impl_organization_resource_id_from_ty_tuple!(u32);
586impl_organization_resource_id_from_ty_tuple!(u16);
587impl_organization_resource_id_from_ty_tuple!(i16);
588impl_organization_resource_id_from_ty_tuple!(u8);
589impl_organization_resource_id_from_ty_tuple!(i8);
590
591/// Institution Id
592///
593/// - Prefix: R
594/// - Min Length: 7
595/// - Max Length: 52
596/// - Real size: 24
597///
598/// # Examples
599///
600/// ```rust
601/// use std::str::FromStr;
602/// use qm_entity::ids::InstitutionId;
603///
604/// let id1 = InstitutionId::parse("R010203").expect("Institution Id");
605/// let id2 = InstitutionId::parse("R120121122").expect("Institution Id");
606/// let id3 = InstitutionId::parse("R250025012502").expect("Institution Id");
607///
608/// assert_eq!((1, 2, 3), id1.unzip());
609/// assert_eq!((0x20, 0x21, 0x22), id2.unzip());
610/// assert_eq!((0x500, 0x501, 0x502), id3.unzip());
611/// ```
612#[derive(
613    Debug,
614    Default,
615    Clone,
616    Copy,
617    PartialEq,
618    Eq,
619    PartialOrd,
620    Ord,
621    Hash,
622    serde::Serialize,
623    serde::Deserialize,
624    async_graphql::Description,
625)]
626pub struct InstitutionId {
627    pub cid: i64,
628    pub oid: i64,
629    pub iid: i64,
630}
631
632impl InstitutionId {
633    pub fn id(&self) -> i64 {
634        self.iid
635    }
636
637    pub fn root(&self) -> CustomerId {
638        CustomerId::from(self.cid)
639    }
640
641    pub fn parent(&self) -> OrganizationId {
642        OrganizationId::from((self.cid, self.oid))
643    }
644
645    fn to_hex(self) -> String {
646        StringWriter::from((self.cid, self.oid, self.iid)).into_inner()
647    }
648
649    pub fn unzip(&self) -> (i64, i64, i64) {
650        (self.cid, self.oid, self.iid)
651    }
652    pub fn untuple(&self) -> (i64, (i64, i64)) {
653        (self.cid, (self.oid, self.iid))
654    }
655
656    pub fn resource(&self, id: ID) -> InstitutionResourceId {
657        InstitutionResourceId::from((self.cid, self.oid, self.iid, id))
658    }
659}
660
661impl FromStr for InstitutionId {
662    type Err = anyhow::Error;
663
664    fn from_str(s: &str) -> Result<Self, Self::Err> {
665        if !s.starts_with(Self::PREFIX) {
666            anyhow::bail!("Invalid InstitutionId")
667        }
668        let mut parser = StringParser::<3>::new(&s[1..]);
669        parser
670            .next()
671            .zip(parser.next())
672            .zip(parser.next())
673            .map(From::from)
674            .ok_or(anyhow::anyhow!("unable to get InstitutionId from '{s}'"))
675    }
676}
677
678impl From<InstitutionId> for i64 {
679    fn from(value: InstitutionId) -> Self {
680        value.id()
681    }
682}
683
684impl<'a> From<&'a InstitutionId> for InfraId {
685    fn from(value: &'a InstitutionId) -> Self {
686        InfraId(value.iid)
687    }
688}
689
690impl From<InstitutionId> for InfraId {
691    fn from(value: InstitutionId) -> Self {
692        InfraId(value.iid)
693    }
694}
695
696impl_id!(InstitutionId, INSTITUTION_ID_PREFIX);
697impl_display_for_id!(InstitutionId);
698impl_institution_id_from_ty_tuple!(i64);
699impl_institution_id_from_ty_tuple!(u64);
700impl_institution_id_from_ty_tuple!(i32);
701impl_institution_id_from_ty_tuple!(u32);
702impl_institution_id_from_ty_tuple!(u16);
703impl_institution_id_from_ty_tuple!(i16);
704impl_institution_id_from_ty_tuple!(u8);
705impl_institution_id_from_ty_tuple!(i8);
706
707/// Institution Resource Id
708///
709/// - Prefix: Q
710/// - Min Length: 31
711/// - Max Length: 76
712/// - Real size: 36
713///
714/// # Examples
715///
716/// ```rust
717/// use std::str::FromStr;
718/// use qm_entity::ids::{InstitutionResourceId, ID};
719///
720/// let id1 = InstitutionResourceId::parse("Q0102036603f7b32b1753f84a719e01").expect("Institution Resource Id");
721/// let id2 = InstitutionResourceId::parse("Q1201211226603f7b32b1753f84a719e02").expect("Institution Resource Id");
722/// let id3 = InstitutionResourceId::parse("Q2500250125026603f7b32b1753f84a719e03").expect("Institution Resource Id");
723///
724/// assert_eq!((1, 2, 3, ID::from_str("6603f7b32b1753f84a719e01").expect("Object ID")), id1.unzip());
725/// assert_eq!((0x20, 0x21, 0x22, ID::from_str("6603f7b32b1753f84a719e02").expect("Object ID")), id2.unzip());
726/// assert_eq!((0x500, 0x501, 0x502, ID::from_str("6603f7b32b1753f84a719e03").expect("Object ID")), id3.unzip());
727/// ```
728#[derive(
729    Debug,
730    Default,
731    Clone,
732    Copy,
733    PartialEq,
734    Eq,
735    PartialOrd,
736    Ord,
737    Hash,
738    serde::Serialize,
739    serde::Deserialize,
740    async_graphql::Description,
741)]
742pub struct InstitutionResourceId {
743    cid: i64,
744    oid: i64,
745    iid: i64,
746    id: ID,
747}
748
749impl InstitutionResourceId {
750    pub fn root(&self) -> CustomerId {
751        CustomerId::from(self.cid)
752    }
753
754    pub fn parent(&self) -> InstitutionId {
755        InstitutionId::from((self.cid, self.oid, self.iid))
756    }
757
758    pub fn unzip(&self) -> (i64, i64, i64, ID) {
759        (self.cid, self.oid, self.iid, self.id)
760    }
761}
762
763impl FromStr for InstitutionResourceId {
764    type Err = anyhow::Error;
765
766    fn from_str(s: &str) -> Result<Self, Self::Err> {
767        if !s.starts_with(Self::PREFIX) {
768            anyhow::bail!("Invalid InstitutionResourceId")
769        }
770        let mut parser = StringParser::<3>::new(&s[1..]).with_object_id();
771        let InstitutionId { cid, oid, iid }: InstitutionId = parser
772            .next()
773            .zip(parser.next())
774            .zip(parser.next())
775            .map(From::from)
776            .ok_or(anyhow::anyhow!(
777                "unable to parse '{s}' into InstitutionResourceId"
778            ))?;
779        let start = parser.end();
780        let end = start + ID_LENGTH;
781        if end > s.len() {
782            anyhow::bail!("Invalid length for InstitutionResourceId");
783        }
784        let id = ID::from_str(&s[start..end])?;
785        Ok(Self { cid, oid, iid, id })
786    }
787}
788
789impl_id!(InstitutionResourceId, INSTITUTION_RESOURCE_ID_PREFIX);
790impl_display_for_resource_id!(InstitutionResourceId);
791impl_institution_resource_id_from_ty_tuple!(i64);
792impl_institution_resource_id_from_ty_tuple!(u64);
793impl_institution_resource_id_from_ty_tuple!(i32);
794impl_institution_resource_id_from_ty_tuple!(u32);
795impl_institution_resource_id_from_ty_tuple!(u16);
796impl_institution_resource_id_from_ty_tuple!(i16);
797impl_institution_resource_id_from_ty_tuple!(u8);
798impl_institution_resource_id_from_ty_tuple!(i8);
799
800#[derive(Debug, Clone, Copy, OneofObject, Hash, PartialEq, Eq, PartialOrd, Ord)]
801#[cfg_attr(
802    feature = "serde-str",
803    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
804)]
805pub enum InfraContext {
806    Customer(CustomerId),
807    Organization(OrganizationId),
808    Institution(InstitutionId),
809}
810
811impl InfraContext {
812    pub fn customer_id(&self) -> InfraId {
813        match self {
814            InfraContext::Customer(b) => b.cid.into(),
815            InfraContext::Organization(b) => b.cid.into(),
816            InfraContext::Institution(b) => b.cid.into(),
817        }
818    }
819
820    pub fn organization_id(&self) -> Option<InfraId> {
821        match self {
822            InfraContext::Customer(_) => None,
823            InfraContext::Organization(b) => Some(b.oid.into()),
824            InfraContext::Institution(b) => Some(b.oid.into()),
825        }
826    }
827
828    pub fn institution_id(&self) -> Option<InfraId> {
829        match self {
830            InfraContext::Customer(_) => None,
831            InfraContext::Organization(_) => None,
832            InfraContext::Institution(b) => Some(b.iid.into()),
833        }
834    }
835
836    pub fn is_customer(&self) -> bool {
837        match self {
838            InfraContext::Customer(_) => true,
839            InfraContext::Organization(_) => false,
840            InfraContext::Institution(_) => false,
841        }
842    }
843
844    pub fn is_organization(&self) -> bool {
845        match self {
846            InfraContext::Customer(_) => false,
847            InfraContext::Organization(_) => true,
848            InfraContext::Institution(_) => false,
849        }
850    }
851
852    pub fn is_institution(&self) -> bool {
853        match self {
854            InfraContext::Customer(_) => false,
855            InfraContext::Organization(_) => false,
856            InfraContext::Institution(_) => true,
857        }
858    }
859
860    pub fn has_customer(&self, a: &CustomerId) -> bool {
861        match self {
862            InfraContext::Customer(b) => a.cid == b.cid,
863            InfraContext::Organization(b) => a.cid == b.cid,
864            InfraContext::Institution(b) => a.cid == b.cid,
865        }
866    }
867    pub fn has_organization(&self, a: &OrganizationId) -> bool {
868        match self {
869            InfraContext::Customer(_) => false,
870            InfraContext::Organization(b) => a == b,
871            InfraContext::Institution(b) => a.cid == b.cid && a.oid == b.oid,
872        }
873    }
874    pub fn has_institution(&self, a: &InstitutionId) -> bool {
875        match self {
876            InfraContext::Customer(_) => false,
877            InfraContext::Organization(_) => false,
878            InfraContext::Institution(b) => a == b,
879        }
880    }
881
882    pub fn ns(&self) -> &'static str {
883        match self {
884            InfraContext::Customer(_) => "customer",
885            InfraContext::Organization(_) => "organization",
886            InfraContext::Institution(_) => "institution",
887        }
888    }
889
890    // Call from user context
891    pub fn combine(self, query_context: Self) -> Self {
892        match &self {
893            InfraContext::Customer(v) => {
894                if query_context.has_customer(v) {
895                    query_context
896                } else {
897                    self
898                }
899            }
900            InfraContext::Organization(v) => {
901                if query_context.has_organization(v) {
902                    query_context
903                } else {
904                    self
905                }
906            }
907            InfraContext::Institution(v) => {
908                if query_context.has_institution(v) {
909                    query_context
910                } else {
911                    self
912                }
913            }
914        }
915    }
916
917    pub fn id(&self) -> i64 {
918        match self {
919            InfraContext::Customer(customer_id) => customer_id.unzip(),
920            InfraContext::Organization(organization_id) => organization_id.id(),
921            InfraContext::Institution(institution_id) => institution_id.id(),
922        }
923    }
924}
925
926impl std::fmt::Display for InfraContext {
927    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
928        match self {
929            Self::Customer(v) => v.fmt(f),
930            Self::Organization(v) => v.fmt(f),
931            Self::Institution(v) => v.fmt(f),
932        }
933    }
934}
935
936impl InfraContext {
937    pub fn parse(s: &str) -> anyhow::Result<Self> {
938        Self::from_str(s)
939    }
940}
941
942impl From<CustomerId> for InfraContext {
943    fn from(value: CustomerId) -> Self {
944        InfraContext::Customer(value)
945    }
946}
947impl From<OrganizationId> for InfraContext {
948    fn from(value: OrganizationId) -> Self {
949        InfraContext::Organization(value)
950    }
951}
952impl From<InstitutionId> for InfraContext {
953    fn from(value: InstitutionId) -> Self {
954        InfraContext::Institution(value)
955    }
956}
957
958impl<'a> From<&'a CustomerId> for InfraContext {
959    fn from(value: &'a CustomerId) -> Self {
960        InfraContext::Customer(*value)
961    }
962}
963impl<'a> From<&'a OrganizationId> for InfraContext {
964    fn from(value: &'a OrganizationId) -> Self {
965        InfraContext::Organization(*value)
966    }
967}
968impl<'a> From<&'a InstitutionId> for InfraContext {
969    fn from(value: &'a InstitutionId) -> Self {
970        InfraContext::Institution(*value)
971    }
972}
973
974impl std::str::FromStr for InfraContext {
975    type Err = anyhow::Error;
976
977    fn from_str(s: &str) -> Result<Self, Self::Err> {
978        if let Some(first_char) = s.chars().next() {
979            return match first_char {
980                CustomerId::PREFIX => CustomerId::parse(s).map(InfraContext::Customer),
981                OrganizationId::PREFIX => OrganizationId::parse(s).map(InfraContext::Organization),
982                InstitutionId::PREFIX => InstitutionId::parse(s).map(InfraContext::Institution),
983                _ => anyhow::bail!("invalid prefix '{first_char}'"),
984            };
985        }
986        anyhow::bail!("unable to parse InfraContext from '{s}'");
987    }
988}
989
990const HEX_CHARS: [char; 16] = [
991    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
992];
993
994struct StringWriter<const N: usize>(String);
995
996struct StringWriterResult<const N: usize>(anyhow::Result<StringWriter<N>>);
997
998impl<const N: usize> StringWriter<N> {
999    fn into_inner(self) -> String {
1000        self.0
1001    }
1002    fn string_length() -> usize {
1003        N * 17
1004    }
1005}
1006
1007impl<const N: usize> StringWriterResult<N> {
1008    fn into_inner(self) -> anyhow::Result<StringWriter<N>> {
1009        self.0
1010    }
1011}
1012
1013impl<const N: usize> FromIterator<i64> for StringWriterResult<N> {
1014    fn from_iter<T: IntoIterator<Item = i64>>(iter: T) -> Self {
1015        let mut iter = iter.into_iter();
1016        let mut s = String::with_capacity(StringWriter::<N>::string_length());
1017        let mut idx: usize;
1018        for i in 0..N {
1019            let item = iter.next();
1020            if item.is_none() {
1021                return Self(Err(anyhow::anyhow!(
1022                    "expected {} elements got {}",
1023                    N,
1024                    i + 1
1025                )));
1026            }
1027            let n = item.unwrap();
1028            idx = s.len();
1029            {
1030                s.write_fmt(format_args!("0{n:X}")).unwrap();
1031            }
1032            let l = s.len();
1033            let s_bytes: &mut [u8] = unsafe { s.as_bytes_mut() };
1034            s_bytes[idx] = HEX_CHARS[l - (idx + 2)] as u8;
1035        }
1036
1037        Self(Ok(StringWriter::<N>(s)))
1038    }
1039}
1040
1041impl From<(i64, i64, i64)> for StringWriter<3> {
1042    fn from(value: (i64, i64, i64)) -> Self {
1043        StringWriterResult::<3>::from_iter([value.0, value.1, value.2])
1044            .into_inner()
1045            .unwrap()
1046    }
1047}
1048
1049impl From<(i64, i64)> for StringWriter<2> {
1050    fn from(value: (i64, i64)) -> Self {
1051        StringWriterResult::<2>::from_iter([value.0, value.1])
1052            .into_inner()
1053            .unwrap()
1054    }
1055}
1056
1057impl From<i64> for StringWriter<1> {
1058    fn from(n: i64) -> Self {
1059        StringWriterResult::<1>::from_iter([n])
1060            .into_inner()
1061            .unwrap()
1062    }
1063}
1064
1065fn is_valid_range(s: &str, start: usize, end: usize) -> bool {
1066    !s.is_empty() && start < end && end <= s.len()
1067}
1068
1069struct StringParser<'a, const N: usize> {
1070    count: usize,
1071    start: usize,
1072    end: usize,
1073    has_object_id_at_end: bool,
1074    s: &'a str,
1075}
1076
1077impl<'a, const N: usize> StringParser<'a, N> {
1078    fn new(s: &'a str) -> StringParser<'a, N> {
1079        StringParser {
1080            count: 0,
1081            start: 0,
1082            end: 1,
1083            has_object_id_at_end: false,
1084            s,
1085        }
1086    }
1087
1088    fn with_object_id(mut self) -> Self {
1089        self.has_object_id_at_end = true;
1090        self
1091    }
1092
1093    fn end(&self) -> usize {
1094        self.end
1095    }
1096}
1097
1098impl<const N: usize> Iterator for StringParser<'_, N> {
1099    type Item = i64;
1100    fn next(&mut self) -> Option<i64> {
1101        if self.count >= N {
1102            return None;
1103        }
1104        if !is_valid_range(self.s, self.start, self.end) {
1105            return None;
1106        }
1107        let l = usize::from_str_radix(&self.s[self.start..self.end], 16);
1108        if l.is_err() {
1109            return None;
1110        }
1111        self.start = self.end;
1112        self.end = self.start + l.unwrap() + 1;
1113        if !is_valid_range(self.s, self.start, self.end) {
1114            return None;
1115        }
1116        let s = &self.s[self.start..self.end];
1117        let result = if s.len() == 16 && s.chars().all(|c| matches!(c, 'f' | 'F')) {
1118            Some(-1i64)
1119        } else {
1120            i64::from_str_radix(s, 16).ok()
1121        };
1122        self.start = self.end;
1123        self.end = self.start + 1;
1124        self.count += 1;
1125        let l = self.s.len();
1126        if self.has_object_id_at_end {
1127            if self.count == N && self.end + 23 != l {
1128                return None;
1129            }
1130        } else if self.count == N && self.end != l + 1 {
1131            return None;
1132        }
1133        result
1134    }
1135}
1136
1137#[rustfmt::skip]
1138#[cfg(test)]
1139mod tests {
1140    use super::*;
1141
1142    #[test]
1143    fn test_string_parser() {
1144        let mut parser = StringParser::<3>::new("010101");
1145        assert_eq!(Some(1), parser.next());
1146        assert_eq!(Some(1), parser.next());
1147        assert_eq!(Some(1), parser.next());
1148        assert_eq!(None, parser.next());
1149        assert_eq!(None, parser.next());
1150    }
1151
1152    #[test]
1153    fn test_null_parser() {
1154        let mut parser = StringParser::<0>::new("01010101");
1155        assert_eq!(None, parser.next());
1156    }
1157
1158    #[test]
1159    fn test_prefix() {
1160        assert_eq!('V', CustomerId::PREFIX);
1161        assert_eq!('U', CustomerResourceId::PREFIX);
1162        assert_eq!('T', OrganizationId::PREFIX);
1163        assert_eq!('S', OrganizationResourceId::PREFIX);
1164        assert_eq!('R', InstitutionId::PREFIX);
1165        assert_eq!('Q', InstitutionResourceId::PREFIX);
1166    }
1167
1168    #[test]
1169    fn test_invalid_prefix() {
1170        assert_eq!(None, CustomerId::parse("U01").ok());
1171        assert_eq!(None, CustomerResourceId::parse("V01").ok());
1172        assert_eq!(None, OrganizationId::parse("S01").ok());
1173        assert_eq!(None, OrganizationResourceId::parse("T01").ok());
1174        assert_eq!(None, InstitutionId::parse("Q01").ok());
1175        assert_eq!(None, InstitutionResourceId::parse("R01").ok());
1176    }
1177
1178    #[test]
1179    fn test_customer_id() {
1180        let max_id = CustomerId::parse("VFFFFFFFFFFFFFFFFF").unwrap();
1181        let id1 = CustomerId::parse("V01").unwrap();
1182        let id2 = CustomerId::parse("V120").unwrap();
1183        let id3 = CustomerId::parse("V2500").unwrap();
1184        let id4 = CustomerId::parse("V36000").unwrap();
1185        let id5 = CustomerId::parse("V48000F").unwrap();
1186        let id6 = CustomerId::parse("V5AF000F").unwrap();
1187        let id7 = CustomerId::parse("V6B5F000F").unwrap();
1188        let id8 = CustomerId::parse("VF7FFFFFFFFFFFFFFF").unwrap();
1189        assert_eq!(CustomerId { cid: -1 }, max_id);
1190        assert_eq!(CustomerId { cid: 1 }, id1);
1191        assert_eq!(CustomerId { cid: 0x20 }, id2);
1192        assert_eq!(CustomerId { cid: 0x500 }, id3);
1193        assert_eq!(CustomerId { cid: 0x6000 }, id4);
1194        assert_eq!(CustomerId { cid: 0x8000F }, id5);
1195        assert_eq!(CustomerId { cid: 0xAF000F }, id6);
1196        assert_eq!(CustomerId { cid: 0xB5F000F, }, id7);
1197        assert_eq!(CustomerId { cid: i64::MAX }, id8);
1198        assert_eq!(id1.to_string(), "V01");
1199        assert_eq!(id2.to_string(), "V120");
1200        assert_eq!(id3.to_string(), "V2500");
1201        assert_eq!(id4.to_string(), "V36000");
1202        assert_eq!(id5.to_string(), "V48000F");
1203        assert_eq!(id6.to_string(), "V5AF000F");
1204        assert_eq!(id7.to_string(), "V6B5F000F");
1205        assert_eq!(id8.to_string(), "VF7FFFFFFFFFFFFFFF");
1206        assert_eq!(None, CustomerId::parse("VF8FFFFFFFFFFFFFFF").ok());
1207        assert_eq!(None, CustomerId::parse("VF9FFFFFFFFFFFFFFF").ok());
1208        assert_eq!(None, CustomerId::parse("VFAFFFFFFFFFFFFFFF").ok());
1209        assert_eq!(None, CustomerId::parse("VFBFFFFFFFFFFFFFFF").ok());
1210        assert_eq!(None, CustomerId::parse("VFCFFFFFFFFFFFFFFF").ok());
1211        assert_eq!(None, CustomerId::parse("VFDFFFFFFFFFFFFFFF").ok());
1212        assert_eq!(None, CustomerId::parse("VFEFFFFFFFFFFFFFFF").ok());
1213        assert_eq!(None, CustomerId::parse("VVV").ok());
1214        assert_eq!(None, CustomerId::parse("V0ABC").ok());
1215        assert_eq!(id1.unzip(), 1);
1216    }
1217
1218    #[test]
1219    fn test_customer_resource_id() {
1220        let oid1 = ID::from_str("6603f7b32b1753f84a719e01").unwrap();
1221        let oid2 = ID::from_str("6603f7b32b1753f84a719e02").unwrap();
1222        let oid3 = ID::from_str("6603f7b32b1753f84a719e03").unwrap();
1223        let oid4 = ID::from_str("6603f7b32b1753f84a719e04").unwrap();
1224        let id1 = CustomerResourceId::parse("U016603f7b32b1753f84a719e01").unwrap();
1225        let id2 = CustomerResourceId::parse("U1206603f7b32b1753f84a719e02").unwrap();
1226        let id3 = CustomerResourceId::parse("U25006603f7b32b1753f84a719e03").unwrap();
1227        let id4 = CustomerResourceId::parse("U360006603f7b32b1753f84a719e04").unwrap();
1228        let id5 = CustomerResourceId::parse("U48000F6603f7b32b1753f84a719e01").unwrap();
1229        let id6 = CustomerResourceId::parse("U5AF000F6603f7b32b1753f84a719e02").unwrap();
1230        let id7 = CustomerResourceId::parse("U6B5F000F6603f7b32b1753f84a719e03").unwrap();
1231        let id8 = CustomerResourceId::parse("UF7FFFFFFFFFFFFFFF6603f7b32b1753f84a719e04").unwrap();
1232        assert_eq!(CustomerResourceId { cid: 1, id: oid1, }, id1);
1233        assert_eq!(CustomerResourceId { cid: 0x20, id: oid2, }, id2);
1234        assert_eq!(CustomerResourceId { cid: 0x500, id: oid3, }, id3);
1235        assert_eq!(CustomerResourceId { cid: 0x6000, id: oid4, }, id4);
1236        assert_eq!(CustomerResourceId { cid: 0x8000F, id: oid1, }, id5);
1237        assert_eq!(CustomerResourceId { cid: 0xAF000F, id: oid2, }, id6);
1238        assert_eq!(CustomerResourceId { cid: 0xB5F000F, id: oid3, }, id7);
1239        assert_eq!(CustomerResourceId { cid: i64::MAX, id: oid4, }, id8);
1240        assert_eq!(id1.to_string(), "U016603f7b32b1753f84a719e01");
1241        assert_eq!(id2.to_string(), "U1206603f7b32b1753f84a719e02");
1242        assert_eq!(id3.to_string(), "U25006603f7b32b1753f84a719e03");
1243        assert_eq!(id4.to_string(), "U360006603f7b32b1753f84a719e04");
1244        assert_eq!(id5.to_string(), "U48000F6603f7b32b1753f84a719e01");
1245        assert_eq!(id6.to_string(), "U5AF000F6603f7b32b1753f84a719e02");
1246        assert_eq!(id7.to_string(), "U6B5F000F6603f7b32b1753f84a719e03");
1247        assert_eq!(id8.to_string(), "UF7FFFFFFFFFFFFFFF6603f7b32b1753f84a719e04");
1248        assert_eq!(None, CustomerResourceId::parse("UF8FFFFFFFFFFFFFFF6603f7b32b1753f84a719e01").ok());
1249        assert_eq!(None, CustomerResourceId::parse("UF9FFFFFFFFFFFFFFF6603f7b32b1753f84a719e02").ok());
1250        assert_eq!(None, CustomerResourceId::parse("UFAFFFFFFFFFFFFFFF6603f7b32b1753f84a719e03").ok());
1251        assert_eq!(None, CustomerResourceId::parse("UFBFFFFFFFFFFFFFFF6603f7b32b1753f84a719e04").ok());
1252        assert_eq!(None, CustomerResourceId::parse("UFCFFFFFFFFFFFFFFF6603f7b32b1753f84a719e01").ok());
1253        assert_eq!(None, CustomerResourceId::parse("UFDFFFFFFFFFFFFFFF6603f7b32b1753f84a719e02").ok());
1254        assert_eq!(None, CustomerResourceId::parse("UFEFFFFFFFFFFFFFFF6603f7b32b1753f84a719e03").ok());
1255        assert_eq!(None, CustomerResourceId::parse("UVV6603f7b32b1753f84a719e04").ok());
1256        assert_eq!(None, CustomerResourceId::parse("U0ABC6603f7b32b1753f84a719e04").ok());
1257        assert_eq!(id1.root(), CustomerId { cid: 1 });
1258        assert_eq!(id1.parent(), CustomerId { cid: 1 });
1259        assert_eq!(id1.unzip(), (1, oid1));
1260    }
1261
1262    #[test]
1263    fn test_organization_id() {
1264        let max_id = OrganizationId::parse("TFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap();
1265        let id1 = OrganizationId::parse("T0101").unwrap();
1266        let id2 = OrganizationId::parse("T120120").unwrap();
1267        let id3 = OrganizationId::parse("T25002500").unwrap();
1268        let id4 = OrganizationId::parse("T3600036000").unwrap();
1269        let id5 = OrganizationId::parse("T48000F48000F").unwrap();
1270        let id6 = OrganizationId::parse("T5AF000F5AF000F").unwrap();
1271        let id7 = OrganizationId::parse("T6B5F000F6B5F000F").unwrap();
1272        let id8 = OrganizationId::parse("TF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF").unwrap();
1273        assert_eq!(OrganizationId { cid: -1, oid: -1 }, max_id);
1274        assert_eq!(OrganizationId { cid: 1, oid: 1 }, id1);
1275        assert_eq!(OrganizationId { cid: 0x20, oid: 0x20 }, id2);
1276        assert_eq!(OrganizationId { cid: 0x500, oid: 0x500 }, id3);
1277        assert_eq!(OrganizationId { cid: 0x6000, oid: 0x6000 }, id4);
1278        assert_eq!(OrganizationId { cid: 0x8000F, oid: 0x8000F }, id5);
1279        assert_eq!(OrganizationId { cid: 0xAF000F, oid: 0xAF000F }, id6);
1280        assert_eq!(OrganizationId { cid: 0xB5F000F, oid: 0xB5F000F }, id7);
1281        assert_eq!(OrganizationId { cid: i64::MAX, oid: i64::MAX }, id8);
1282        assert_eq!(id1.to_string(), "T0101");
1283        assert_eq!(id2.to_string(), "T120120");
1284        assert_eq!(id3.to_string(), "T25002500");
1285        assert_eq!(id4.to_string(), "T3600036000");
1286        assert_eq!(id5.to_string(), "T48000F48000F");
1287        assert_eq!(id6.to_string(), "T5AF000F5AF000F");
1288        assert_eq!(id7.to_string(), "T6B5F000F6B5F000F");
1289        assert_eq!(id8.to_string(), "TF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF");
1290        assert_eq!(None, OrganizationId::parse("TF8FFFFFFFFFFFFFFF8FFFFFFFFFFFFFFF").ok());
1291        assert_eq!(None, OrganizationId::parse("TF9FFFFFFFFFFFFFFF9FFFFFFFFFFFFFFF").ok());
1292        assert_eq!(None, OrganizationId::parse("TFAFFFFFFFFFFFFFFFAFFFFFFFFFFFFFFF").ok());
1293        assert_eq!(None, OrganizationId::parse("TFBFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFF").ok());
1294        assert_eq!(None, OrganizationId::parse("TFCFFFFFFFFFFFFFFFCFFFFFFFFFFFFFFF").ok());
1295        assert_eq!(None, OrganizationId::parse("TFDFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFF").ok());
1296        assert_eq!(None, OrganizationId::parse("TFEFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFF").ok());
1297        assert_eq!(None, OrganizationId::parse("TFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").ok());
1298        assert_eq!(None, OrganizationId::parse("TVVVU").ok());
1299        assert_eq!(None, OrganizationId::parse("TFABC1C").ok());
1300        assert_eq!(id1.root(), CustomerId { cid: 1 });
1301        assert_eq!(id1.parent(), CustomerId { cid: 1 });
1302        assert_eq!(id1.unzip(), (1, 1));
1303    }
1304
1305    #[test]
1306    fn test_organization_resource_id() {
1307        let oid1 = ID::from_str("6603f7b32b1753f84a719e01").unwrap();
1308        let oid2 = ID::from_str("6603f7b32b1753f84a719e02").unwrap();
1309        let oid3 = ID::from_str("6603f7b32b1753f84a719e03").unwrap();
1310        let oid4 = ID::from_str("6603f7b32b1753f84a719e04").unwrap();
1311        let id1 = OrganizationResourceId::parse("S01016603f7b32b1753f84a719e01").unwrap();
1312        let id2 = OrganizationResourceId::parse("S1201206603f7b32b1753f84a719e02").unwrap();
1313        let id3 = OrganizationResourceId::parse("S250025006603f7b32b1753f84a719e03").unwrap();
1314        let id4 = OrganizationResourceId::parse("S36000360006603f7b32b1753f84a719e04").unwrap();
1315        let id5 = OrganizationResourceId::parse("S48000F48000F6603f7b32b1753f84a719e01").unwrap();
1316        let id6 = OrganizationResourceId::parse("S5AF000F5AF000F6603f7b32b1753f84a719e02").unwrap();
1317        let id7 = OrganizationResourceId::parse("S6B5F000F6B5F000F6603f7b32b1753f84a719e03").unwrap();
1318        let id8 = OrganizationResourceId::parse("SF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF6603f7b32b1753f84a719e04").unwrap();
1319        assert_eq!(OrganizationResourceId { cid: 1, oid: 1, id: oid1, }, id1);
1320        assert_eq!(OrganizationResourceId { cid: 0x20, oid: 0x20, id: oid2, }, id2);
1321        assert_eq!(OrganizationResourceId { cid: 0x500, oid: 0x500, id: oid3, }, id3);
1322        assert_eq!(OrganizationResourceId { cid: 0x6000, oid: 0x6000, id: oid4, }, id4);
1323        assert_eq!(OrganizationResourceId { cid: 0x8000F, oid: 0x8000F, id: oid1, }, id5);
1324        assert_eq!(OrganizationResourceId { cid: 0xAF000F, oid: 0xAF000F, id: oid2, }, id6);
1325        assert_eq!(OrganizationResourceId { cid: 0xB5F000F, oid: 0xB5F000F, id: oid3, }, id7);
1326        assert_eq!(OrganizationResourceId { cid: i64::MAX, oid: i64::MAX, id: oid4, }, id8);
1327        assert_eq!(id1.to_string(), "S01016603f7b32b1753f84a719e01");
1328        assert_eq!(id2.to_string(), "S1201206603f7b32b1753f84a719e02");
1329        assert_eq!(id3.to_string(), "S250025006603f7b32b1753f84a719e03");
1330        assert_eq!(id4.to_string(), "S36000360006603f7b32b1753f84a719e04");
1331        assert_eq!(id5.to_string(), "S48000F48000F6603f7b32b1753f84a719e01");
1332        assert_eq!(id6.to_string(), "S5AF000F5AF000F6603f7b32b1753f84a719e02");
1333        assert_eq!(id7.to_string(), "S6B5F000F6B5F000F6603f7b32b1753f84a719e03");
1334        assert_eq!(id8.to_string(), "SF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF6603f7b32b1753f84a719e04");
1335        assert_eq!(None, OrganizationResourceId::parse("SF8FFFFFFFFFFFFFFFF8FFFFFFFFFFFFFFF6603f7b32b1753f84a719e01").ok());
1336        assert_eq!(None, OrganizationResourceId::parse("SF9FFFFFFFFFFFFFFFF9FFFFFFFFFFFFFFF6603f7b32b1753f84a719e02").ok());
1337        assert_eq!(None, OrganizationResourceId::parse("SFAFFFFFFFFFFFFFFFFAFFFFFFFFFFFFFFF6603f7b32b1753f84a719e03").ok());
1338        assert_eq!(None, OrganizationResourceId::parse("SFBFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFF6603f7b32b1753f84a719e04").ok());
1339        assert_eq!(None, OrganizationResourceId::parse("SFCFFFFFFFFFFFFFFFFCFFFFFFFFFFFFFFF6603f7b32b1753f84a719e01").ok());
1340        assert_eq!(None, OrganizationResourceId::parse("SFDFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFF6603f7b32b1753f84a719e02").ok());
1341        assert_eq!(None, OrganizationResourceId::parse("SFEFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFF6603f7b32b1753f84a719e03").ok());
1342        assert_eq!(None, OrganizationResourceId::parse("SVV6603f7b32b1753f84a719e04").ok());
1343        assert_eq!(None, OrganizationResourceId::parse("S0A0A0A0A0A0ABC6603f7b32b1753f84a719e04").ok());
1344        assert_eq!(id1.root(), CustomerId { cid: 1 });
1345        assert_eq!(id1.parent(), OrganizationId { cid: 1, oid: 1 });
1346        assert_eq!(id1.unzip(), (1, 1, oid1));
1347    }
1348
1349    #[test]
1350    fn test_institution_id() {
1351        let id1 = InstitutionId::parse("R010101").unwrap();
1352        let id2 = InstitutionId::parse("R120120120").unwrap();
1353        let id3 = InstitutionId::parse("R250025002500").unwrap();
1354        let id4 = InstitutionId::parse("R360003600036000").unwrap();
1355        let id5 = InstitutionId::parse("R48000F48000F48000F").unwrap();
1356        let id6 = InstitutionId::parse("R5AF000F5AF000F5AF000F").unwrap();
1357        let id7 = InstitutionId::parse("R6B5F000F6B5F000F6B5F000F").unwrap();
1358        let id8 = InstitutionId::parse("RF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF").unwrap();
1359        assert_eq!(InstitutionId { cid: 1, oid: 1, iid: 1, }, id1);
1360        assert_eq!(InstitutionId { cid: 0x20, oid: 0x20, iid: 0x20, }, id2);
1361        assert_eq!(InstitutionId { cid: 0x500, oid: 0x500, iid: 0x500, }, id3);
1362        assert_eq!(InstitutionId { cid: 0x6000, oid: 0x6000, iid: 0x6000, }, id4);
1363        assert_eq!(InstitutionId { cid: 0x8000F, oid: 0x8000F, iid: 0x8000F, }, id5);
1364        assert_eq!(InstitutionId { cid: 0xAF000F, oid: 0xAF000F, iid: 0xAF000F, }, id6);
1365        assert_eq!(InstitutionId { cid: 0xB5F000F, oid: 0xB5F000F, iid: 0xB5F000F, }, id7);
1366        assert_eq!(InstitutionId { cid: i64::MAX, oid: i64::MAX, iid: i64::MAX }, id8);
1367        assert_eq!(id1.to_string(), "R010101");
1368        assert_eq!(id2.to_string(), "R120120120");
1369        assert_eq!(id3.to_string(), "R250025002500");
1370        assert_eq!(id4.to_string(), "R360003600036000");
1371        assert_eq!(id5.to_string(), "R48000F48000F48000F");
1372        assert_eq!(id6.to_string(), "R5AF000F5AF000F5AF000F");
1373        assert_eq!(id7.to_string(), "R6B5F000F6B5F000F6B5F000F");
1374        assert_eq!(id8.to_string(), "RF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF");
1375        assert_eq!(None, InstitutionId::parse("RF8FFFFFFFFFFFFFFF8FFFFFFFFFFFFFFF8FFFFFFFFFFFFFFF").ok());
1376        assert_eq!(None, InstitutionId::parse("RF9FFFFFFFFFFFFFFF9FFFFFFFFFFFFFFF9FFFFFFFFFFFFFFF").ok());
1377        assert_eq!(None, InstitutionId::parse("RFAFFFFFFFFFFFFFFFAFFFFFFFFFFFFFFFAFFFFFFFFFFFFFFF").ok());
1378        assert_eq!(None, InstitutionId::parse("RFBFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFF").ok());
1379        assert_eq!(None, InstitutionId::parse("RFCFFFFFFFFFFFFFFFCFFFFFFFFFFFFFFFCFFFFFFFFFFFFFFF").ok());
1380        assert_eq!(None, InstitutionId::parse("RFDFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFF").ok());
1381        assert_eq!(None, InstitutionId::parse("RFEFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFF").ok());
1382        assert_eq!(None, InstitutionId::parse("R0FF").ok());
1383        assert_eq!(None, InstitutionId::parse("RF0").ok());
1384        assert_eq!(id1.root(), CustomerId { cid: 1 });
1385        assert_eq!(id1.parent(), OrganizationId { cid: 1, oid: 1 });
1386        assert_eq!(id1.unzip(), (1, 1, 1));
1387    }
1388
1389
1390    #[test]
1391    fn test_institution_resource_id() {
1392        let oid1 = ID::from_str("6603f7b32b1753f84a719e01").unwrap();
1393        let oid2 = ID::from_str("6603f7b32b1753f84a719e02").unwrap();
1394        let oid3 = ID::from_str("6603f7b32b1753f84a719e03").unwrap();
1395        let oid4 = ID::from_str("6603f7b32b1753f84a719e04").unwrap();
1396        let id1 = InstitutionResourceId::parse("Q0101016603f7b32b1753f84a719e01").unwrap();
1397        let id2 = InstitutionResourceId::parse("Q1201201206603f7b32b1753f84a719e02").unwrap();
1398        let id3 = InstitutionResourceId::parse("Q2500250025006603f7b32b1753f84a719e03").unwrap();
1399        let id4 = InstitutionResourceId::parse("Q3600036000360006603f7b32b1753f84a719e04").unwrap();
1400        let id5 = InstitutionResourceId::parse("Q48000F48000F48000F6603f7b32b1753f84a719e01").unwrap();
1401        let id6 = InstitutionResourceId::parse("Q5AF000F5AF000F5AF000F6603f7b32b1753f84a719e02").unwrap();
1402        let id7 = InstitutionResourceId::parse("Q6B5F000F6B5F000F6B5F000F6603f7b32b1753f84a719e03").unwrap();
1403        let id8 = InstitutionResourceId::parse("QF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF6603f7b32b1753f84a719e04").unwrap();
1404        assert_eq!(InstitutionResourceId { cid: 1, oid: 1, iid: 1, id: oid1, }, id1);
1405        assert_eq!(InstitutionResourceId { cid: 0x20, oid: 0x20, iid: 0x20, id: oid2, }, id2);
1406        assert_eq!(InstitutionResourceId { cid: 0x500, oid: 0x500, iid: 0x500, id: oid3, }, id3);
1407        assert_eq!(InstitutionResourceId { cid: 0x6000, oid: 0x6000, iid: 0x6000, id: oid4, }, id4);
1408        assert_eq!(InstitutionResourceId { cid: 0x8000F, oid: 0x8000F, iid: 0x8000F, id: oid1, }, id5);
1409        assert_eq!(InstitutionResourceId { cid: 0xAF000F, oid: 0xAF000F, iid: 0xAF000F, id: oid2, }, id6);
1410        assert_eq!(InstitutionResourceId { cid: 0xB5F000F, oid: 0xB5F000F, iid: 0xB5F000F, id: oid3, }, id7);
1411        assert_eq!(InstitutionResourceId { cid: i64::MAX, oid: i64::MAX, iid: i64::MAX, id: oid4, }, id8);
1412        assert_eq!(id1.to_string(), "Q0101016603f7b32b1753f84a719e01");
1413        assert_eq!(id2.to_string(), "Q1201201206603f7b32b1753f84a719e02");
1414        assert_eq!(id3.to_string(), "Q2500250025006603f7b32b1753f84a719e03");
1415        assert_eq!(id4.to_string(), "Q3600036000360006603f7b32b1753f84a719e04");
1416        assert_eq!(id5.to_string(), "Q48000F48000F48000F6603f7b32b1753f84a719e01");
1417        assert_eq!(id6.to_string(), "Q5AF000F5AF000F5AF000F6603f7b32b1753f84a719e02");
1418        assert_eq!(id7.to_string(), "Q6B5F000F6B5F000F6B5F000F6603f7b32b1753f84a719e03");
1419        assert_eq!(id8.to_string(), "QF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF6603f7b32b1753f84a719e04");
1420        assert_eq!(None, InstitutionResourceId::parse("QF8FFFFFFFFFFFFFFFF8FFFFFFFFFFFFFFF8FFFFFFFFFFFFFFF6603f7b32b1753f84a719e01").ok());
1421        assert_eq!(None, InstitutionResourceId::parse("QF9FFFFFFFFFFFFFFFF9FFFFFFFFFFFFFFF9FFFFFFFFFFFFFFF6603f7b32b1753f84a719e02").ok());
1422        assert_eq!(None, InstitutionResourceId::parse("QFAFFFFFFFFFFFFFFFFAFFFFFFFFFFFFFFFAFFFFFFFFFFFFFFF6603f7b32b1753f84a719e03").ok());
1423        assert_eq!(None, InstitutionResourceId::parse("QFBFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFF6603f7b32b1753f84a719e04").ok());
1424        assert_eq!(None, InstitutionResourceId::parse("QFCFFFFFFFFFFFFFFFFCFFFFFFFFFFFFFFFCFFFFFFFFFFFFFFF6603f7b32b1753f84a719e01").ok());
1425        assert_eq!(None, InstitutionResourceId::parse("QFDFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFF6603f7b32b1753f84a719e02").ok());
1426        assert_eq!(None, InstitutionResourceId::parse("QFEFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFF6603f7b32b1753f84a719e03").ok());
1427        assert_eq!(None, InstitutionResourceId::parse("QVV6603f7b32b1753f84a719e04").ok());
1428        assert_eq!(None, InstitutionResourceId::parse("Q0A0A0A0A0A0ABC6603f7b32b1753f84a719e04").ok());
1429        assert_eq!(id1.root(), CustomerId { cid: 1 });
1430        assert_eq!(id1.parent(), InstitutionId { cid: 1, oid: 1, iid: 1 });
1431        assert_eq!(id1.unzip(), (1, 1, 1, oid1));
1432    }
1433
1434    #[cfg(feature = "serde-str")]
1435    #[test]
1436    fn test_infra_context_serde() {
1437        use super::InfraContext;
1438        let infra_context = serde_json::from_str::<InfraContext>("\"V09\"").expect("Failed to parse InfraContext");
1439        assert_eq!(infra_context, InfraContext::Customer(9.into()));
1440        assert_eq!(serde_json::to_string(&infra_context).expect("Failed to serialize InfraContext"), "\"V09\"");
1441    }
1442}