1use 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#[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#[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#[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#[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#[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#[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 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}