use std::time::SystemTime;
use bytes::Bytes;
use crate::{
body::Body,
common::{HttpVersion, Method},
date,
headers::{HeaderName, Headers, header_keys},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RequestTarget {
Origin(String),
Absolute(String),
Authority(String),
Asterisk,
}
impl RequestTarget {
pub fn origin(path: impl Into<String>) -> Self {
RequestTarget::Origin(path.into())
}
pub fn as_str(&self) -> &str {
match self {
RequestTarget::Origin(s) | RequestTarget::Absolute(s) | RequestTarget::Authority(s) => {
s.as_str()
}
RequestTarget::Asterisk => "*",
}
}
}
#[derive(Debug, Clone)]
pub struct Request {
method: Method,
target: RequestTarget,
version: HttpVersion,
headers: Headers,
body: Body,
payload: Bytes,
}
impl Request {
pub fn new(method: Method, target: RequestTarget) -> Self {
Self {
method,
target,
version: HttpVersion::HTTP_1_1,
headers: Headers::new(),
body: Body::Empty,
payload: Bytes::new(),
}
}
pub fn builder(method: Method, target: RequestTarget) -> RequestBuilder {
RequestBuilder::new(method, target)
}
pub fn method(&self) -> &Method {
&self.method
}
pub fn target(&self) -> &RequestTarget {
&self.target
}
pub fn version(&self) -> HttpVersion {
self.version
}
pub fn headers(&self) -> &Headers {
&self.headers
}
pub fn header(&self, name: impl Into<HeaderName>) -> Option<&str> {
self.headers.get(name)
}
pub fn contains_header(&self, name: impl Into<HeaderName>) -> bool {
self.headers.contains(name)
}
pub fn body(&self) -> &Body {
&self.body
}
pub fn body_bytes(&self) -> &Bytes {
&self.payload
}
pub fn headers_mut(&mut self) -> &mut Headers {
&mut self.headers
}
pub fn set_version(&mut self, version: HttpVersion) {
self.version = version;
}
pub fn set_body(&mut self, body: Body) {
self.body = body;
}
pub fn set_body_bytes(&mut self, bytes: impl Into<Bytes>) {
self.payload = bytes.into();
}
pub fn into_body_bytes(self) -> Bytes {
self.payload
}
pub fn take_body_bytes(&mut self) -> Bytes {
std::mem::take(&mut self.payload)
}
pub fn expect_100_continue(&self) -> bool {
self.header(header_keys::EXPECT)
.map(|value| {
value
.split(',')
.any(|token| token.trim().eq_ignore_ascii_case("100-continue"))
})
.unwrap_or(false)
}
pub fn wants_close(&self) -> bool {
self.header(header_keys::CONNECTION).map_or(false, |value| {
value
.split(',')
.any(|token| token.trim().eq_ignore_ascii_case("close"))
})
}
pub fn if_none_match(&self) -> Option<&str> {
self.header(header_keys::IF_NONE_MATCH)
.map(|value| value.trim())
}
pub fn if_match(&self) -> Option<&str> {
self.header(header_keys::IF_MATCH).map(|value| value.trim())
}
pub fn if_unmodified_since(&self) -> Option<SystemTime> {
self.header(header_keys::IF_UNMODIFIED_SINCE)
.and_then(|value| date::parse_http_date(value.trim()))
}
pub fn if_modified_since(&self) -> Option<SystemTime> {
self.header(header_keys::IF_MODIFIED_SINCE)
.and_then(|value| date::parse_http_date(value.trim()))
}
}
#[derive(Debug, Clone)]
pub struct RequestBuilder {
method: Method,
target: RequestTarget,
version: HttpVersion,
headers: Headers,
body: Body,
payload: Bytes,
}
impl RequestBuilder {
fn new(method: Method, target: RequestTarget) -> Self {
Self {
method,
target,
version: HttpVersion::HTTP_1_1,
headers: Headers::new(),
body: Body::Empty,
payload: Bytes::new(),
}
}
pub fn version(mut self, version: HttpVersion) -> Self {
self.version = version;
self
}
pub fn header(mut self, name: impl Into<HeaderName>, value: impl Into<String>) -> Self {
self.headers.insert(name, value);
self
}
pub fn body(mut self, body: Body) -> Self {
self.body = body;
self
}
pub fn body_bytes(mut self, bytes: impl Into<Bytes>) -> Self {
self.payload = bytes.into();
self
}
pub fn build(self) -> Request {
Request {
method: self.method,
target: self.target,
version: self.version,
headers: self.headers,
body: self.body,
payload: self.payload,
}
}
}