137 lines
4.7 KiB
Rust
137 lines
4.7 KiB
Rust
|
|
use std::fmt;
|
||
|
|
|
||
|
|
use http::{HeaderMap, Method};
|
||
|
|
use http_body_util::BodyExt;
|
||
|
|
use serde::de::DeserializeOwned;
|
||
|
|
use url::Url;
|
||
|
|
|
||
|
|
pub const BODY_PRINT_LIMIT: usize = 10_000;
|
||
|
|
|
||
|
|
/// Specifies limitations on printing request bodies when logging requests. For some mock servers
|
||
|
|
/// the bodies may be too large to reasonably print and it may be desirable to limit them.
|
||
|
|
#[derive(Debug, Copy, Clone)]
|
||
|
|
pub enum BodyPrintLimit {
|
||
|
|
/// Maximum length of a body to print in bytes.
|
||
|
|
Limited(usize),
|
||
|
|
/// There is no limit to the size of a body that may be printed.
|
||
|
|
Unlimited,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// An incoming request to an instance of [`MockServer`].
|
||
|
|
///
|
||
|
|
/// Each matcher gets an immutable reference to a `Request` instance in the [`matches`] method
|
||
|
|
/// defined in the [`Match`] trait.
|
||
|
|
///
|
||
|
|
/// [`MockServer`]: crate::MockServer
|
||
|
|
/// [`matches`]: crate::Match::matches
|
||
|
|
/// [`Match`]: crate::Match
|
||
|
|
///
|
||
|
|
/// ### Implementation notes:
|
||
|
|
/// We can't use `http_types::Request` directly in our `Match::matches` signature:
|
||
|
|
/// it requires having mutable access to the request to extract the body (which gets
|
||
|
|
/// consumed when read!).
|
||
|
|
/// It would also require `matches` to be async, which is cumbersome due to the lack of async traits.
|
||
|
|
///
|
||
|
|
/// We introduce our `Request` type to perform this extraction once when the request
|
||
|
|
/// arrives in the mock serve, store the result and pass an immutable reference to it
|
||
|
|
/// to all our matchers.
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct Request {
|
||
|
|
pub url: Url,
|
||
|
|
pub method: Method,
|
||
|
|
pub headers: HeaderMap,
|
||
|
|
pub body: Vec<u8>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Request {
|
||
|
|
pub fn body_json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
|
||
|
|
serde_json::from_slice(&self.body)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(crate) async fn from_hyper(request: hyper::Request<hyper::body::Incoming>) -> Request {
|
||
|
|
let (parts, body) = request.into_parts();
|
||
|
|
let url = match parts.uri.authority() {
|
||
|
|
Some(_) => parts.uri.to_string(),
|
||
|
|
None => format!("http://localhost{}", parts.uri),
|
||
|
|
}
|
||
|
|
.parse()
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
let body = body
|
||
|
|
.collect()
|
||
|
|
.await
|
||
|
|
.expect("Failed to read request body.")
|
||
|
|
.to_bytes();
|
||
|
|
|
||
|
|
Self {
|
||
|
|
url,
|
||
|
|
method: parts.method,
|
||
|
|
headers: parts.headers,
|
||
|
|
body: body.to_vec(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(crate) fn print_with_limit(
|
||
|
|
&self,
|
||
|
|
mut buffer: impl fmt::Write,
|
||
|
|
body_print_limit: BodyPrintLimit,
|
||
|
|
) -> fmt::Result {
|
||
|
|
writeln!(buffer, "{} {}", self.method, self.url)?;
|
||
|
|
for name in self.headers.keys() {
|
||
|
|
let values = self
|
||
|
|
.headers
|
||
|
|
.get_all(name)
|
||
|
|
.iter()
|
||
|
|
.map(|value| String::from_utf8_lossy(value.as_bytes()))
|
||
|
|
.collect::<Vec<_>>();
|
||
|
|
let values = values.join(",");
|
||
|
|
writeln!(buffer, "{}: {}", name, values)?;
|
||
|
|
}
|
||
|
|
|
||
|
|
match body_print_limit {
|
||
|
|
BodyPrintLimit::Limited(limit) if self.body.len() > limit => {
|
||
|
|
let mut written = false;
|
||
|
|
for end_byte in limit..(limit + 4).max(self.body.len()) {
|
||
|
|
if let Ok(truncated) = std::str::from_utf8(&self.body[..end_byte]) {
|
||
|
|
written = true;
|
||
|
|
writeln!(buffer, "{}", truncated)?;
|
||
|
|
if end_byte < self.body.len() {
|
||
|
|
writeln!(
|
||
|
|
buffer,
|
||
|
|
"We truncated the body because it was too large: {} bytes (limit: {} bytes)",
|
||
|
|
self.body.len(),
|
||
|
|
limit
|
||
|
|
)?;
|
||
|
|
writeln!(
|
||
|
|
buffer,
|
||
|
|
"Increase this limit by setting `WIREMOCK_BODY_PRINT_LIMIT`, or calling `MockServerBuilder::body_print_limit` when building your MockServer instance"
|
||
|
|
)?;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !written {
|
||
|
|
writeln!(
|
||
|
|
buffer,
|
||
|
|
"Body is likely binary (invalid utf-8) size is {} bytes",
|
||
|
|
self.body.len()
|
||
|
|
)
|
||
|
|
} else {
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
if let Ok(body) = std::str::from_utf8(&self.body) {
|
||
|
|
writeln!(buffer, "{}", body)
|
||
|
|
} else {
|
||
|
|
writeln!(
|
||
|
|
buffer,
|
||
|
|
"Body is likely binary (invalid utf-8) size is {} bytes",
|
||
|
|
self.body.len()
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|