use seamless::{
api::{ Api, ApiBody, ApiError },
handler::{ body::{ FromJson, Capped, IntoBody }, request::Bytes, response::ToJson },
http::{ Request },
};
use serde_json::{ Value, json };
#[tokio::main]
async fn main() {
let mut api = Api::new();
api.add("basic/echo")
.description("Echoes back a JSON string")
.handler(|FromJson(body)| ToJson::<String>(body));
api.add("basic/reverse")
.description("Reverse an array of numbers")
.handler(|body: Capped<FromJson<Vec<usize>>, {8 * 1024}>| {
ToJson(body.into_body().into_iter().rev().collect::<Vec<usize>>())
});
api.add("meta/status")
.description("Get the current API status")
.handler(|| status().map(ToJson));
api.add("maths/divide")
.description("Divide two numbers by each other")
.handler(|FromJson(body)| divide(body));
let req = Request::post("/maths/divide")
.header("content-type", "application/json")
.body(Bytes::from_vec(serde_json::to_vec(&BinaryInput { a: 20, b: 10 }).unwrap()))
.unwrap();
let actual: Value = serde_json::from_slice(&api.handle(req).await.unwrap().into_body()).unwrap();
let expected = serde_json::to_value(json!({ "a": 20, "b": 10, "result": 2 })).unwrap();
assert_eq!(actual, expected);
let req = Request::post("/maths/divide")
.header("content-type", "application/json")
.body(Bytes::from_vec(serde_json::to_vec(&BinaryInput { a: 10, b: 0 }).unwrap()))
.unwrap();
assert_eq!(
api.handle(req).await.unwrap_err().unwrap_err(),
ApiError {
code: 400,
internal_message: "Division by zero".to_owned(),
external_message: "Division by zero".to_owned(),
value: None
}
);
let req = Request::get("/meta/status")
.header("content-type", "application/json")
.body(Bytes::from_vec(Vec::new()))
.unwrap();
let actual: Value = serde_json::from_slice(&api.handle(req).await.unwrap().into_body()).unwrap();
let expected = serde_json::to_value(json!({ "status": "Ok" })).unwrap();
assert_eq!(actual, expected);
}
#[derive(ApiError, Debug, thiserror::Error)]
enum MathsError {
#[error("Division by zero")]
#[api_error(external, code=400)]
DivideByZero
}
#[ApiBody]
struct BinaryInput {
a: usize,
b: usize
}
#[ApiBody]
#[derive(PartialEq)]
struct BinaryOutput {
a: usize,
b: usize,
result: usize
}
async fn divide(input: BinaryInput) -> Result<ToJson<BinaryOutput>,MathsError> {
let a = input.a;
let b = input.b;
a.checked_div(b)
.ok_or(MathsError::DivideByZero)
.map(|result| ToJson(BinaryOutput { a, b, result }))
}
#[ApiBody]
struct Status {
status: StatusValue
}
#[ApiBody]
enum StatusValue {
Ok,
NotOk
}
fn status() -> Option<Status> {
Some(Status {
status: StatusValue::Ok
})
}
#[test]
fn test_main() {
main()
}