use http::HeaderMap;
use serde::Serialize;
use crate::protocol::{PageEnvelope, PartialPageEnvelope};
pub const PAGE_VISIT_HEADER: &str = "x-haven-visit";
pub const PAGE_VERSION_HEADER: &str = "x-haven-version";
pub const PAGE_LOCATION_HEADER: &str = "x-haven-location";
pub const PARTIAL_COMPONENT_HEADER: &str = "x-haven-partial-component";
pub const PARTIAL_ONLY_HEADER: &str = "x-haven-partial-only";
pub const PARTIAL_KIND_HEADER: &str = "x-haven-partial-kind";
pub const PARTIAL_RESPONSE_HEADER: &str = "x-haven-partial";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartialReloadKind {
Props,
Resources,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PartialReloadEntry {
pub kind: PartialReloadKind,
pub key: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PartialReloadRequest {
pub component: String,
pub entries: Vec<PartialReloadEntry>,
}
pub struct PageProtocol(pub PageEnvelope);
pub struct PartialPageProtocol(pub PartialPageEnvelope);
pub struct ValidationErrors(pub PageEnvelope);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Redirect {
pub(crate) status: http::StatusCode,
pub(crate) location: String,
pub(crate) hard: bool,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum BrowserStreamEvent<T> {
Item { data: T },
Done,
Error { message: String },
}
pub fn wants_page_protocol(headers: &HeaderMap) -> bool {
headers
.get(PAGE_VISIT_HEADER)
.and_then(|value| value.to_str().ok())
.map(|value| value.eq_ignore_ascii_case("true"))
.unwrap_or(false)
}
pub fn page_version(headers: &HeaderMap) -> Option<&str> {
headers.get(PAGE_VERSION_HEADER)?.to_str().ok()
}
pub fn partial_reload_request(headers: &HeaderMap) -> Option<PartialReloadRequest> {
if !wants_page_protocol(headers) {
return None;
}
let component = headers.get(PARTIAL_COMPONENT_HEADER)?.to_str().ok()?.trim();
if component.is_empty() {
return None;
}
let only = headers.get(PARTIAL_ONLY_HEADER)?.to_str().ok()?.trim();
let kinds = headers.get(PARTIAL_KIND_HEADER)?.to_str().ok()?.trim();
if only.is_empty() || kinds.is_empty() {
return None;
}
let keys = only
.split(',')
.map(str::trim)
.filter(|value| !value.is_empty());
let kinds = kinds
.split(',')
.map(str::trim)
.filter(|value| !value.is_empty());
let entries = keys
.zip(kinds)
.filter_map(|(key, kind)| {
let kind = match kind {
"props" => PartialReloadKind::Props,
"resources" => PartialReloadKind::Resources,
_ => return None,
};
Some(PartialReloadEntry {
kind,
key: key.to_owned(),
})
})
.collect::<Vec<_>>();
if entries.is_empty() {
return None;
}
Some(PartialReloadRequest {
component: component.to_owned(),
entries,
})
}
impl Redirect {
pub fn to(location: impl Into<String>) -> Self {
Self::see_other(location)
}
pub fn see_other(location: impl Into<String>) -> Self {
Self {
status: http::StatusCode::SEE_OTHER,
location: location.into(),
hard: false,
}
}
pub fn temporary(location: impl Into<String>) -> Self {
Self {
status: http::StatusCode::TEMPORARY_REDIRECT,
location: location.into(),
hard: false,
}
}
pub fn permanent(location: impl Into<String>) -> Self {
Self {
status: http::StatusCode::PERMANENT_REDIRECT,
location: location.into(),
hard: false,
}
}
pub fn hard(location: impl Into<String>) -> Self {
Self {
status: http::StatusCode::CONFLICT,
location: location.into(),
hard: true,
}
}
pub fn location(&self) -> &str {
&self.location
}
}