use std::{collections::HashMap, net::Ipv4Addr};
use kynos::{
prelude::*,
schema::{MapKey, constraints::Constraints, unchecked::Unchecked},
server::Server,
};
use serde::{Deserialize, Serialize};
#[derive(Schema, Serialize, Deserialize, PartialEq, Eq, Hash)]
struct Sku(String);
impl MapKey for Sku {
fn key_constraints() -> Constraints {
let mut constraints = Constraints::default();
constraints.pattern = Some("^[A-Z]{3}-[0-9]{4}$".to_owned());
constraints
}
}
#[derive(Schema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Product {
#[schema(min_length = 1, max_length = 120)]
display_name: String,
#[schema(pattern = "^[a-z][a-z0-9-]*$")]
slug: String,
#[schema(minimum = 0, maximum = 1_000_000)]
price_cents: u64,
#[schema(min_items = 1, max_items = 10, unique_items)]
tags: Vec<String>,
#[schema(max_length = 2_000)]
summary: Option<String>,
#[deprecated(note = "use `tags`; the note stays in Rust and never reaches the description")]
#[schema(max_length = 120)]
keywords: Option<String>,
#[serde(flatten)]
#[schema(open)]
attributes: HashMap<String, String>,
}
#[derive(Schema, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum Pricing {
Fixed { cents: u64 },
Tiered { tiers: Vec<Tier> },
#[deprecated(note = "priced per contract instead")]
Negotiated,
}
#[derive(Schema, Serialize, Deserialize)]
struct Tier {
#[schema(minimum = 1)]
from_quantity: u32,
#[schema(minimum = 0)]
cents: u64,
}
#[derive(Schema, Serialize, Deserialize)]
struct Catalogue {
products: HashMap<Sku, Product>,
pricing: HashMap<Sku, Pricing>,
}
#[derive(Schema, Serialize, Deserialize)]
struct Ingest {
#[schema(pattern = "^[a-z0-9-]+$")]
supplier: String,
payload: Unchecked<serde_json::Value>,
}
#[kynos::get("/catalogue")]
async fn get_catalogue() -> Json<Catalogue> {
let sku = Sku("ABC-0001".to_owned());
let product = Product {
display_name: "Mechanical Keyboard".to_owned(),
slug: "mechanical-keyboard".to_owned(),
price_cents: 12_950,
tags: vec!["input".to_owned(), "keyboard".to_owned()],
summary: Some("Eighty-seven keys and no regrets.".to_owned()),
#[allow(deprecated)]
keywords: None,
attributes: HashMap::from([("switches".to_owned(), "tactile".to_owned())]),
};
Json(Catalogue {
products: HashMap::from([(Sku(sku.0.clone()), product)]),
pricing: HashMap::from([(
sku,
Pricing::Tiered {
tiers: vec![
Tier {
from_quantity: 1,
cents: 12_950,
},
Tier {
from_quantity: 10,
cents: 11_500,
},
],
},
)]),
})
}
#[kynos::post("/ingest")]
async fn post_ingest(Json(ingest): Json<Ingest>) -> NoContent {
println!("{}: {}", ingest.supplier, ingest.payload.into_inner());
NoContent
}
#[tokio::main]
async fn main() -> kynos::Result<()> {
let router = Router::<()>::new().mount(kynos::routes![get_catalogue, post_ingest]);
for violation in router.validate()? {
println!("{violation}");
}
let document = router.openapi()?;
println!("{}", document.to_json()?);
Server::new(router.build(())?)
.bind((Ipv4Addr::UNSPECIFIED, 3000))
.serve()
.await
}