use std::{net::Ipv4Addr, sync::Arc};
use kynos::{
di::inject::Inject,
extract::{body::binary::Binary, media},
openapi::{Contact, Info, License, Server as ApiServer, ServerVariable, SpecVersion},
prelude::*,
server::Server,
};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
struct Description(Arc<bytes::Bytes>);
#[derive(Provider)]
struct App {
description: Description,
}
#[derive(Schema, Serialize, Deserialize)]
struct User {
id: u64,
name: String,
}
#[kynos::get("/users")]
async fn list_users() -> Json<Vec<User>> {
Json(vec![User {
id: 1,
name: "Ada Lovelace".to_owned(),
}])
}
#[kynos::query("/users")]
async fn search(Json(filter): Json<User>) -> Json<Vec<User>> {
Json(vec![filter])
}
#[kynos::get("/openapi.json")]
async fn openapi_json(Inject(description): Inject<Description>) -> Binary<media::Json> {
Binary::new(bytes::Bytes::clone(&description.0))
}
fn describe() -> Info {
Info {
summary: Some("Accounts, and what can be done to them".to_owned()),
description: Some(
"The first paragraph of each handler's doc comment becomes that \
operation's summary, and the rest its description. This field is \
the same idea for the API as a whole."
.to_owned(),
),
terms_of_service: Some("https://example.com/terms".to_owned()),
contact: Some(Contact {
name: Some("API Platform".to_owned()),
url: Some("https://example.com/support".to_owned()),
email: Some("api@example.com".to_owned()),
..Contact::default()
}),
license: Some(License::spdx("Apache-2.0", "Apache-2.0")),
..Info::new("Example Users API", "1.4.0")
}
}
fn deployment() -> ApiServer {
ApiServer::new("https://{region}.api.example.com/v1")
.with_description("Regional production endpoints")
.with_variable(
"region",
ServerVariable {
enumeration: Some(vec![
"eu-west-1".to_owned(),
"us-east-1".to_owned(),
"ap-south-1".to_owned(),
]),
description: Some("The deployment a client should reach".to_owned()),
..ServerVariable::new("eu-west-1")
},
)
}
#[tokio::main]
async fn main() -> kynos::Result<()> {
let router = Router::<App>::new()
.info(describe())
.server(deployment())
.mount(kynos::routes![list_users, search, openapi_json]);
for violation in router.validate()? {
println!("warning: {violation}");
}
let document = router.openapi()?;
println!("{}", document.to_json()?);
println!("{}", document.to_yaml()?);
match router.openapi_as(SpecVersion::V3_1) {
Ok(_) => println!("unexpected: 3.1 accepted a QUERY operation"),
Err(error) => println!("3.1 refused, correctly: {error}"),
}
let description = Description(Arc::new(bytes::Bytes::from(document.to_json()?)));
Server::new(router.build(App { description })?)
.bind((Ipv4Addr::UNSPECIFIED, 3000))
.serve()
.await
}