Skip to content

Commit 23d2479

Browse files
Add with_header_from_request function
Signed-off-by: Alex Johnson <alex.kattathra.johnson@gmail.com>
1 parent 0168e88 commit 23d2479

File tree

3 files changed

+77
-6
lines changed

3 files changed

+77
-6
lines changed

src/mock.rs

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::diff;
22
use crate::matcher::{Matcher, PathAndQueryMatcher, RequestMatcher};
3-
use crate::response::{Body, Response};
3+
use crate::response::{Body, Header, Response};
44
use crate::server::RemoteMock;
55
use crate::server::State;
66
use crate::Request;
@@ -370,11 +370,47 @@ impl Mock {
370370
self.inner
371371
.response
372372
.headers
373-
.append(field.into_header_name(), value.to_owned());
373+
.append(field.into_header_name(), Header::String(value.to_string()));
374374

375375
self
376376
}
377377

378+
///
379+
/// Sets the headers of the mock response dynamically while exposing the request object.
380+
///
381+
/// You can use this method to provide custom headers for every incoming request.
382+
///
383+
/// The function must be thread-safe. If it's a closure, it can't be borrowing its context.
384+
/// Use `move` closures and `Arc` to share any data.
385+
///
386+
/// ### Example
387+
///
388+
/// ```
389+
/// let mut s = mockito::Server::new();
390+
///
391+
/// let _m = s.mock("GET", mockito::Matcher::Any).with_header_from_request("user", |request| {
392+
/// if request.path() == "/bob" {
393+
/// "bob".into()
394+
/// } else if request.path() == "/alice" {
395+
/// "alice".into()
396+
/// } else {
397+
/// "everyone".into()
398+
/// }
399+
/// });
400+
/// ```
401+
///
402+
pub fn with_header_from_request<T: IntoHeaderName>(
403+
mut self,
404+
field: T,
405+
callback: impl Fn(&Request) -> String + Send + Sync + 'static,
406+
) -> Self {
407+
self.inner.response.headers.append(
408+
field.into_header_name(),
409+
Header::FnWithRequest(Arc::new(move |req| callback(req))),
410+
);
411+
self
412+
}
413+
378414
///
379415
/// Sets the body of the mock response. Its `Content-Length` is handled automatically.
380416
///

src/response.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,40 @@ use tokio::sync::mpsc;
1313
#[derive(Clone, Debug, PartialEq)]
1414
pub(crate) struct Response {
1515
pub status: StatusCode,
16-
pub headers: HeaderMap<String>,
16+
pub headers: HeaderMap<Header>,
1717
pub body: Body,
1818
}
1919

20+
#[derive(Clone)]
21+
pub(crate) enum Header {
22+
String(String),
23+
FnWithRequest(Arc<HeaderFnWithRequest>),
24+
}
25+
26+
impl fmt::Debug for Header {
27+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28+
match *self {
29+
Header::String(ref s) => s.fmt(f),
30+
Header::FnWithRequest(_) => f.write_str("<callback>"),
31+
}
32+
}
33+
}
34+
35+
impl PartialEq for Header {
36+
fn eq(&self, other: &Self) -> bool {
37+
match (self, other) {
38+
(Header::String(ref a), Header::String(ref b)) => a == b,
39+
(Header::FnWithRequest(ref a), Header::FnWithRequest(ref b)) => std::ptr::eq(
40+
a.as_ref() as *const HeaderFnWithRequest as *const u8,
41+
b.as_ref() as *const HeaderFnWithRequest as *const u8,
42+
),
43+
_ => false,
44+
}
45+
}
46+
}
47+
48+
type HeaderFnWithRequest = dyn Fn(&Request) -> String + Send + Sync;
49+
2050
type BodyFnWithWriter = dyn Fn(&mut dyn io::Write) -> io::Result<()> + Send + Sync + 'static;
2151
type BodyFnWithRequest = dyn Fn(&Request) -> Bytes + Send + Sync + 'static;
2252

@@ -57,7 +87,7 @@ impl PartialEq for Body {
5787
impl Default for Response {
5888
fn default() -> Self {
5989
let mut headers = HeaderMap::with_capacity(1);
60-
headers.insert("connection", "close".parse().unwrap());
90+
headers.insert("connection", Header::String("close".to_string()));
6191
Self {
6292
status: StatusCode::OK,
6393
headers,

src/server.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::mock::InnerMock;
22
use crate::request::Request;
3-
use crate::response::{Body as ResponseBody, ChunkedStream};
3+
use crate::response::{Body as ResponseBody, ChunkedStream, Header};
44
use crate::ServerGuard;
55
use crate::{Error, ErrorKind, Matcher, Mock};
66
use bytes::Bytes;
@@ -559,7 +559,12 @@ fn respond_with_mock(request: Request, mock: &RemoteMock) -> Result<Response<Bod
559559
let mut response = Response::builder().status(status);
560560

561561
for (name, value) in mock.inner.response.headers.iter() {
562-
response = response.header(name, value);
562+
match value {
563+
Header::String(value) => response = response.header(name, value),
564+
Header::FnWithRequest(header_fn) => {
565+
response = response.header(name, header_fn(&request))
566+
}
567+
}
563568
}
564569

565570
let body = if request.method() != "HEAD" {

0 commit comments

Comments
 (0)