use crate::SETTLE;
use alux_ext::ext;
use alux_http::{
BytesOutAlg, CacheControl, ChunksAlg, ChunksExt, EmptyOutAlg, FromPartsAlg, HeaderOutAlg, HtmlOutAlg, HttpApiAlg,
JsonOutAlg, NamedValuesAlg, PartAlg, RedirectOutAlg, ResultOutAlg, StatusOutAlg, StreamOutAlg, TextOutAlg, http,
};
use alux_shape::Shape;
use core::convert::Infallible;
use core::fmt::Display;
use core::future::Future;
use core::time::Duration;
use serde::{Deserialize, Serialize};
use std::io::{Error as IoError, ErrorKind};
use tokio::time::sleep;
#[derive(Debug, Serialize, Deserialize, Shape)]
pub struct Session {
pub session: String,
}
#[derive(Debug, Serialize, Deserialize, Shape)]
pub struct Agent {
pub user_agent: String,
}
impl NamedValuesAlg for Session {}
impl NamedValuesAlg for Agent {}
#[derive(Debug, Serialize, Deserialize, Shape)]
pub struct Amount {
pub value: u32,
}
pub trait ShopAlg {
fn item(&self, id: u32) -> impl Future<Output = Result<u32, IoError>> + Send;
fn items(&self) -> impl Future<Output = Vec<u32>> + Send;
fn add(&self, value: u32) -> impl Future<Output = u32> + Send;
fn note(&self, note: String) -> impl Future<Output = String> + Send;
fn clear(&self) -> impl Future<Output = ()> + Send;
fn home(&self) -> impl Future<Output = String> + Send;
fn page(&self) -> impl Future<Output = String> + Send;
fn stored(&self) -> impl Future<Output = Vec<u8>> + Send;
fn who(&self, session: String) -> impl Future<Output = String> + Send;
fn agent(&self, agent: String) -> impl Future<Output = String> + Send;
fn cached(&self) -> impl Future<Output = (String, Vec<u32>)> + Send;
}
#[ext(name = ShopOperationExt, defunc)]
pub impl<This> This
where
This: ShopAlg,
{
async fn shop_item(&self, id: u32) -> Result<u32, IoError> {
self.item(id).await
}
async fn shop_items(&self) -> Vec<u32> {
self.items().await
}
async fn shop_add(&self, value: u32) -> u32 {
self.add(value).await
}
async fn shop_fill(&self, amount: Amount) -> u32 {
self.add(amount.value).await
}
async fn shop_note(&self, note: String) -> String {
self.note(note).await
}
async fn shop_clear(&self) {
self.clear().await;
}
async fn shop_home(&self) -> String {
self.home().await
}
async fn shop_page(&self) -> String {
self.page().await
}
async fn shop_stored(&self) -> Vec<u8> {
self.stored().await
}
async fn shop_who(&self, session: Session) -> String {
self.who(session.session).await
}
async fn shop_agent(&self, agent: Agent) -> String {
self.agent(agent.user_agent).await
}
async fn shop_cached(&self) -> (String, Vec<u32>) {
self.cached().await
}
}
#[ext(name = ShopApiExt, defunc(via = http))]
pub impl<This> This
where
This: HttpApiAlg
+ HeaderOutAlg
+ JsonOutAlg
+ TextOutAlg
+ HtmlOutAlg
+ BytesOutAlg
+ EmptyOutAlg
+ RedirectOutAlg
+ StatusOutAlg
+ ResultOutAlg,
{
fn shop_api<Alg>(&self)
where
Alg: ShopAlg,
{
self.routes()
.get("/item/:id", self.op(Alg::shop_item).path::<u32>().json().result())
.get("/items", self.op(Alg::shop_items).json())
.post("/items", self.op(Alg::shop_add).body::<u32>().json().status::<201>())
.put("/items", self.op(Alg::shop_fill).form::<Amount>().json())
.patch("/items", self.op(Alg::shop_note).raw_body::<String>().text())
.delete("/items", self.op(Alg::shop_clear).empty())
.get("/home", self.op(Alg::shop_home).redirect())
.get("/page", self.op(Alg::shop_page).html())
.get("/stored", self.op(Alg::shop_stored).bytes())
.get("/session", self.op(Alg::shop_who).cookie::<Session>().text())
.get("/agent", self.op(Alg::shop_agent).in_header::<Agent>().text())
.get("/cached", self.op(Alg::shop_cached).json().out_header::<CacheControl>())
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Shop;
impl ShopAlg for Shop {
async fn item(&self, id: u32) -> Result<u32, IoError> {
match id {
1 => Ok(7),
_ => Err(IoError::new(ErrorKind::NotFound, "no such reading")),
}
}
async fn items(&self) -> Vec<u32> {
vec![7]
}
async fn add(&self, value: u32) -> u32 {
value
}
async fn note(&self, note: String) -> String {
format!("noted {note}")
}
async fn clear(&self) {}
async fn home(&self) -> String {
"/items".to_owned()
}
async fn page(&self) -> String {
"<p>7</p>".to_owned()
}
async fn stored(&self) -> Vec<u8> {
b"seven".to_vec()
}
async fn who(&self, session: String) -> String {
format!("known as {session}")
}
async fn agent(&self, agent: String) -> String {
format!("sent by {agent}")
}
async fn cached(&self) -> (String, Vec<u32>) {
("max-age=60".to_owned(), vec![7])
}
}
pub const LABELS: &[&str] = &[
"GET /item/{id}",
"GET /items",
"POST /items",
"PUT /items",
"PATCH /items",
"DELETE /items",
"GET /home",
"GET /page",
"GET /stored",
"GET /session",
"GET /agent",
"GET /cached",
];
pub trait WideAlg {
fn wide(&self, stated: Vec<String>) -> impl Future<Output = String> + Send;
}
#[ext(name = WideOperationExt, defunc)]
pub impl<This> This
where
This: WideAlg,
{
#[allow(clippy::too_many_arguments)]
async fn wide_all(
&self,
first: String,
second: String,
third: String,
fourth: String,
fifth: String,
sixth: String,
seventh: String,
eighth: String,
ninth: String,
tenth: String,
eleventh: String,
twelfth: String,
thirteenth: String,
fourteenth: String,
fifteenth: String,
sixteenth: String,
) -> String {
let stated = vec![
first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth,
fourteenth, fifteenth, sixteenth,
];
self.wide(stated).await
}
}
#[ext(name = WideApiExt, defunc(via = http))]
pub impl<This> This
where
This: HttpApiAlg + TextOutAlg,
{
fn wide_api<Alg>(&self)
where
Alg: WideAlg,
{
self.routes().post(
"/wide",
self.op(Alg::wide_all)
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.raw_body::<String>()
.text(),
)
}
}
impl WideAlg for Shop {
async fn wide(&self, stated: Vec<String>) -> String {
stated.len().to_string()
}
}
#[derive(Debug, Default)]
pub struct Ticks {
left: Vec<&'static str>,
}
impl ChunksAlg for Ticks {
type Chunk = Vec<u8>;
type Error = Infallible;
async fn next_chunk(&mut self) -> Option<Result<Self::Chunk, Self::Error>> {
self.left.pop().map(|tick| Ok(tick.as_bytes().to_vec()))
}
}
pub trait TicksAlg {
fn ticks(&self) -> impl Future<Output = Ticks> + Send;
}
impl TicksAlg for Shop {
async fn ticks(&self) -> Ticks {
Ticks { left: vec!["three", "two", "one"] }
}
}
#[ext(name = TicksOperationExt, defunc)]
pub impl<This> This
where
This: TicksAlg,
{
async fn shop_ticks(&self) -> Ticks {
self.ticks().await
}
}
#[ext(name = StreamApiExt, defunc(via = http))]
pub impl<This> This
where
This: HttpApiAlg + StreamOutAlg,
{
fn stream_api<Alg>(&self)
where
Alg: TicksAlg,
{
self.routes().get("/ticks", self.op(Alg::shop_ticks).stream())
}
}
pub const ANSWERS_LATE: Duration = Duration::from_secs(30);
pub const ANSWERS_SOON: Duration = Duration::from_secs(1);
pub const SLOW: Duration = ANSWERS_LATE.saturating_add(SETTLE);
pub const PAUSE: Duration = ANSWERS_SOON.saturating_add(SETTLE);
pub trait SlowAlg {
fn slow(&self) -> impl Future<Output = String> + Send;
fn pause(&self) -> impl Future<Output = String> + Send;
}
impl SlowAlg for Shop {
async fn slow(&self) -> String {
sleep(SLOW).await;
"waited".to_owned()
}
async fn pause(&self) -> String {
sleep(PAUSE).await;
"paused".to_owned()
}
}
#[ext(name = SlowOperationExt, defunc)]
pub impl<This> This
where
This: SlowAlg,
{
async fn shop_slow(&self) -> String {
self.slow().await
}
async fn shop_pause(&self) -> String {
self.pause().await
}
}
#[ext(name = LifecycleApiExt, defunc(via = http))]
pub impl<This> This
where
This: HttpApiAlg + JsonOutAlg + TextOutAlg,
{
fn lifecycle_api<Alg>(&self)
where
Alg: ShopAlg + SlowAlg,
{
self.routes()
.get("/items", self.op(Alg::shop_items).json())
.get("/slow", self.op(Alg::shop_slow).text())
.get("/pause", self.op(Alg::shop_pause).text())
}
}
#[derive(Debug, Default)]
pub struct Upload {
pub stated: Vec<String>,
}
impl<Parts> FromPartsAlg<Parts> for Upload
where
Parts: ChunksAlg + Send,
Parts::Error: Display + Send,
Parts::Chunk: PartAlg + Send,
<Parts::Chunk as PartAlg>::Content: ChunksAlg<Chunk = Vec<u8>> + Send + 'static,
<<Parts::Chunk as PartAlg>::Content as ChunksAlg>::Error: Display,
{
type Error = String;
async fn from_parts(mut parts: Parts) -> Result<Self, Self::Error> {
let mut stated = Vec::new();
while let Some(part) = parts.next_chunk().await {
let part = part.map_err(|error| error.to_string())?;
let name = part.part_name().unwrap_or_default().to_owned();
let carried = part.part_content().gathered().await.map_err(|error| error.to_string())?;
let carried = String::from_utf8_lossy(&carried.concat()).into_owned();
stated.push(format!("{name}={carried}"));
}
Ok(Self { stated })
}
}
pub trait UploadAlg {
fn uploaded(&self, stated: Vec<String>) -> impl Future<Output = String> + Send;
}
impl UploadAlg for Shop {
async fn uploaded(&self, stated: Vec<String>) -> String {
stated.join(",")
}
}
#[ext(name = UploadOperationExt, defunc)]
pub impl<This> This
where
This: UploadAlg,
{
async fn shop_upload(&self, upload: Upload) -> String {
self.uploaded(upload.stated).await
}
}
#[ext(name = MultipartApiExt, defunc(via = http))]
pub impl<This> This
where
This: HttpApiAlg + TextOutAlg,
{
fn multipart_api<Alg>(&self)
where
Alg: UploadAlg,
{
self.routes().post("/upload", self.op(Alg::shop_upload).multipart::<Upload>().text())
}
}