use std::{iter::once, str::FromStr};
#[cfg(feature = "axum")]
use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts};
#[cfg(feature = "axum")]
use axum_extra::TypedHeader;
use headers_core::{Error, Header};
use http::{HeaderMap, HeaderName, HeaderValue, Uri};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::util::{iter::IterExt, uri::UriExt};
use super::options::{LocationOptions, SwapOption};
static HX_LOCATION: HeaderName = HeaderName::from_static("hx-location");
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HxLocation {
#[serde(with = "http_serde::uri")]
path: Uri,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
options: Option<LocationOptions>,
}
impl HxLocation {
pub fn new(path: Uri) -> Self {
Self {
path,
options: None,
}
}
pub fn with_options(mut self, options: LocationOptions) -> Self {
self.options = Some(options);
self
}
pub fn path(&self) -> &Uri {
&self.path
}
pub fn source(&self) -> Option<&str> {
self.options.as_ref().and_then(|o| o.source())
}
pub fn event(&self) -> Option<&str> {
self.options.as_ref().and_then(|o| o.event())
}
pub fn handler(&self) -> Option<&str> {
self.options.as_ref().and_then(|o| o.handler())
}
pub fn target(&self) -> Option<&str> {
self.options.as_ref().and_then(|o| o.target())
}
pub fn swap(&self) -> Option<SwapOption> {
self.options.as_ref().and_then(|o| o.swap())
}
pub fn values(&self) -> Option<&Value> {
self.options.as_ref().and_then(|o| o.values())
}
pub fn headers(&self) -> Option<&HeaderMap> {
self.options.as_ref().and_then(|o| o.headers())
}
pub fn select(&self) -> Option<&str> {
self.options.as_ref().and_then(|o| o.select())
}
}
impl From<Uri> for HxLocation {
fn from(path: Uri) -> Self {
Self::new(path)
}
}
#[cfg(feature = "axum")]
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
impl IntoResponseParts for HxLocation {
type Error = <TypedHeader<Self> as IntoResponseParts>::Error;
fn into_response_parts(self, res: ResponseParts) -> Result<ResponseParts, Self::Error> {
TypedHeader(self).into_response_parts(res)
}
}
#[cfg(feature = "axum")]
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
impl IntoResponse for HxLocation {
fn into_response(self) -> Response {
TypedHeader(self).into_response()
}
}
impl Header for HxLocation {
fn name() -> &'static HeaderName {
&HX_LOCATION
}
fn decode<'i, I>(values: &mut I) -> Result<Self, Error>
where
Self: Sized,
I: Iterator<Item = &'i HeaderValue>,
{
let value = values.just_one().ok_or_else(Error::invalid)?;
let json_value: Value =
serde_json::from_slice(value.as_bytes()).map_err(|_| Error::invalid())?;
if json_value.is_string() {
let path = Uri::from_str(json_value.as_str().unwrap()).map_err(|_| Error::invalid())?;
return Ok(Self::new(path));
}
serde_json::from_value(json_value).map_err(|_| Error::invalid())
}
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
if self.options.is_none() {
values.extend(once(
HeaderValue::from_uri(&self.path).expect("invalid value for HX-Location"),
));
} else {
let value = serde_json::to_string(self).expect("invalid value for HX-Location");
values.extend(once(
HeaderValue::from_str(&value).expect("invalid value for HX-Location"),
));
}
}
}