use axum::extract::{FromRequestParts, OptionalFromRequestParts};
use axum::http::request::Parts;
use serde::Serialize;
use crate::validation::{ValidationError, Validator};
use crate::{AppState, AuthUser, Error, Result, Session, Toast, Validate};
pub struct LiveContext {
pub(crate) state: AppState,
session: Option<Session>,
user: Option<AuthUser>,
pub(crate) component: &'static str,
pub(crate) toast: Option<Toast>,
pub(crate) redirect: Option<String>,
pub(crate) events: Vec<(String, serde_json::Value)>,
}
impl LiveContext {
pub fn state(&self) -> &AppState {
&self.state
}
pub async fn mount<C: super::LiveComponent>(&self, c: C) -> Result<super::Mounted> {
let id = format!("rx-live-{}", &crate::random_token()[..12]);
super::render(self, &c, id, false).await
}
pub fn session(&self) -> Option<&Session> {
self.session.as_ref()
}
pub fn user(&self) -> Result<&AuthUser> {
self.user.as_ref().ok_or(Error::Unauthorized)
}
pub fn toast(&mut self, toast: Toast) {
self.toast = Some(toast);
}
pub fn redirect(&mut self, url: impl Into<String>) {
self.redirect = Some(url.into());
}
pub fn dispatch(&mut self, name: &str, detail: serde_json::Value) {
self.events
.push((format!("rx:{}:{name}", self.component), detail));
}
pub async fn validate(&self, value: &(impl Validate + Serialize)) -> Result {
let errors = Validator::rules_of(value)
.finish_for(&self.state, self.user.as_ref().map(|u| u.user()))
.await?;
if errors.is_empty() {
Ok(())
} else {
Err(ValidationError::new(errors).with_input(value).into())
}
}
}
impl<S: Send + Sync> FromRequestParts<S> for LiveContext {
type Rejection = Error;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> {
let app = parts
.extensions
.get::<AppState>()
.cloned()
.ok_or_else(|| anyhow::anyhow!("the auth middleware is not installed"))?;
let session = parts.extensions.get::<Session>().cloned();
let user = match <AuthUser as OptionalFromRequestParts<S>>::from_request_parts(parts, state)
.await
{
Ok(user) => user,
Err(never) => match never {},
};
Ok(Self {
state: app,
session,
user,
component: "",
toast: None,
redirect: None,
events: Vec::new(),
})
}
}