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';
23pub const CUSTOMER_RESOURCE_ID_PREFIX: char = 'U';
25pub const ORGANIZATION_ID_PREFIX: char = 'T';
27pub const ORGANIZATION_RESOURCE_ID_PREFIX: char = 'S';
29pub const INSTITUTION_ID_PREFIX: char = 'R';
31pub const INSTITUTION_RESOURCE_ID_PREFIX: char = 'Q';
33pub 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)]
51pub 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
87trait Prefixed {
89 const PREFIX: char;
91}
92
93macro_rules! impl_id {
94 ($t:ty, $p:expr) => {
95 impl $t {
96 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#[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 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#[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 pub fn root(&self) -> CustomerId {
354 CustomerId::from(self.cid)
355 }
356
357 pub fn parent(&self) -> CustomerId {
359 CustomerId::from(self.cid)
360 }
361
362 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#[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 pub fn id(&self) -> i64 {
443 self.oid
444 }
445
446 pub fn root(&self) -> CustomerId {
448 CustomerId::from(self.cid)
449 }
450
451 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 pub fn unzip(&self) -> (i64, i64) {
462 (self.cid, self.oid)
463 }
464
465 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#[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 pub fn root(&self) -> CustomerId {
560 CustomerId::from(self.cid)
561 }
562
563 pub fn parent(&self) -> OrganizationId {
565 OrganizationId::from((self.cid, self.oid))
566 }
567
568 pub fn id(&self) -> &ID {
570 &self.id
571 }
572
573 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#[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 pub cid: i64,
653 pub oid: i64,
655 pub iid: i64,
657}
658
659impl InstitutionId {
660 pub fn id(&self) -> i64 {
662 self.iid
663 }
664
665 pub fn root(&self) -> CustomerId {
667 CustomerId::from(self.cid)
668 }
669
670 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 pub fn unzip(&self) -> (i64, i64, i64) {
681 (self.cid, self.oid, self.iid)
682 }
683
684 pub fn untuple(&self) -> (i64, (i64, i64)) {
686 (self.cid, (self.oid, self.iid))
687 }
688
689 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#[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 pub fn root(&self) -> CustomerId {
786 CustomerId::from(self.cid)
787 }
788
789 pub fn parent(&self) -> InstitutionId {
791 InstitutionId::from((self.cid, self.oid, self.iid))
792 }
793
794 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#[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(CustomerId),
846 Organization(OrganizationId),
848 Institution(InstitutionId),
850}
851
852impl InfraContext {
853 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 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 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 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 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 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 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 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 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 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 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 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 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}