Skip to main content

better_auth_core/user_fields/
transform.rs

1use crate::FieldValue as Value;
2use crate::{AuthError, AuthResult};
3use std::{future::Future, pin::Pin, sync::Arc};
4
5type TransformResult = AuthResult<Value>;
6type TransformFuture = Pin<Box<dyn Future<Output = TransformResult> + Send>>;
7
8/// A field callback. Undefined, null, and native object values remain distinct.
9#[derive(Clone)]
10pub struct UserFieldTransform(Callback);
11
12#[derive(Clone)]
13enum Callback {
14    Sync(Arc<dyn Fn(Value) -> TransformResult + Send + Sync>),
15    Async(Arc<dyn Fn(Value) -> TransformFuture + Send + Sync>),
16}
17
18impl UserFieldTransform {
19    /// Use a synchronous callback at public-input and adapter boundaries.
20    pub fn new(callback: impl Fn(Value) -> TransformResult + Send + Sync + 'static) -> Self {
21        Self(Callback::Sync(Arc::new(callback)))
22    }
23
24    /// Await a callback at core, Organization, and supported plugin adapter boundaries.
25    /// Synchronous public-input parsing rejects async callbacks.
26    pub fn new_async<F, Fut>(callback: F) -> Self
27    where
28        F: Fn(Value) -> Fut + Send + Sync + 'static,
29        Fut: Future<Output = TransformResult> + Send + 'static,
30    {
31        Self(Callback::Async(Arc::new(move |value| {
32            Box::pin(callback(value))
33        })))
34    }
35
36    /// Invoke a callback and await its result without changing its original error.
37    pub async fn call(&self, value: Value) -> TransformResult {
38        match &self.0 {
39            Callback::Sync(callback) => callback(value),
40            Callback::Async(callback) => callback(value).await,
41        }
42    }
43
44    /// Invoke a callback at a synchronous policy boundary.
45    /// Async callbacks return a configuration error before application work starts.
46    pub fn call_sync(&self, value: Value) -> TransformResult {
47        match &self.0 {
48            Callback::Sync(callback) => callback(value),
49            Callback::Async(_) => Err(AuthError::config(
50                "Async field transforms require an adapter boundary; public-input parsing is synchronous",
51            )),
52        }
53    }
54
55    pub(crate) fn is_async(&self) -> bool {
56        matches!(self.0, Callback::Async(_))
57    }
58}