better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
use super::*;
use better_auth_core::field_value::{FieldObjectSource, FieldPropertyDescriptor};
use std::sync::{
    Weak,
    atomic::{AtomicUsize, Ordering},
};

struct Method {
    name: &'static str,
    label: &'static str,
    observer: Observer,
    fields: FieldMap,
    getter_failure: Option<FieldValue>,
    invoke: FieldFunction,
}

struct Properties {
    method: Method,
    receiver: Weak<FieldValue>,
    callback: FieldFunction,
}

impl Method {
    fn bind(self, milliseconds: Option<f64>) -> Arc<FieldValue> {
        Arc::new_cyclic(|receiver: &Weak<FieldValue>| {
            let expected = receiver.clone();
            let observer = self.observer.clone();
            let phase = format!("call:{}:{}", self.name, self.label);
            let invoke = self.invoke.clone();
            let callback = FieldFunction::contextual(move |receiver, arguments| {
                let expected = expected
                    .upgrade()
                    .ok_or_else(|| AuthError::internal("Native method receiver is absent"))?;
                lock(&observer)?.call(&phase, receiver, &expected, arguments)?;
                invoke.call_with(receiver, arguments)
            });
            let source = FieldObject::from_source(Arc::new(Properties {
                method: self,
                receiver: receiver.clone(),
                callback,
            }));
            match milliseconds {
                Some(milliseconds) => {
                    FieldDate::from_milliseconds_with_properties(milliseconds, source).into()
                }
                None => source.into(),
            }
        })
    }
}

impl FieldObjectSource for Properties {
    fn own_keys(&self) -> AuthResult<Vec<String>> {
        Ok(self
            .method
            .fields
            .keys()
            .cloned()
            .chain(std::iter::once(self.method.name.into()))
            .collect())
    }
    fn own_property_descriptor(&self, name: &str) -> AuthResult<Option<FieldPropertyDescriptor>> {
        Ok(if name == self.method.name {
            Some(FieldPropertyDescriptor { enumerable: false })
        } else {
            self.method
                .fields
                .contains_key(name)
                .then_some(FieldPropertyDescriptor { enumerable: true })
        })
    }
    fn get(&self, _: &str) -> AuthResult<Option<FieldValue>> {
        Err(AuthError::internal(
            "Native getter requires its original receiver",
        ))
    }
    fn get_with_receiver(
        &self,
        name: &str,
        receiver: &FieldValue,
    ) -> AuthResult<Option<FieldValue>> {
        if name != self.method.name {
            return Ok(self.method.fields.get(name).cloned());
        }
        let expected = self
            .receiver
            .upgrade()
            .ok_or_else(|| AuthError::internal("Native getter receiver is absent"))?;
        lock(&self.method.observer)?.record(
            &format!("get:{name}:{}", self.method.label),
            [(
                "sameReceiver".into(),
                receiver.strict_equals(&expected).into(),
            )],
        );
        if self.method.name == "getTime" {
            assert_ne!(self.method.label, "excluded");
        }
        if let Some(error) = &self.method.getter_failure {
            return Err(AuthError::from_thrown(error.clone()));
        }
        Ok(Some(self.callback.clone().into()))
    }
    fn set(&self, _: String, _: FieldValue) -> AuthResult<Option<FieldValue>> {
        Err(AuthError::internal(
            "Logout must not mutate projected timestamp properties",
        ))
    }
    fn delete(&self, _: &str) -> AuthResult<Option<FieldValue>> {
        Err(AuthError::internal(
            "Logout must not delete projected timestamp properties",
        ))
    }
    fn snapshot_fields(&self) -> AuthResult<FieldMap> {
        Err(AuthError::internal(
            "Logout timestamp consumption must preserve native property access",
        ))
    }
    fn identity(&self) -> usize {
        std::ptr::from_ref(self) as usize
    }
}

pub(super) fn projected(
    name: &str,
    observer: &Observer,
    original: &FieldValue,
) -> AuthResult<Vec<Arc<FieldValue>>> {
    let calls = Arc::new(AtomicUsize::new(0));
    let conversion_failure: FieldValue = FieldMap::from([
        ("conversionFailure".into(), name.into()),
        ("original".into(), original.clone()),
    ])
    .into();
    ["first", "second", "excluded"]
        .into_iter()
        .zip([0.0, 1.0, 2.0])
        .map(|(label, index)| {
            if name == "builtin-dates" {
                return Ok(Arc::new(date(NOW + index)));
            }
            let number_error = if label == "first" && name == "value-of-error" {
                Some(original.clone())
            } else if label == "first" && name == "later-method-precedes-conversion-error" {
                Some(conversion_failure.clone())
            } else {
                None
            };
            let method_calls = calls.clone();
            let observed = observer.clone();
            let number_result = Method {
                name: "valueOf",
                label,
                observer: observer.clone(),
                fields: FieldMap::new(),
                getter_failure: None,
                invoke: FieldFunction::contextual(move |_, _| {
                    let count = method_calls.load(Ordering::SeqCst);
                    lock(&observed)?.record(
                        &format!("number:{label}"),
                        [("methodCalls".into(), count.into())],
                    );
                    if let Some(error) = &number_error {
                        return Err(AuthError::from_thrown(error.clone()));
                    }
                    Ok(if label == "first" {
                        if count < 2 { 100 } else { 0 }
                    } else {
                        10
                    }
                    .into())
                }),
            }
            .bind(None);
            let method_error = ((label == "first" && name == "get-time-method-error")
                || (label == "second" && name == "later-method-precedes-conversion-error"))
                .then(|| original.clone());
            let getter_failure =
                (label == "first" && name == "get-time-getter-error").then(|| original.clone());
            let deferred = [
                "deferred-number-conversion",
                "value-of-error",
                "later-method-precedes-conversion-error",
            ]
            .contains(&name);
            let nan = name == "nan-stable-order";
            let infinity = name == "infinity-stable-order";
            let method_calls = calls.clone();
            Ok(Method {
                name: "getTime",
                label,
                observer: observer.clone(),
                getter_failure,
                fields: if name == "date-own-get-time" {
                    FieldMap::new()
                } else {
                    FieldMap::from([("label".into(), label.into())])
                },
                invoke: FieldFunction::contextual(move |_, _| {
                    let _ = method_calls.fetch_add(1, Ordering::SeqCst);
                    if let Some(error) = &method_error {
                        return Err(AuthError::from_thrown(error.clone()));
                    }
                    Ok(if deferred {
                        number_result.as_ref().clone()
                    } else if nan {
                        FieldValue::Undefined
                    } else if infinity {
                        f64::INFINITY.into()
                    } else if label == "first" {
                        "5".into()
                    } else {
                        "9".into()
                    })
                }),
            }
            .bind((name == "date-own-get-time").then_some(NOW + index)))
        })
        .collect()
}