#![allow(dead_code)]
use kynos::{
http::{HeaderMap, HeaderName, HeaderValue, Method, Request, StatusCode, body::Body},
prelude::*,
response::status::NoContent,
router::service::Service,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Pool(pub(crate) u32);
#[derive(kynos::Provider)]
pub(crate) struct App {
pub(crate) pool: Pool,
}
impl App {
#[must_use]
pub(crate) fn new() -> Self {
Self { pool: Pool(7) }
}
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, PartialEq, Schema, Serialize, Deserialize)]
pub(crate) struct User {
pub(crate) id: u64,
pub(crate) name: String,
}
#[derive(Schema, kynos::PathParams)]
pub(crate) struct UserPath {
pub(crate) id: u64,
}
#[derive(Schema, kynos::QueryParams)]
pub(crate) struct UserQuery {
pub(crate) limit: Option<u32>,
}
#[derive(Debug, thiserror::Error, kynos::ApiError)]
#[problem(base = "https://errors.example.com/")]
pub(crate) enum StoreError {
#[error("that name is already taken")]
#[problem(status = 409, type = "https://errors.example.com/name-taken")]
NameTaken,
}
#[kynos::get("/users/{id}")]
pub(crate) async fn get_user(Path(path): Path<UserPath>, Inject(pool): Inject<Pool>) -> Json<User> {
Json(User {
id: path.id,
name: format!("user from pool {}", pool.0),
})
}
#[kynos::get("/users")]
pub(crate) async fn list_users(Query(query): Query<UserQuery>) -> Json<Vec<User>> {
let names = ["Ada Lovelace", "Grace Hopper", "Barbara Liskov"];
let wanted = query.limit.unwrap_or(u32::MAX) as usize;
Json(
names
.into_iter()
.take(wanted)
.enumerate()
.map(|(index, name)| User {
id: index as u64,
name: name.to_owned(),
})
.collect(),
)
}
#[kynos::post("/users")]
pub(crate) 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),
))
}
#[kynos::delete("/users/{id}")]
pub(crate) async fn delete_user(Path(path): Path<UserPath>) -> NoContent {
let _ = path;
NoContent
}
#[must_use]
pub(crate) fn router() -> Router<App> {
Router::<App>::new().mount(kynos::routes![
get_user,
list_users,
create_user,
delete_user
])
}
#[must_use]
pub(crate) fn service() -> Service<App> {
router().build(App::new()).expect("a describable router")
}
pub(crate) struct Reply {
pub(crate) status: StatusCode,
pub(crate) headers: HeaderMap,
pub(crate) body: bytes::Bytes,
}
impl Reply {
#[must_use]
pub(crate) fn field(&self, name: &str) -> Option<String> {
self.headers
.get(name)
.map(|value| value.to_str().expect("a printable field").to_owned())
}
#[must_use]
pub(crate) fn fields(&self, name: &str) -> Vec<String> {
self.headers
.get_all(name)
.iter()
.map(|value| value.to_str().expect("a printable field").to_owned())
.collect()
}
#[must_use]
pub(crate) fn text(&self) -> String {
String::from_utf8(self.body.to_vec()).expect("a printable body")
}
#[must_use]
pub(crate) fn json(&self) -> serde_json::Value {
serde_json::from_slice(&self.body).unwrap_or_else(|error| {
panic!("a JSON body: {error}, from {:?}", self.text());
})
}
}
pub(crate) struct Pending<'a, C> {
service: &'a Service<C>,
method: Method,
target: String,
headers: Vec<(HeaderName, HeaderValue)>,
body: Option<bytes::Bytes>,
}
impl<C: 'static> Pending<'_, C> {
#[must_use]
pub(crate) fn header(mut self, name: &str, value: &str) -> Self {
self.headers.push((
HeaderName::from_bytes(name.as_bytes()).expect("a usable field name"),
HeaderValue::from_str(value).expect("a usable field value"),
));
self
}
#[must_use]
pub(crate) fn json<T: Serialize>(mut self, value: &T) -> Self {
self.body = Some(
serde_json::to_vec(value)
.expect("a serializable body")
.into(),
);
self.header("content-type", "application/json")
}
#[must_use]
pub(crate) fn body(mut self, bytes: impl Into<bytes::Bytes>) -> Self {
self.body = Some(bytes.into());
self
}
pub(crate) async fn call(self) -> Reply {
let mut request = Request::new(match self.body {
Some(bytes) => Body::from_bytes(bytes),
None => Body::empty(),
});
*request.method_mut() = self.method;
*request.uri_mut() = self.target.parse().expect("a usable request target");
for (name, value) in self.headers {
request.headers_mut().insert(name, value);
}
let response = self.service.call(request).await;
let status = response.status();
let headers = response.headers().clone();
let body = drain(response.into_body()).await;
Reply {
status,
headers,
body,
}
}
}
async fn drain(body: Body) -> bytes::Bytes {
use http_body_util::BodyExt;
body.collect().await.expect("a readable body").to_bytes()
}
pub(crate) fn send<'a, C>(service: &'a Service<C>, method: Method, target: &str) -> Pending<'a, C> {
Pending {
service,
method,
target: target.to_owned(),
headers: Vec::new(),
body: None,
}
}
pub(crate) fn get<'a, C>(service: &'a Service<C>, target: &str) -> Pending<'a, C> {
send(service, Method::GET, target)
}
pub(crate) fn post<'a, C>(service: &'a Service<C>, target: &str) -> Pending<'a, C> {
send(service, Method::POST, target)
}