#[macro_export]
macro_rules! status {
($status:expr) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::empty()
)
};
($status:expr ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::empty();
[ $( $header ),* ]
)
};
($status:expr, text: $body:expr) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$body.into();
[ $crate::headers::ContentType::text_utf_8() ]
)
};
($status:expr, text: $body:expr ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$body.into();
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
($status:expr, fmt: $fmt:literal) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt));
[ $crate::headers::ContentType::text_utf_8() ]
)
};
($status:expr, fmt: $fmt:literal ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
($status:expr, fmt: $fmt:literal, $( $arg:expr ),+ $(,)? ) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[ $crate::headers::ContentType::text_utf_8() ]
)
};
($status:expr, fmt: $fmt:literal, $( $arg:expr ),+ $(,)? ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
($status:expr, json: $body:expr) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
body;
[ $crate::headers::ContentType::json() ]
),
Err(err) => Err(err),
}
}};
($status:expr, json: $body:expr ; [ $( $header:expr ),* $(,)? ]) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
body;
[
$crate::headers::ContentType::json(),
$( $header ),*
]
),
Err(err) => Err(err),
}
}};
($status:expr, { $($json:tt)* }) => {{
match $crate::HttpBody::json($crate::json::json_internal!({ $($json)* })) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
body;
[ $crate::headers::ContentType::json() ]
),
Err(err) => Err(err),
}
}};
($status:expr, { $($json:tt)* } ; [ $( $header:expr ),* $(,)? ]) => {{
match $crate::HttpBody::json($crate::json::json_internal!({ $($json)* })) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
body;
[
$crate::headers::ContentType::json(),
$( $header ),*
]
),
Err(err) => Err(err),
}
}};
($status:expr, $fmt:literal) => {{
const __S: &str = $fmt;
if $crate::utils::str::memchr_contains(b'{', __S.as_bytes()) {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt));
[ $crate::headers::ContentType::text_utf_8() ]
)
} else {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::text(__S);
[ $crate::headers::ContentType::text_utf_8() ]
)
}
}};
($status:expr, $fmt:literal ; [ $( $header:expr ),* $(,)? ]) => {{
const __S: &str = $fmt;
if $crate::utils::str::memchr_contains(b'{', __S.as_bytes()) {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
} else {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::text(__S);
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
}
}};
($status:expr, $fmt:literal, $( $arg:expr ),+ $(,)? ) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[ $crate::headers::ContentType::text_utf_8() ]
)
};
($status:expr, $fmt:literal, $( $arg:expr ),+ $(,)? ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
($status:expr, $body:expr) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
body;
[ $crate::headers::ContentType::json() ]
),
Err(err) => Err(err),
}
}};
($status:expr, $body:expr ; [ $( $header:expr ),* $(,)? ]) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::from_u16($status).unwrap_or($crate::http::StatusCode::OK),
body;
[
$crate::headers::ContentType::json(),
$( $header ),*
]
),
Err(err) => Err(err),
}
}};
}
#[cfg(test)]
mod tests {
use http_body_util::BodyExt;
use serde::Serialize;
#[derive(Serialize)]
struct TestPayload {
name: String,
}
#[tokio::test]
async fn it_creates_200_response() {
let response = status!(200);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(body.len(), 0);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_200_with_text_response() {
let text = "test";
let response = status!(200, text);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "\"test\"");
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_200_with_json_response() {
let payload = TestPayload {
name: "test".into(),
};
let response = status!(200, payload);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_anonymous_type_200_response_with_json_body() {
let response = status!(200, { "name": "test" });
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_empty_401_response() {
let response = status!(401);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(body.len(), 0);
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_401_response_with_text_body() {
let response = status!(401, "You are not authorized!");
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "You are not authorized!");
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_401_response_with_interpolated_text_body() {
let name = "John";
let response = status!(401, "{} is not authorized!", name);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "John is not authorized!");
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_401_response_with_formatted_text_body() {
let name = "John";
let response = status!(401, "{name} is not authorized!");
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "John is not authorized!");
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_401_response_with_json_body() {
let payload = TestPayload {
name: "test".into(),
};
let response = status!(401, payload);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_anonymous_type_401_response_with_json_body() {
let response = status!(401, { "name": "test" });
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_empty_403_response() {
let response = status!(403);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(body.len(), 0);
assert_eq!(response.status(), 403);
}
#[tokio::test]
async fn it_creates_403_response_with_text_body() {
let response = status!(403, "It's forbidden!");
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "It's forbidden!");
assert_eq!(response.status(), 403);
}
#[tokio::test]
async fn it_creates_403_response_with_json_body() {
let payload = TestPayload {
name: "test".into(),
};
let response = status!(403, payload);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(response.status(), 403);
}
#[tokio::test]
async fn it_creates_anonymous_type_403_response_with_json_body() {
let response = status!(403, { "name": "test" });
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(response.status(), 403);
}
#[tokio::test]
async fn it_creates_empty_status_response_with_headers() {
let response = status!(400; [
("x-api-key", "some api key"),
("x-req-id", "some req id"),
]);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(body.len(), 0);
assert_eq!(response.status(), 400);
assert_eq!(response.headers().get("x-api-key").unwrap(), "some api key");
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
}
#[tokio::test]
async fn it_creates_empty_status_response_with_body_and_headers() {
let payload = TestPayload {
name: "test".into(),
};
let response = status!(406, payload; [
("x-api-key", "some api key"),
("x-req-id", "some req id"),
]);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(response.status(), 406);
assert_eq!(response.headers().get("x-api-key").unwrap(), "some api key");
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
}
#[tokio::test]
async fn it_sets_content_type_for_text_sugar() {
let response = status!(401, "Unauthorized!");
assert!(response.is_ok());
let response = response.unwrap();
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
}
#[tokio::test]
async fn it_sets_content_type_for_json_typed() {
let payload = TestPayload {
name: "test".into(),
};
let response = status!(401, payload);
assert!(response.is_ok());
let response = response.unwrap();
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
}
#[tokio::test]
async fn it_sets_content_type_for_json_inline_object() {
let response = status!(401, { "name": "test" });
assert!(response.is_ok());
let response = response.unwrap();
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
}
#[tokio::test]
async fn it_creates_text_prefix_to_string_response() {
let response = status!(418, text: true);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "true");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 418);
}
#[tokio::test]
async fn it_creates_text_prefix_number_response() {
let response = status!(418, text: 150);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "150");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 418);
}
#[tokio::test]
async fn it_creates_fmt_formatted_response() {
let name = "John";
let response = status!(401, fmt: "{} is not authorized!", name);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "John is not authorized!");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_fmt_interpolated_response() {
let name = "John";
let response = status!(401, fmt: "{name} is not authorized!");
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "John is not authorized!");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_fmt_with_headers_using_semicolon_separator() {
let name = "John";
let response = status!(401, fmt: "{} is not authorized!", name; [
("x-req-id", "some req id"),
]);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "John is not authorized!");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 401);
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
}
#[tokio::test]
async fn it_creates_text_prefix_with_headers_using_semicolon_separator() {
let response = status!(401, text: true; [
("x-req-id", "some req id"),
]);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "true");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 401);
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
}
#[tokio::test]
async fn it_creates_explicit_json_mode_response() {
let payload = TestPayload {
name: "test".into(),
};
let response = status!(401, json: payload);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_explicit_json_string_mode_response() {
let response = status!(401, json: "ok");
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "\"ok\"");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn it_creates_json_inline_object_with_headers_using_semicolon_separator() {
let response = status!(401, { "name": "test" }; [
("x-req-id", "some req id"),
]);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(body), "{\"name\":\"test\"}");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 401);
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
}
#[tokio::test]
async fn it_creates_empty_response_with_headers_using_semicolon_separator() {
let response = status!(204; [
("x-req-id", "some req id"),
]);
assert!(response.is_ok());
let mut response = response.unwrap();
let body = &response.body_mut().collect().await.unwrap().to_bytes();
assert_eq!(body.len(), 0);
assert_eq!(response.status(), 204);
assert!(response.headers().get("Content-Type").is_none());
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
}
}