Skip to main content

alibi_core/wire/
user.rs

1use crate::AuthUser;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize, Serializer};
4use std::borrow::Cow;
5/// Public user response shape.
6#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
7#[serde(try_from = "UserViewInput")]
8pub struct UserView {
9    pub id: String,
10    pub name: Option<String>,
11    pub email: Option<String>,
12    #[serde(rename = "emailVerified")]
13    pub email_verified: bool,
14    pub image: Option<String>,
15    #[serde(rename = "createdAt")]
16    pub created_at: DateTime<Utc>,
17    #[serde(rename = "updatedAt")]
18    pub updated_at: DateTime<Utc>,
19    #[serde(default)]
20    pub username: Option<String>,
21    #[serde(rename = "displayUsername", default)]
22    pub display_username: Option<String>,
23    #[serde(rename = "twoFactorEnabled", default)]
24    pub two_factor_enabled: Option<bool>,
25    #[serde(default)]
26    pub role: Option<String>,
27    #[serde(default)]
28    pub banned: Option<bool>,
29    #[serde(rename = "banReason", default)]
30    pub ban_reason: Option<String>,
31    #[serde(rename = "banExpires", default)]
32    pub ban_expires: Option<DateTime<Utc>>,
33    #[serde(rename = "isAnonymous", default)]
34    pub is_anonymous: Option<bool>,
35    #[serde(rename = "phoneNumber", default)]
36    pub phone_number: Option<String>,
37    #[serde(rename = "phoneNumberVerified", default)]
38    pub phone_number_verified: Option<bool>,
39    #[serde(rename = "lastLoginMethod", default)]
40    pub last_login_method: Option<String>,
41    /// Nullable fields contributed by an enabled plugin's output schema.
42    #[serde(
43        flatten,
44        default,
45        deserialize_with = "crate::utils::json::deserialize_btree_map"
46    )]
47    pub extension_fields: std::collections::BTreeMap<String, serde_json::Value>,
48    #[serde(skip)]
49    pub metadata: serde_json::Value,
50    /// Fields absent from an authenticated cache projection.
51    #[serde(skip)]
52    pub omitted_fields: std::collections::BTreeSet<String>,
53}
54
55impl Serialize for UserView {
56    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
57        use serde::ser::SerializeMap;
58        let mut map = serializer.serialize_map(None)?;
59        macro_rules! entry {
60            ($name:literal, $value:expr) => {
61                if !self.omitted_fields.contains($name) {
62                    map.serialize_entry($name, $value)?;
63                }
64            };
65        }
66        entry!("id", &self.id);
67        entry!("name", &self.name);
68        entry!("email", &self.email);
69        if let Some(value) = self.extension_fields.get("emailVerified") {
70            entry!("emailVerified", value);
71        } else {
72            entry!("emailVerified", &self.email_verified);
73        }
74        entry!("image", &self.image);
75        entry!(
76            "createdAt",
77            &crate::utils::datetime::json_date_millis(self.created_at.timestamp_millis())
78        );
79        entry!(
80            "updatedAt",
81            &crate::utils::datetime::json_date_millis(self.updated_at.timestamp_millis())
82        );
83        if let Some(value) = &self.username {
84            entry!("username", value);
85        }
86        if let Some(value) = &self.display_username {
87            entry!("displayUsername", value);
88        }
89        if let Some(value) = &self.two_factor_enabled {
90            entry!("twoFactorEnabled", value);
91        }
92        if let Some(value) = &self.role {
93            entry!("role", value);
94        }
95        if let Some(value) = &self.banned {
96            entry!("banned", value);
97        }
98        if let Some(value) = &self.ban_reason {
99            entry!("banReason", value);
100        }
101        if let Some(value) = &self.ban_expires {
102            entry!(
103                "banExpires",
104                &crate::utils::datetime::json_date_millis(value.timestamp_millis())
105            );
106        }
107        if let Some(value) = &self.is_anonymous {
108            entry!("isAnonymous", value);
109        }
110        if let Some(value) = &self.phone_number {
111            entry!("phoneNumber", value);
112        }
113        if let Some(value) = &self.phone_number_verified {
114            entry!("phoneNumberVerified", value);
115        }
116        if let Some(value) = &self.last_login_method {
117            entry!("lastLoginMethod", value);
118        }
119        for (name, value) in &self.extension_fields {
120            if name != "emailVerified" && !self.omitted_fields.contains(name) {
121                map.serialize_entry(name, value)?;
122            }
123        }
124        map.end()
125    }
126}
127
128#[derive(Deserialize)]
129pub(in crate::wire) struct UserViewInput {
130    pub(in crate::wire) id: String,
131    pub(in crate::wire) name: Option<String>,
132    pub(in crate::wire) email: Option<String>,
133    #[serde(rename = "emailVerified")]
134    pub(in crate::wire) email_verified: serde_json::Value,
135    pub(in crate::wire) image: Option<String>,
136    #[serde(rename = "createdAt")]
137    pub(in crate::wire) created_at: DateTime<Utc>,
138    #[serde(rename = "updatedAt")]
139    pub(in crate::wire) updated_at: DateTime<Utc>,
140    #[serde(default)]
141    pub(in crate::wire) username: Option<String>,
142    #[serde(rename = "displayUsername", default)]
143    pub(in crate::wire) display_username: Option<String>,
144    #[serde(rename = "twoFactorEnabled", default)]
145    pub(in crate::wire) two_factor_enabled: Option<bool>,
146    #[serde(default)]
147    pub(in crate::wire) role: Option<String>,
148    #[serde(default)]
149    pub(in crate::wire) banned: Option<bool>,
150    #[serde(rename = "banReason", default)]
151    pub(in crate::wire) ban_reason: Option<String>,
152    #[serde(rename = "banExpires", default)]
153    pub(in crate::wire) ban_expires: Option<DateTime<Utc>>,
154    #[serde(rename = "isAnonymous", default)]
155    pub(in crate::wire) is_anonymous: Option<bool>,
156    #[serde(rename = "phoneNumber", default)]
157    pub(in crate::wire) phone_number: Option<String>,
158    #[serde(rename = "phoneNumberVerified", default)]
159    pub(in crate::wire) phone_number_verified: Option<bool>,
160    #[serde(rename = "lastLoginMethod", default)]
161    pub(in crate::wire) last_login_method: Option<String>,
162    /// Nullable fields contributed by an enabled plugin's output schema.
163    #[serde(
164        flatten,
165        default,
166        deserialize_with = "crate::utils::json::deserialize_btree_map"
167    )]
168    pub(in crate::wire) extension_fields: std::collections::BTreeMap<String, serde_json::Value>,
169    #[serde(skip)]
170    pub(in crate::wire) metadata: serde_json::Value,
171}
172
173impl TryFrom<UserViewInput> for UserView {
174    type Error = &'static str;
175    fn try_from(mut input: UserViewInput) -> Result<Self, Self::Error> {
176        let email_verified = match &input.email_verified {
177            serde_json::Value::Bool(value) => *value,
178            serde_json::Value::String(value) => !value.is_empty(),
179            _ => {
180                return Err("User verification output must be a boolean or retained SQLite string");
181            }
182        };
183        if !input.email_verified.is_boolean() {
184            _ = input
185                .extension_fields
186                .insert("emailVerified".into(), input.email_verified);
187        }
188        Ok(Self {
189            email_verified,
190            extension_fields: input.extension_fields,
191            id: input.id,
192            name: input.name,
193            email: input.email,
194            image: input.image,
195            created_at: input.created_at,
196            updated_at: input.updated_at,
197            username: input.username,
198            display_username: input.display_username,
199            two_factor_enabled: input.two_factor_enabled,
200            role: input.role,
201            banned: input.banned,
202            ban_reason: input.ban_reason,
203            ban_expires: input.ban_expires,
204            is_anonymous: input.is_anonymous,
205            phone_number: input.phone_number,
206            phone_number_verified: input.phone_number_verified,
207            last_login_method: input.last_login_method,
208            metadata: input.metadata,
209            omitted_fields: std::collections::BTreeSet::default(),
210        })
211    }
212}
213
214impl<T: AuthUser> From<&T> for UserView {
215    fn from(user: &T) -> Self {
216        let mut view = Self {
217            id: user.id().into_owned(),
218            name: user.name().map(str::to_owned),
219            email: user.email().map(str::to_owned),
220            email_verified: user.email_verified(),
221            image: user.image().map(str::to_owned),
222            created_at: user.created_at(),
223            updated_at: user.updated_at(),
224            username: user.username().map(str::to_owned),
225            display_username: user.display_username().map(str::to_owned),
226            two_factor_enabled: user.two_factor_enabled_value(),
227            role: user.role().map(str::to_owned),
228            banned: user.banned_value(),
229            ban_reason: user.ban_reason().map(str::to_owned),
230            ban_expires: user.ban_expires(),
231            is_anonymous: user.is_anonymous(),
232            phone_number: user.phone_number().map(str::to_owned),
233            phone_number_verified: user.phone_number_verified(),
234            last_login_method: user.last_login_method().map(str::to_owned),
235            extension_fields: std::collections::BTreeMap::default(),
236            metadata: user.metadata().clone(),
237            omitted_fields: std::collections::BTreeSet::default(),
238        };
239        if let Some(value) = user
240            .adapter_snapshot()
241            .and_then(|output| output.values().get("emailVerified"))
242            && !value.is_boolean()
243        {
244            _ = view
245                .extension_fields
246                .insert("emailVerified".into(), value.clone());
247        }
248        view
249    }
250}
251
252impl AuthUser for UserView {
253    fn retained_user_view(&self) -> Option<&UserView> {
254        Some(self)
255    }
256    fn additional_fields(&self) -> crate::field_policy::FieldOutput {
257        self.extension_fields.clone().into_iter().collect()
258    }
259    fn id(&self) -> Cow<'_, str> {
260        Cow::Borrowed(&self.id)
261    }
262    fn email(&self) -> Option<&str> {
263        self.email.as_deref()
264    }
265    fn name(&self) -> Option<&str> {
266        self.name.as_deref()
267    }
268    fn email_verified(&self) -> bool {
269        self.email_verified
270    }
271    fn image(&self) -> Option<&str> {
272        self.image.as_deref()
273    }
274    fn created_at(&self) -> DateTime<Utc> {
275        self.created_at
276    }
277    fn updated_at(&self) -> DateTime<Utc> {
278        self.updated_at
279    }
280    fn username(&self) -> Option<&str> {
281        self.username.as_deref()
282    }
283    fn display_username(&self) -> Option<&str> {
284        self.display_username.as_deref()
285    }
286    fn two_factor_enabled(&self) -> bool {
287        self.two_factor_enabled.unwrap_or(false)
288    }
289
290    fn two_factor_enabled_value(&self) -> Option<bool> {
291        self.two_factor_enabled
292    }
293    fn role(&self) -> Option<&str> {
294        self.role.as_deref()
295    }
296    fn banned(&self) -> bool {
297        self.banned.unwrap_or(false)
298    }
299
300    fn banned_value(&self) -> Option<bool> {
301        self.banned
302    }
303    fn ban_reason(&self) -> Option<&str> {
304        self.ban_reason.as_deref()
305    }
306    fn ban_expires(&self) -> Option<DateTime<Utc>> {
307        self.ban_expires
308    }
309    fn is_anonymous(&self) -> Option<bool> {
310        self.is_anonymous
311    }
312    fn phone_number(&self) -> Option<&str> {
313        self.phone_number.as_deref()
314    }
315    fn phone_number_verified(&self) -> Option<bool> {
316        self.phone_number_verified
317    }
318    fn last_login_method(&self) -> Option<&str> {
319        self.last_login_method.as_deref()
320    }
321    fn metadata(&self) -> &serde_json::Value {
322        &self.metadata
323    }
324}