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