Skip to main content

alibi_core/
adapter_record.rs

1//! Retained adapter output alongside immutable, typed storage authority.
2
3use crate::{AuthAccount, AuthResult, AuthSession, AuthUser, field_policy::FieldOutput};
4use chrono::{DateTime, Utc};
5use serde::{Serialize, Serializer};
6use std::borrow::Cow;
7
8/// Declared adapter values retain JavaScript undefined presence for trusted
9/// observers. Serialization omits undefined values, as public JSON does.
10#[derive(Clone, Debug, Default, Serialize)]
11#[serde(transparent)]
12pub struct AdapterOutput {
13    values: FieldOutput,
14    #[serde(skip)]
15    undefined_fields: std::collections::BTreeSet<String>,
16}
17
18impl AdapterOutput {
19    pub(crate) fn from_values(values: FieldOutput) -> Self {
20        Self {
21            values,
22            undefined_fields: std::collections::BTreeSet::new(),
23        }
24    }
25
26    pub(crate) fn insert(&mut self, name: String, value: Option<serde_json::Value>) {
27        match value {
28            Some(value) => {
29                let _ignored_previous = self.undefined_fields.remove(&name);
30                drop(self.values.insert(name, value));
31            }
32            None => {
33                drop(self.values.remove(&name));
34                let _ignored_clone = self.undefined_fields.insert(name);
35            }
36        }
37    }
38
39    pub(crate) fn extend(&mut self, output: Self) {
40        for (name, value) in output.values {
41            self.insert(name, Some(value));
42        }
43        for name in output.undefined_fields {
44            self.insert(name, None);
45        }
46    }
47
48    /// Whether this is an own declared output property, including undefined.
49    #[must_use]
50    pub fn contains_field(&self, name: &str) -> bool {
51        self.values.contains_key(name) || self.undefined_fields.contains(name)
52    }
53
54    #[must_use]
55    pub fn field_is_undefined(&self, name: &str) -> bool {
56        self.undefined_fields.contains(name)
57    }
58
59    /// JSON-representable values. Undefined remains observable through the
60    /// presence accessors and is intentionally absent from this map.
61    #[must_use]
62    pub const fn values(&self) -> &FieldOutput {
63        &self.values
64    }
65
66    pub(crate) fn filter_returned(&self, fields: &crate::field_policy::FieldConfigs) -> Self {
67        let mut output = self.clone();
68        for (name, field) in fields {
69            if !field.returned {
70                drop(output.values.remove(name));
71                let _removed = output.undefined_fields.remove(name);
72            }
73        }
74        output
75    }
76}
77
78/// One actual adapter result. Output transforms may change additional values or
79/// omit them, while identity, ownership and credential accessors retain the
80/// physical model. Public response filtering is a separate operation.
81#[derive(Clone, Debug)]
82pub struct AdapterRecord<M> {
83    stored: M,
84    output: AdapterOutput,
85}
86
87impl<M: Serialize> AdapterRecord<M> {
88    /// Retain the model's honest serialized snapshot without applying policies.
89    /// Initialized stores replace this with their declared adapter output.
90    ///
91    /// # Errors
92    /// Returns an error if the model cannot serialize to an object.
93    pub fn physical(stored: M) -> AuthResult<Self> {
94        let serde_json::Value::Object(output) = serde_json::to_value(&stored)? else {
95            return Err(crate::AuthError::internal(
96                "An adapter model must serialize to an object",
97            ));
98        };
99        Ok(Self {
100            stored,
101            output: AdapterOutput::from_values(output),
102        })
103    }
104}
105
106impl<M> AdapterRecord<M> {
107    pub(crate) fn with_output(stored: M, output: AdapterOutput) -> Self {
108        Self { stored, output }
109    }
110
111    pub(crate) fn retain_provider_verification(&mut self, value: serde_json::Value) {
112        self.output.insert("emailVerified".into(), Some(value));
113    }
114
115    #[must_use]
116    pub const fn stored(&self) -> &M {
117        &self.stored
118    }
119
120    #[must_use]
121    pub fn into_stored(self) -> M {
122        self.stored
123    }
124
125    /// Trusted adapter output, including declared `returned: false` fields.
126    /// This snapshot is immutable and must not be used as storage authority.
127    #[must_use]
128    pub const fn raw_snapshot(&self) -> &AdapterOutput {
129        &self.output
130    }
131}
132
133impl<M> Serialize for AdapterRecord<M> {
134    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
135        self.output.serialize(serializer)
136    }
137}
138
139macro_rules! delegate {
140    ($name:ident, $output:ty) => {
141        fn $name(&self) -> $output {
142            self.stored.$name()
143        }
144    };
145}
146
147impl<M: AuthUser> AuthUser for AdapterRecord<M> {
148    fn adapter_snapshot(&self) -> Option<&AdapterOutput> {
149        Some(&self.output)
150    }
151    delegate!(additional_fields, FieldOutput);
152    delegate!(id, Cow<'_, str>);
153    delegate!(email, Option<&str>);
154    delegate!(name, Option<&str>);
155    delegate!(email_verified, bool);
156    delegate!(image, Option<&str>);
157    delegate!(created_at, DateTime<Utc>);
158    delegate!(updated_at, DateTime<Utc>);
159    delegate!(username, Option<&str>);
160    delegate!(display_username, Option<&str>);
161    delegate!(two_factor_enabled, bool);
162    delegate!(two_factor_enabled_value, Option<bool>);
163    delegate!(role, Option<&str>);
164    delegate!(banned, bool);
165    delegate!(banned_value, Option<bool>);
166    delegate!(ban_reason, Option<&str>);
167    delegate!(ban_expires, Option<DateTime<Utc>>);
168    delegate!(metadata, &serde_json::Value);
169    delegate!(is_anonymous, Option<bool>);
170    delegate!(phone_number, Option<&str>);
171    delegate!(phone_number_verified, Option<bool>);
172    delegate!(last_login_method, Option<&str>);
173}
174
175impl<M: AuthSession> AuthSession for AdapterRecord<M> {
176    fn adapter_snapshot(&self) -> Option<&AdapterOutput> {
177        Some(&self.output)
178    }
179    delegate!(additional_fields, FieldOutput);
180    delegate!(id, Cow<'_, str>);
181    delegate!(expires_at, DateTime<Utc>);
182    delegate!(token, &str);
183    delegate!(created_at, DateTime<Utc>);
184    delegate!(updated_at, DateTime<Utc>);
185    delegate!(ip_address, Option<&str>);
186    delegate!(user_agent, Option<&str>);
187    delegate!(user_id, Cow<'_, str>);
188    delegate!(impersonated_by, Option<&str>);
189    delegate!(active_organization_id, Option<&str>);
190    delegate!(active_team_id, Option<&str>);
191    delegate!(active, bool);
192}
193
194impl<M: AuthAccount> AuthAccount for AdapterRecord<M> {
195    fn adapter_snapshot(&self) -> Option<&AdapterOutput> {
196        Some(&self.output)
197    }
198    delegate!(additional_fields, FieldOutput);
199    delegate!(id, Cow<'_, str>);
200    delegate!(account_id, &str);
201    delegate!(provider_id, &str);
202    delegate!(user_id, Cow<'_, str>);
203    delegate!(access_token, Option<&str>);
204    delegate!(refresh_token, Option<&str>);
205    delegate!(id_token, Option<&str>);
206    delegate!(access_token_expires_at, Option<DateTime<Utc>>);
207    delegate!(refresh_token_expires_at, Option<DateTime<Utc>>);
208    delegate!(scope, Option<&str>);
209    delegate!(password, Option<&str>);
210    delegate!(created_at, DateTime<Utc>);
211    delegate!(updated_at, DateTime<Utc>);
212}