use kynos::{http::StatusCode, prelude::*, test::TestClient};
use serde::{Deserialize, Serialize};
#[derive(Schema, Serialize, Deserialize)]
struct User {
id: u64,
name: String,
}
#[allow(dead_code)]
#[derive(Schema, PathParams)]
struct UserPath {
id: u64,
}
#[allow(dead_code)]
#[derive(Debug, thiserror::Error, ApiError)]
#[problem(base = "https://errors.example.com/")]
enum StoreError {
#[error("that name is already taken")]
#[problem(status = 409, type = "https://errors.example.com/name-taken")]
NameTaken,
}
#[kynos::get("/users/{id}")]
async fn get_user(Path(path): Path<UserPath>) -> Json<User> {
Json(User {
id: path.id,
name: "Ada Lovelace".to_owned(),
})
}
#[kynos::post("/users")]
async fn create_user(Json(user): Json<User>) -> Result<Created<Json<User>>, StoreError> {
if user.name == "taken" {
return Err(StoreError::NameTaken);
}
Ok(Created::at(
get_user::relative_uri(UserPath { id: user.id }),
Json(user),
))
}
fn service() -> kynos::Result<kynos::router::service::Service<()>> {
Router::<()>::new()
.mount(kynos::routes![get_user, create_user])
.build(())
}
#[tokio::main]
async fn main() -> kynos::Result<()> {
let client = TestClient::new(service()?);
let user: User = client
.get("/users/42")
.header("accept", "application/json")
.send()
.await
.assert_status(StatusCode::OK)
.json();
println!("fetched {}", user.name);
client
.post("/users")
.json(&User {
id: 42,
name: "taken".to_owned(),
})
.send()
.await
.assert_status(StatusCode::CONFLICT)
.assert_problem_type("https://errors.example.com/name-taken");
exercise_the_rejections(&client).await;
client.assert_conformance();
println!("every observed response conforms to the description");
client.assert_declared_responses_covered();
println!("every declared response was exercised");
Ok(())
}
async fn exercise_the_rejections(client: &TestClient<()>) {
client
.get("/users/not-a-number")
.send()
.await
.assert_status(StatusCode::BAD_REQUEST);
client
.post("/users")
.header("content-type", "application/json")
.send()
.await
.assert_status(StatusCode::BAD_REQUEST);
client
.post("/users")
.header("content-type", "text/plain")
.send()
.await
.assert_status(StatusCode::UNSUPPORTED_MEDIA_TYPE);
client
.post("/users")
.json(&serde_json::json!({ "id": "one", "name": 1 }))
.send()
.await
.assert_status(StatusCode::UNPROCESSABLE_ENTITY);
client
.post("/users")
.json(&User {
id: 1,
name: "fresh".to_owned(),
})
.send()
.await
.assert_status(StatusCode::CREATED);
}