use cookie::Cookie;
use http::{HeaderName, HeaderValue, StatusCode};
use std::sync::{Arc, Mutex, MutexGuard};
use vorma_matcher::Params as MatcherParams;
use vorma_tasks::ExecCtx;
use crate::head;
use crate::mux::{None, RequestCtx};
use crate::request::HttpRequest;
use crate::response::{CLIENT_ACCEPTS_REDIRECT_HEADER, ResponseEffects};
#[derive(Clone, Copy, Debug)]
pub struct Params<'a> {
inner: &'a MatcherParams,
}
impl<'a> Params<'a> {
pub fn get(&self, key: &str) -> Option<&'a str> {
self.inner.get(key).map(String::as_str)
}
pub fn iter(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
self.inner
.iter()
.map(|(key, value)| (key.as_str(), value.as_str()))
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
pub struct ResourceCtx<S, E, I, P = ()> {
inner: RequestCtx<S, E, I>,
params: P,
}
impl<S, E, I, P> ResourceCtx<S, E, I, P> {
pub(crate) fn new(inner: RequestCtx<S, E, I>, params: P) -> Self {
Self { inner, params }
}
pub fn state(&self) -> &S {
self.inner.state()
}
pub fn input(&self) -> &I {
self.inner.input()
}
pub fn params(&self) -> &P {
&self.params
}
pub fn param(&self, key: &str) -> &str {
self.inner.param(key).unwrap_or("")
}
pub fn splat_values(&self) -> &[String] {
self.inner.splat_values()
}
pub fn request(&self) -> HttpRequest<'_> {
HttpRequest::new(self.inner.request())
}
pub fn exec_ctx(&self) -> &ExecCtx<E> {
self.inner.exec_ctx()
}
pub fn public_url(&self, src_path: &str) -> crate::Result<String> {
self.inner.public_url(src_path)
}
pub fn response(&self) -> ResponseHandle<'_> {
response_handle_for(&self.inner)
}
}
pub struct ViewCtx<S, E, I, P = ()> {
inner: RequestCtx<S, E, I>,
params: P,
}
impl<S, E, I, P> ViewCtx<S, E, I, P> {
pub(crate) fn new(inner: RequestCtx<S, E, I>, params: P) -> Self {
Self { inner, params }
}
pub fn state(&self) -> &S {
self.inner.state()
}
pub fn input(&self) -> &I {
self.inner.input()
}
pub fn params(&self) -> &P {
&self.params
}
pub fn param(&self, key: &str) -> &str {
self.inner.param(key).unwrap_or("")
}
pub fn splat_values(&self) -> &[String] {
self.inner.splat_values()
}
pub fn request(&self) -> HttpRequest<'_> {
HttpRequest::new(self.inner.request())
}
pub fn exec_ctx(&self) -> &ExecCtx<E> {
self.inner.exec_ctx()
}
pub fn public_url(&self, src_path: &str) -> crate::Result<String> {
self.inner.public_url(src_path)
}
pub fn response(&self) -> ResponseHandle<'_> {
response_handle_for(&self.inner)
}
pub fn head(&self) -> HeadHandle {
head_handle_for(&self.inner)
}
}
pub struct ResponseHandle<'a> {
effects: Arc<Mutex<ResponseEffects>>,
request_headers: &'a http::HeaderMap,
}
impl ResponseHandle<'_> {
pub fn set_status(&mut self, status: StatusCode) -> &mut Self {
self.effects()
.set_status(status, std::option::Option::<String>::None);
self
}
pub fn set_error_status(&mut self, status: StatusCode, text: impl Into<String>) -> &mut Self {
assert!(
status.as_u16() >= 400,
"set_error_status requires an error status, got {status}"
);
self.effects().set_status(status, Some(text.into()));
self
}
pub fn set_header(&mut self, key: HeaderName, value: HeaderValue) -> &mut Self {
self.effects().set_header(key, value);
self
}
pub fn append_header(&mut self, key: HeaderName, value: HeaderValue) -> &mut Self {
self.effects().add_header(key, value);
self
}
pub fn set_cookie(&mut self, cookie: Cookie<'static>) -> &mut Self {
self.effects().set_cookie(cookie);
self
}
pub fn redirect(&mut self, location: impl AsRef<str>) -> Result<&mut Self, String> {
self.redirect_with_status(location, Option::None)
}
pub fn redirect_with_status(
&mut self,
location: impl AsRef<str>,
status: impl Into<Option<StatusCode>>,
) -> Result<&mut Self, String> {
let accepts_client_redirect = accepts_client_redirect(self.request_headers);
self.effects()
.redirect(accepts_client_redirect, location.as_ref(), status.into())
.map_err(|err| err.to_string())?;
Ok(self)
}
fn effects(&self) -> MutexGuard<'_, ResponseEffects> {
self.effects.lock().expect("response effects lock poisoned")
}
}
pub(super) fn accepts_client_redirect(headers: &http::HeaderMap) -> bool {
let Some(value) = headers.get(CLIENT_ACCEPTS_REDIRECT_HEADER) else {
return false;
};
let Ok(value) = value.to_str() else {
return false;
};
matches!(value, "1" | "t" | "T" | "TRUE" | "true" | "True")
}
fn response_handle_for<S, E, I>(inner: &RequestCtx<S, E, I>) -> ResponseHandle<'_> {
let request_headers = inner.request().headers();
ResponseHandle {
effects: inner.response_effects(),
request_headers,
}
}
fn head_handle_for<S, E, I>(inner: &RequestCtx<S, E, I>) -> HeadHandle {
HeadHandle {
effects: inner.response_effects(),
}
}
pub struct HeadHandle {
effects: Arc<Mutex<ResponseEffects>>,
}
impl HeadHandle {
pub fn title(&mut self, title: impl Into<String>) -> &mut Self {
self.effects().head_builder().title(title);
self
}
pub fn description(&mut self, description: impl Into<String>) -> &mut Self {
self.effects().head_builder().description(description);
self
}
pub fn icon(&mut self, href: impl Into<String>) -> &mut Self {
self.effects().head_builder().icon(href);
self
}
pub fn preload(&mut self, href: impl Into<String>, r#as: impl Into<String>) -> &mut Self {
self.effects().head_builder().preload(href, r#as);
self
}
pub fn meta_name_content(
&mut self,
name: impl Into<String>,
content: impl Into<String>,
) -> &mut Self {
self.effects()
.head_builder()
.meta_name_content(name, content);
self
}
pub fn meta_property_content(
&mut self,
property: impl Into<String>,
content: impl Into<String>,
) -> &mut Self {
self.effects()
.head_builder()
.meta_property_content(property, content);
self
}
pub fn meta_charset(&mut self, charset: impl Into<String>) -> &mut Self {
self.effects().head_builder().meta_charset(charset);
self
}
pub fn add(
&mut self,
defs: impl IntoIterator<Item = head::HtmlElementDef>,
) -> Result<&mut Self, String> {
self.effects().head_builder().add(defs)?;
Ok(self)
}
pub fn append(&mut self, other: &head::HeadBuilder) -> &mut Self {
self.effects().head_builder().append(other);
self
}
fn effects(&self) -> MutexGuard<'_, ResponseEffects> {
self.effects.lock().expect("response effects lock poisoned")
}
}
pub struct MiddlewareCtx<S, E = Box<dyn std::error::Error + Send + Sync>> {
inner: RequestCtx<S, E, None>,
}
impl<S, E> MiddlewareCtx<S, E> {
pub(crate) fn new(inner: RequestCtx<S, E, None>) -> Self {
Self { inner }
}
pub fn matched_pattern(&self) -> &str {
self.inner.matched_pattern()
}
pub fn params(&self) -> Params<'_> {
Params {
inner: self.inner.params(),
}
}
pub fn param(&self, key: &str) -> &str {
self.inner.param(key).unwrap_or("")
}
pub fn splat_values(&self) -> &[String] {
self.inner.splat_values()
}
pub fn state(&self) -> &S {
self.inner.state()
}
pub fn exec_ctx(&self) -> &ExecCtx<E> {
self.inner.exec_ctx()
}
pub fn request(&self) -> HttpRequest<'_> {
HttpRequest::new(self.inner.request())
}
pub fn public_url(&self, src_path: &str) -> crate::Result<String> {
self.inner.public_url(src_path)
}
pub fn response(&self) -> ResponseHandle<'_> {
response_handle_for(&self.inner)
}
pub fn head(&self) -> HeadHandle {
head_handle_for(&self.inner)
}
}