Skip to main content

qm_entity/ids/
object.rs

1use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
2use qm_mongodb::bson::{self, oid::ObjectId, Bson};
3
4use crate::ids::InfraContext;
5
6use super::{CustomerId, InstitutionId, OrganizationId};
7
8#[derive(
9    Debug,
10    Default,
11    Clone,
12    Copy,
13    PartialEq,
14    Eq,
15    PartialOrd,
16    Ord,
17    Hash,
18    serde::Serialize,
19    serde::Deserialize,
20)]
21#[serde(transparent)]
22/// MongoDB ObjectId wrapper for GraphQL ID type.
23pub struct ID(ObjectId);
24
25#[Scalar]
26impl ScalarType for ID {
27    fn parse(value: Value) -> InputValueResult<Self> {
28        match value {
29            Value::String(s) => Ok(ID(ObjectId::parse_str(s)?)),
30            Value::Object(o) => {
31                let json = Value::Object(o).into_json()?;
32                let bson: Bson = Bson::try_from(json)?;
33                bson.as_object_id()
34                    .map(ID)
35                    .ok_or_else(|| InputValueError::custom("could not parse the value as an ID"))
36            }
37            _ => Err(InputValueError::expected_type(value)),
38        }
39    }
40
41    fn to_value(&self) -> Value {
42        Value::String(self.0.to_string())
43    }
44}
45
46impl std::ops::Deref for ID {
47    type Target = ObjectId;
48
49    fn deref(&self) -> &Self::Target {
50        &self.0
51    }
52}
53
54impl std::ops::DerefMut for ID {
55    fn deref_mut(&mut self) -> &mut Self::Target {
56        &mut self.0
57    }
58}
59
60impl std::str::FromStr for ID {
61    type Err = bson::error::Error;
62
63    fn from_str(s: &str) -> Result<Self, Self::Err> {
64        ObjectId::from_str(s).map(ID)
65    }
66}
67
68impl From<ObjectId> for ID {
69    fn from(value: ObjectId) -> Self {
70        Self(value)
71    }
72}
73
74impl From<ID> for Bson {
75    fn from(val: ID) -> Self {
76        val.0.into()
77    }
78}
79
80#[derive(
81    Debug,
82    Clone,
83    Copy,
84    PartialEq,
85    Eq,
86    PartialOrd,
87    Ord,
88    Hash,
89    Default,
90    serde::Serialize,
91    serde::Deserialize,
92)]
93/// Owner identifier that can represent a customer, organization, or institution.
94pub struct OwnerId {
95    /// Customer ID.
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub cid: Option<i64>,
98    /// Organization ID.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub oid: Option<i64>,
101    /// Institution ID.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub iid: Option<i64>,
104}
105
106impl From<CustomerId> for OwnerId {
107    fn from(value: CustomerId) -> Self {
108        Self {
109            cid: Some(value.unzip()),
110            ..Default::default()
111        }
112    }
113}
114
115impl From<OrganizationId> for OwnerId {
116    fn from(value: OrganizationId) -> Self {
117        let (cid, oid) = value.unzip();
118        Self {
119            cid: Some(cid),
120            oid: Some(oid),
121            ..Default::default()
122        }
123    }
124}
125
126impl From<InstitutionId> for OwnerId {
127    fn from(value: InstitutionId) -> Self {
128        let (cid, oid, iid) = value.unzip();
129        Self {
130            cid: Some(cid),
131            oid: Some(oid),
132            iid: Some(iid),
133        }
134    }
135}
136
137impl From<InfraContext> for OwnerId {
138    fn from(value: InfraContext) -> Self {
139        match value {
140            InfraContext::Customer(v) => v.into(),
141            InfraContext::Organization(v) => v.into(),
142            InfraContext::Institution(v) => v.into(),
143        }
144    }
145}
146
147impl<'a> TryFrom<&'a OwnerId> for InfraContext {
148    type Error = anyhow::Error;
149
150    fn try_from(value: &'a OwnerId) -> Result<Self, Self::Error> {
151        match value {
152            OwnerId {
153                cid: Some(cid),
154                oid: Some(oid),
155                iid: Some(iid),
156            } => Ok(InfraContext::Institution((*cid, *oid, *iid).into())),
157            OwnerId {
158                cid: Some(cid),
159                oid: Some(oid),
160                iid: None,
161            } => Ok(InfraContext::Organization((*cid, *oid).into())),
162            OwnerId {
163                cid: Some(cid),
164                oid: None,
165                iid: None,
166            } => Ok(InfraContext::Customer((*cid).into())),
167            _ => anyhow::bail!("invalid owner id"),
168        }
169    }
170}
171
172#[derive(Default, serde::Deserialize, serde::Serialize, Debug, Clone)]
173#[serde(transparent)]
174/// Owner wrapper type.
175pub struct Owner {
176    #[serde(skip_serializing_if = "Owner::is_none")]
177    o: OwnerType,
178}
179
180impl Owner {
181    /// Creates a new Owner from an OwnerType.
182    pub fn new(o: OwnerType) -> Self {
183        Self { o }
184    }
185
186    /// Returns the OwnerId if present.
187    pub fn as_owner_id(&self) -> Option<&OwnerId> {
188        self.o.as_owner_id()
189    }
190}
191
192impl From<InfraContext> for Owner {
193    fn from(value: InfraContext) -> Self {
194        Self { o: value.into() }
195    }
196}
197
198/// Owner type enumeration.
199#[derive(Default, serde::Deserialize, serde::Serialize, Debug, Clone)]
200#[serde(tag = "ty", content = "id")]
201pub enum OwnerType {
202    /// No owner.
203    #[default]
204    None,
205    /// Customer owner.
206    Customer(OwnerId),
207    /// Organization owner.
208    Organization(OwnerId),
209    /// Institution owner.
210    Institution(OwnerId),
211}
212
213impl OwnerType {
214    /// Returns true if this is None.
215    pub fn is_none(&self) -> bool {
216        matches!(self, Self::None)
217    }
218
219    /// Returns the OwnerId if present.
220    pub fn as_owner_id(&self) -> Option<&OwnerId> {
221        match self {
222            OwnerType::None => None,
223            OwnerType::Customer(id) | OwnerType::Organization(id) | OwnerType::Institution(id) => {
224                Some(id)
225            }
226        }
227    }
228}
229
230impl From<InfraContext> for OwnerType {
231    fn from(value: InfraContext) -> Self {
232        match value {
233            InfraContext::Customer(v) => OwnerType::Customer(v.into()),
234            InfraContext::Organization(v) => OwnerType::Organization(v.into()),
235            InfraContext::Institution(v) => OwnerType::Institution(v.into()),
236        }
237    }
238}