use crate::primitives::header_block::HeaderBlock;
use crate::primitives::method::Method;
use crate::primitives::request_target::{RequestTarget, RequestTargetError};
use crate::primitives::version::HttpVersion;
#[derive(Debug)]
pub enum RequestHeadError {
Target(RequestTargetError),
Version(crate::primitives::version::HttpVersionError),
HeaderName(crate::primitives::header_block::HeaderError),
HeaderValue(crate::primitives::header_block::HeaderError),
AbsoluteForm,
}
impl std::fmt::Display for RequestHeadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Target(e) => write!(f, "invalid request target: {e}"),
Self::Version(e) => write!(f, "unsupported HTTP version: {e}"),
Self::HeaderName(e) => write!(f, "invalid header name: {e}"),
Self::HeaderValue(e) => write!(f, "invalid header value: {e}"),
Self::AbsoluteForm => write!(f, "absolute-form URI not supported"),
}
}
}
impl std::error::Error for RequestHeadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Target(e) => Some(e),
Self::Version(e) => Some(e),
Self::HeaderName(e) => Some(e),
Self::HeaderValue(e) => Some(e),
Self::AbsoluteForm => None,
}
}
}
impl From<RequestTargetError> for RequestHeadError {
fn from(e: RequestTargetError) -> Self {
Self::Target(e)
}
}
impl From<crate::primitives::version::HttpVersionError> for RequestHeadError {
fn from(e: crate::primitives::version::HttpVersionError) -> Self {
Self::Version(e)
}
}
#[derive(Debug, Clone)]
pub struct RequestHead {
method: Method,
target: RequestTarget,
version: HttpVersion,
headers: HeaderBlock,
}
impl RequestHead {
pub fn new(
method: Method,
target: RequestTarget,
version: HttpVersion,
headers: HeaderBlock,
) -> Self {
Self {
method,
target,
version,
headers,
}
}
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) -> &HeaderBlock {
&self.headers
}
pub fn is_head(&self) -> bool {
self.method.is_head()
}
pub fn is_get(&self) -> bool {
self.method.is_get()
}
pub fn permits_static_resolution(&self) -> bool {
self.method.permits_static_resolution()
}
pub fn try_from_hyper<B>(req: &hyper::Request<B>) -> Result<Self, RequestHeadError> {
let method =
Method::new(req.method().as_str()).map_err(|_| RequestHeadError::AbsoluteForm)?;
let uri = req.uri();
if uri.authority().is_some() {
return Err(RequestHeadError::AbsoluteForm);
}
let target_str = uri.to_string();
let target = RequestTarget::parse(target_str)?;
let version = HttpVersion::from(&req.version());
let mut headers = HeaderBlock::with_capacity(req.headers().len());
for (name, value) in req.headers().iter() {
let header_name = crate::primitives::header_block::HeaderName::new(name.as_str())
.map_err(RequestHeadError::HeaderName)?;
let value_str = value.to_str().unwrap_or("").to_string();
let header_value = crate::primitives::header_block::HeaderValue::new(value_str)
.map_err(RequestHeadError::HeaderValue)?;
headers.push(header_name, header_value);
}
Ok(Self::new(method, target, version, headers))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::header_block::HeaderBlock;
use crate::primitives::method::Method;
use crate::primitives::request_target::RequestTarget;
use crate::primitives::version::HttpVersion;
fn make_head(method: &str, target: &str) -> RequestHead {
RequestHead::new(
Method::new(method).unwrap(),
RequestTarget::parse(target).unwrap(),
HttpVersion::Http11,
HeaderBlock::new(),
)
}
#[test]
fn basic_construction() {
let head = make_head("GET", "/");
assert_eq!(head.method().as_str(), "GET");
assert_eq!(head.target().path(), "/");
assert_eq!(head.version(), HttpVersion::Http11);
assert!(head.headers().is_empty());
}
#[test]
fn is_head() {
let head = make_head("HEAD", "/foo");
assert!(head.is_head());
assert!(!head.is_get());
}
#[test]
fn is_get() {
let head = make_head("GET", "/foo");
assert!(head.is_get());
assert!(!head.is_head());
}
#[test]
fn permits_static_resolution() {
let head = make_head("GET", "/");
assert!(head.permits_static_resolution());
let head = make_head("HEAD", "/");
assert!(head.permits_static_resolution());
let head = make_head("POST", "/");
assert!(!head.permits_static_resolution());
}
#[test]
fn with_headers() {
let mut headers = HeaderBlock::new();
headers.push_str("content-type", "text/html").unwrap();
let head = RequestHead::new(
Method::get(),
RequestTarget::parse("/").unwrap(),
HttpVersion::Http11,
headers,
);
assert!(head.headers().contains("content-type"));
}
#[test]
fn clone_preserves_values() {
let head = make_head("GET", "/foo?bar");
let cloned = head.clone();
assert_eq!(cloned.method().as_str(), "GET");
assert_eq!(cloned.target().path(), "/foo");
assert_eq!(cloned.target().query(), Some("bar"));
}
}