use std::{any::Any, sync::Mutex};
use http::{HeaderMap, Method, request::Parts, uri::PathAndQuery};
use topcoat_core::{
context::{ContextValues, RequestContext},
error::Result,
};
use crate::{Body, request::Request};
const REWRITE_LIMIT: usize = 8;
#[must_use]
#[track_caller]
pub fn rewrite(path: impl AsRef<str>, body: impl Into<Body>) -> RewriteError {
RewriteError {
path_and_query: PathAndQuery::try_from(path.as_ref())
.expect("rewrite path is not a valid uri path and query"),
body: Mutex::new(body.into()),
method: None,
headers: None,
context: RequestContext::new(),
}
}
#[derive(Debug)]
pub struct RewriteError {
path_and_query: PathAndQuery,
body: Mutex<Body>,
method: Option<Method>,
headers: Option<HeaderMap>,
context: RequestContext,
}
impl RewriteError {
#[must_use]
pub fn method(mut self, method: Method) -> Self {
self.method = Some(method);
self
}
#[must_use]
pub fn headers(mut self, headers: HeaderMap) -> Self {
self.headers = Some(headers);
self
}
#[must_use]
pub fn with<T>(mut self, value: T) -> Self
where
T: Any + Send + Sync,
{
self.context.insert(value);
self
}
fn into_parts(self) -> RewriteParts {
let body = match self.body.into_inner() {
Ok(body) => body,
Err(poisoned) => poisoned.into_inner(),
};
RewriteParts {
path_and_query: self.path_and_query,
body,
method: self.method,
headers: self.headers,
context: self.context,
}
}
}
struct RewriteParts {
path_and_query: PathAndQuery,
body: Body,
method: Option<Method>,
headers: Option<HeaderMap>,
context: RequestContext,
}
impl std::fmt::Display for RewriteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "rewrite to {}", self.path_and_query)
}
}
impl std::error::Error for RewriteError {}
#[derive(Default)]
pub(crate) struct RewriteChain {
visited: Vec<Dispatch>,
context: RequestContext,
}
impl RewriteChain {
pub(crate) fn carrying(values: impl ContextValues) -> Self {
let mut context = RequestContext::new();
values.install(&mut context);
Self {
visited: Vec::new(),
context,
}
}
pub(crate) fn context(&self) -> &RequestContext {
&self.context
}
pub(crate) fn follow(&mut self, previous: &Parts, rewrite: RewriteError) -> Result<Request> {
let rewrite = rewrite.into_parts();
self.visited.push(Dispatch::of(previous));
let next = Dispatch {
method: rewrite.method.unwrap_or_else(|| previous.method.clone()),
path_and_query: rewrite.path_and_query,
};
if self.visited.contains(&next) {
return Err(RewriteLoopError::cycle(&self.visited, &next).into());
}
if self.visited.len() > REWRITE_LIMIT {
return Err(RewriteLoopError::limit(&self.visited, &next).into());
}
let mut parts = previous.clone();
let mut uri = std::mem::take(&mut parts.uri).into_parts();
uri.path_and_query = Some(next.path_and_query);
parts.uri = http::Uri::from_parts(uri)
.expect("replacing the path of a valid request uri keeps it valid");
parts.method = next.method;
if let Some(headers) = rewrite.headers {
parts.headers = headers;
}
rewrite.context.install(&mut self.context);
Ok(Request::from_parts(parts, rewrite.body))
}
}
#[derive(Debug, PartialEq, Eq)]
struct Dispatch {
method: Method,
path_and_query: PathAndQuery,
}
impl Dispatch {
fn of(parts: &Parts) -> Self {
let path_and_query = parts.uri.path_and_query().cloned().unwrap_or_else(|| {
PathAndQuery::try_from(parts.uri.path())
.expect("the path of a valid request uri is a valid path and query")
});
Self {
method: parts.method.clone(),
path_and_query,
}
}
}
impl std::fmt::Display for Dispatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.method, self.path_and_query)
}
}
#[derive(Debug)]
struct RewriteLoopError {
message: String,
}
impl RewriteLoopError {
fn cycle(visited: &[Dispatch], target: &Dispatch) -> Self {
Self {
message: format!(
"the rewrite to {target} creates a cycle: {} -> {target}",
Self::chain(visited)
),
}
}
fn limit(visited: &[Dispatch], target: &Dispatch) -> Self {
Self {
message: format!(
"the request was rewritten more than {REWRITE_LIMIT} times: {} -> {target}",
Self::chain(visited)
),
}
}
fn chain(visited: &[Dispatch]) -> String {
visited
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(" -> ")
}
}
impl std::fmt::Display for RewriteLoopError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for RewriteLoopError {}