#[macro_export]
macro_rules! ok {
() => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::empty()
)
};
([ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::empty();
[ $( $header ),* ]
)
};
(text: $body:expr) => {
$crate::response!(
$crate::http::StatusCode::OK,
$body.into();
[ $crate::headers::ContentType::text_utf_8() ]
)
};
(text: $body:expr ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::OK,
$body.into();
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
(fmt: $fmt:literal) => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt));
[ $crate::headers::ContentType::text_utf_8() ]
)
};
(fmt: $fmt:literal ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
(fmt: $fmt:literal, $( $arg:expr ),+ $(,)? ) => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[ $crate::headers::ContentType::text_utf_8() ]
)
};
(fmt: $fmt:literal, $( $arg:expr ),+ $(,)? ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
(json: $body:expr) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::OK,
body;
[ $crate::headers::ContentType::json() ]
),
Err(err) => Err(err),
}
}};
(json: $body:expr ; [ $( $header:expr ),* $(,)? ]) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::OK,
body;
[
$crate::headers::ContentType::json(),
$( $header ),*
]
),
Err(err) => Err(err),
}
}};
({ $($json:tt)* }) => {{
match $crate::HttpBody::json($crate::json::json_internal!({ $($json)* })) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::OK,
body;
[ $crate::headers::ContentType::json() ]
),
Err(err) => Err(err),
}
}};
{ $($name:tt : $value:tt),* $(,)? } => {{
match $crate::HttpBody::json($crate::json::json_internal!({ $($name: $value),* })) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::OK,
body;
[ $crate::headers::ContentType::json() ]
),
Err(err) => Err(err),
}
}};
({ $($json:tt)* } ; [ $( $header:expr ),* $(,)? ]) => {{
match $crate::HttpBody::json($crate::json::json_internal!({ $($json)* })) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::OK,
body;
[
$crate::headers::ContentType::json(),
$( $header ),*
]
),
Err(err) => Err(err),
}
}};
($fmt:literal) => {{
const __S: &str = $fmt;
if $crate::utils::str::memchr_contains(b'{', __S.as_bytes()) {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt));
[ $crate::headers::ContentType::text_utf_8() ]
)
} else {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::text(__S);
[ $crate::headers::ContentType::text_utf_8() ]
)
}
}};
($fmt:literal ; [ $( $header:expr ),* $(,)? ]) => {{
const __S: &str = $fmt;
if $crate::utils::str::memchr_contains(b'{', __S.as_bytes()) {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
} else {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::text(__S);
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
}
}};
($fmt:literal, $( $arg:expr ),+ $(,)? ) => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[ $crate::headers::ContentType::text_utf_8() ]
)
};
($fmt:literal, $( $arg:expr ),+ $(,)? ; [ $( $header:expr ),* $(,)? ]) => {
$crate::response!(
$crate::http::StatusCode::OK,
$crate::HttpBody::full(format!($fmt, $( $arg ),+));
[
$crate::headers::ContentType::text_utf_8(),
$( $header ),*
]
)
};
($body:expr) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::OK,
body;
[ $crate::headers::ContentType::json() ]
),
Err(err) => Err(err),
}
}};
($body:expr ; [ $( $header:expr ),* $(,)? ]) => {{
match $crate::HttpBody::json($body) {
Ok(body) => $crate::response!(
$crate::http::StatusCode::OK,
body;
[
$crate::headers::ContentType::json(),
$( $header ),*
]
),
Err(err) => Err(err),
}
}};
}
#[cfg(test)]
#[allow(unreachable_pub)]
#[allow(unused)]
mod tests {
use http_body_util::BodyExt;
use serde::Serialize;
use crate::headers;
#[derive(Serialize)]
struct TestPayload {
name: String,
}
headers! {
(ApiKey, "x-api-key"),
(RequestId, "x-req-id")
}
#[tokio::test]
async fn it_creates_json_ok_response() {
let payload = TestPayload {
name: "test".into(),
};
let response = ok!(payload);
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(), 200);
}
#[tokio::test]
async fn it_creates_json_from_inline_struct_ok_response() {
let response = ok!(TestPayload {
name: "test".into()
});
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(), 200);
}
#[tokio::test]
async fn it_creates_anonymous_type_json_ok_variant_1_response() {
let response = ok!({ "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.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_anonymous_type_json_ok_variant_2_response() {
let response = ok! {
"name_1": 1,
"name_2": "test 2"
};
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_1\":1,\"name_2\":\"test 2\"}"
);
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_text_ok_response() {
let text = "test";
let response = ok!(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.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_literal_text_ok_response() {
let response = ok!("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), "test");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_expr_ok_response() {
let response = ok!(5 + 5);
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), "10");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_number_ok_response() {
let number = 100;
let response = ok!(number);
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), "100");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_boolean_ok_response() {
let response = ok!(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(), 200);
}
#[tokio::test]
async fn it_creates_char_ok_response() {
let ch = 'a';
let response = ok!(ch);
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), "\"a\"");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_array_ok_response() {
let vec = vec![1, 2, 3];
let response = ok!(vec);
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), "[1,2,3]");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_formatted_text_ok_response() {
let text = "test";
let response = ok!("This is text: {}", 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), "This is text: test");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_interpolated_text_ok_response() {
let text = "test";
let response = ok!("This is text: {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), "This is text: test");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_empty_ok_response() {
let response = ok!();
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!(response.headers().get("Content-Type").is_none());
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_text_response_with_custom_headers() {
let response = ok!("ok"; [
("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), "ok");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
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_text_response_with_empty_custom_headers() {
#[allow(unused_mut)]
let response = ok!("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(),
"text/plain; charset=utf-8"
);
assert_eq!(response.headers().len(), 2);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_json_response_with_custom_headers() {
let payload = TestPayload {
name: "test".into(),
};
let response = ok!(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.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
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_anonymous_json_response_with_custom_headers() {
let response = ok!({ "name": "test" }; [
("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.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
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_ok_response_with_headers() {
let response = ok!([
ApiKey::from_static("some api key"),
RequestId::from_static("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(), 200);
assert_eq!(response.headers().get("x-api-key").unwrap(), "some api key");
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
assert!(response.headers().get("Content-Type").is_none());
}
#[tokio::test]
async fn it_creates_empty_ok_response_with_raw_headers() {
let response = ok!([("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(), 200);
assert_eq!(response.headers().get("x-api-key").unwrap(), "some api key");
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
assert!(response.headers().get("Content-Type").is_none());
}
#[tokio::test]
async fn it_creates_text_prefixed_string_ok_response() {
let response = ok!(text: "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), "test");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_text_prefixed_number_ok_response() {
let response = ok!(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(), 200);
}
#[tokio::test]
async fn it_creates_text_prefixed_char_ok_response() {
let response = ok!(text: 'a');
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), "a");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn it_creates_text_prefixed_response_with_custom_headers() {
let response = ok!(text: "ok"; [
("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), "ok");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
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_text_prefixed_response_with_headers() {
let response = ok!(text: "ok"; [
ApiKey::from_static("some api key"),
RequestId::from_static("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), "ok");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
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_text_prefixed_formatted_response_with_custom_headers() {
let name = "volga";
let response = ok!(fmt: "hello {}", name; [
("x-req-id", "123"),
]);
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), "hello volga");
assert_eq!(
response.headers().get("Content-Type").unwrap(),
"text/plain; charset=utf-8"
);
assert_eq!(response.status(), 200);
assert_eq!(response.headers().get("x-req-id").unwrap(), "123");
}
#[tokio::test]
async fn it_creates_explicit_json_ok_response() {
let payload = TestPayload {
name: "test".into(),
};
let response = ok!(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(), 200);
}
#[tokio::test]
async fn it_creates_explicit_json_string_ok_response() {
let response = ok!(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(), 200);
}
#[tokio::test]
async fn it_creates_explicit_json_response_with_custom_headers() {
let payload = TestPayload {
name: "test".into(),
};
let response = ok!(json: 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.headers().get("Content-Type").unwrap(),
"application/json"
);
assert_eq!(response.status(), 200);
assert_eq!(response.headers().get("x-api-key").unwrap(), "some api key");
assert_eq!(response.headers().get("x-req-id").unwrap(), "some req id");
}
}