Skip to main content

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