use tokio::sync::OnceCell;
use crate::{Backend, HashedPassword};
pub trait User: Send {
type Id: Clone + PartialEq + std::fmt::Debug + Send;
fn id(&self) -> &Self::Id;
fn email(&self) -> &str;
fn hashed_password(&self) -> Option<&HashedPassword>;
fn set_hashed_password(&mut self, hashed_password: Option<HashedPassword>) {
let _ = hashed_password;
}
}
pub struct SessionUser<U: User> {
id: Option<U::Id>,
user: OnceCell<U>,
}
impl<U: User> SessionUser<U> {
pub fn new(id: Option<U::Id>) -> Self {
Self {
id,
user: OnceCell::new(),
}
}
pub fn is_authenticated(&self) -> bool {
self.id.is_some()
}
pub fn id(&self) -> Option<&U::Id> {
self.id.as_ref()
}
pub fn set_id(&mut self, id: Option<U::Id>) {
if id != self.id {
self.id = id;
self.user = OnceCell::new();
}
}
pub async fn user<B: Backend<User = U>>(
&self,
backend: &B,
) -> Result<Option<&U>, B::Error> {
enum Error<E> {
UserNotFound,
Inner(E),
}
if let Some(id) = &self.id {
let result = self
.user
.get_or_try_init(async || match backend.load_user(id).await {
Ok(Some(user)) => Ok(user),
Ok(None) => Err(Error::UserNotFound),
Err(e) => Err(Error::Inner(e)),
})
.await;
match result {
Ok(user) => Ok(Some(user)),
Err(Error::UserNotFound) => Ok(None),
Err(Error::Inner(e)) => Err(e),
}
} else {
Ok(None)
}
}
pub async fn user_mut<B: Backend<User = U>>(
&mut self,
backend: &B,
) -> Result<Option<&mut U>, B::Error> {
self.user(backend).await?;
Ok(self.user.get_mut())
}
pub fn get_mut(&mut self) -> Option<&mut U> {
self.user.get_mut()
}
pub fn set_user(&mut self, user: Option<U>) {
self.id = user.as_ref().map(|user| user.id().clone());
self.user = OnceCell::new_with(user);
}
}