use http::HeaderValue;
use http::response::Parts;
use serde::Serialize;
use serde_json::Value;
use topcoat_core::{context::Cx, error::Result};
use topcoat_router::IntoResponseParts;
use crate::SwapOption;
use crate::header;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HxLocation {
pub path: String,
pub options: LocationOptions,
}
impl HxLocation {
#[must_use]
pub fn new(path: impl Into<String>) -> Self {
Self {
path: path.into(),
options: LocationOptions::default(),
}
}
#[must_use]
pub fn source(mut self, source: impl Into<String>) -> Self {
self.options.source = Some(source.into());
self
}
#[must_use]
pub fn event(mut self, event: impl Into<String>) -> Self {
self.options.event = Some(event.into());
self
}
#[must_use]
pub fn handler(mut self, handler: impl Into<String>) -> Self {
self.options.handler = Some(handler.into());
self
}
#[must_use]
pub fn target(mut self, target: impl Into<String>) -> Self {
self.options.target = Some(target.into());
self
}
#[must_use]
pub fn swap(mut self, swap: SwapOption) -> Self {
self.options.swap = Some(swap);
self
}
#[must_use]
pub fn select(mut self, select: impl Into<String>) -> Self {
self.options.select = Some(select.into());
self
}
#[must_use]
pub fn values(mut self, values: Value) -> Self {
self.options.values = Some(values);
self
}
#[must_use]
pub fn headers(mut self, headers: Value) -> Self {
self.options.headers = Some(headers);
self
}
fn header_value(self) -> Result<HeaderValue> {
if self.options.is_empty() {
return Ok(HeaderValue::from_str(&self.path)?);
}
let mut object = match serde_json::to_value(&self.options)? {
Value::Object(map) => map,
_ => serde_json::Map::new(),
};
object.insert("path".to_owned(), Value::String(self.path));
Ok(HeaderValue::from_str(&serde_json::to_string(&object)?)?)
}
}
impl From<&str> for HxLocation {
fn from(path: &str) -> Self {
Self::new(path)
}
}
impl From<String> for HxLocation {
fn from(path: String) -> Self {
Self::new(path)
}
}
impl IntoResponseParts for HxLocation {
fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
parts
.headers
.insert(header::HX_LOCATION, self.header_value()?);
Ok(())
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct LocationOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handler: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub swap: Option<SwapOption>,
#[serde(skip_serializing_if = "Option::is_none")]
pub select: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub values: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<Value>,
}
impl LocationOptions {
fn is_empty(&self) -> bool {
*self == Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn header_value(location: HxLocation) -> String {
let mut parts = http::Response::new(()).into_parts().0;
location
.into_response_parts(&Cx::default(), &mut parts)
.unwrap();
parts
.headers
.get(header::HX_LOCATION)
.unwrap()
.to_str()
.unwrap()
.to_owned()
}
#[test]
fn plain_path_when_no_options() {
assert_eq!(header_value(HxLocation::new("/home")), "/home");
}
#[test]
fn options_serialize_as_json_with_path() {
let value = header_value(
HxLocation::new("/home")
.target("#main")
.swap(SwapOption::InnerHtml),
);
let json: Value = serde_json::from_str(&value).unwrap();
assert_eq!(json["path"], "/home");
assert_eq!(json["target"], "#main");
assert_eq!(json["swap"], "innerHTML");
assert!(json.get("source").is_none());
}
}