1use crate::SETTLE;
7use alux_ext::ext;
8use alux_http::{
9 BytesOutAlg, CacheControl, ChunksAlg, ChunksExt, EmptyOutAlg, FromPartsAlg, HeaderOutAlg, HtmlOutAlg, HttpApiAlg,
10 JsonOutAlg, NamedValuesAlg, PartAlg, RedirectOutAlg, ResultOutAlg, StatusOutAlg, StreamOutAlg, TextOutAlg, http,
11};
12use alux_shape::Shape;
13use core::convert::Infallible;
14use core::fmt::Display;
15use core::future::Future;
16use core::time::Duration;
17use serde::{Deserialize, Serialize};
18use std::io::{Error as IoError, ErrorKind};
19use tokio::time::sleep;
20
21#[derive(Debug, Serialize, Deserialize, Shape)]
23pub struct Session {
24 pub session: String,
26}
27
28#[derive(Debug, Serialize, Deserialize, Shape)]
30pub struct Agent {
31 pub user_agent: String,
33}
34
35impl NamedValuesAlg for Session {}
36
37impl NamedValuesAlg for Agent {}
38
39#[derive(Debug, Serialize, Deserialize, Shape)]
41pub struct Amount {
42 pub value: u32,
44}
45
46pub trait ShopAlg {
48 fn item(&self, id: u32) -> impl Future<Output = Result<u32, IoError>> + Send;
50 fn items(&self) -> impl Future<Output = Vec<u32>> + Send;
52 fn add(&self, value: u32) -> impl Future<Output = u32> + Send;
54 fn note(&self, note: String) -> impl Future<Output = String> + Send;
56 fn clear(&self) -> impl Future<Output = ()> + Send;
58 fn home(&self) -> impl Future<Output = String> + Send;
60 fn page(&self) -> impl Future<Output = String> + Send;
62 fn stored(&self) -> impl Future<Output = Vec<u8>> + Send;
64 fn who(&self, session: String) -> impl Future<Output = String> + Send;
66 fn agent(&self, agent: String) -> impl Future<Output = String> + Send;
68 fn cached(&self) -> impl Future<Output = (String, Vec<u32>)> + Send;
70}
71
72#[ext(name = ShopOperationExt, defunc)]
74pub impl<This> This
75where
76 This: ShopAlg,
77{
78 async fn shop_item(&self, id: u32) -> Result<u32, IoError> {
80 self.item(id).await
81 }
82
83 async fn shop_items(&self) -> Vec<u32> {
85 self.items().await
86 }
87
88 async fn shop_add(&self, value: u32) -> u32 {
90 self.add(value).await
91 }
92
93 async fn shop_fill(&self, amount: Amount) -> u32 {
95 self.add(amount.value).await
96 }
97
98 async fn shop_note(&self, note: String) -> String {
100 self.note(note).await
101 }
102
103 async fn shop_clear(&self) {
105 self.clear().await;
106 }
107
108 async fn shop_home(&self) -> String {
110 self.home().await
111 }
112
113 async fn shop_page(&self) -> String {
115 self.page().await
116 }
117
118 async fn shop_stored(&self) -> Vec<u8> {
120 self.stored().await
121 }
122
123 async fn shop_who(&self, session: Session) -> String {
125 self.who(session.session).await
126 }
127
128 async fn shop_agent(&self, agent: Agent) -> String {
130 self.agent(agent.user_agent).await
131 }
132
133 async fn shop_cached(&self) -> (String, Vec<u32>) {
135 self.cached().await
136 }
137}
138
139#[ext(name = ShopApiExt, defunc(via = http))]
141pub impl<This> This
142where
143 This: HttpApiAlg
144 + HeaderOutAlg
145 + JsonOutAlg
146 + TextOutAlg
147 + HtmlOutAlg
148 + BytesOutAlg
149 + EmptyOutAlg
150 + RedirectOutAlg
151 + StatusOutAlg
152 + ResultOutAlg,
153{
154 fn shop_api<Alg>(&self)
156 where
157 Alg: ShopAlg,
158 {
159 self.routes()
160 .get("/item/:id", self.op(Alg::shop_item).path::<u32>().json().result())
162 .get("/items", self.op(Alg::shop_items).json())
164 .post("/items", self.op(Alg::shop_add).body::<u32>().json().status::<201>())
166 .put("/items", self.op(Alg::shop_fill).form::<Amount>().json())
168 .patch("/items", self.op(Alg::shop_note).raw_body::<String>().text())
170 .delete("/items", self.op(Alg::shop_clear).empty())
172 .get("/home", self.op(Alg::shop_home).redirect())
174 .get("/page", self.op(Alg::shop_page).html())
176 .get("/stored", self.op(Alg::shop_stored).bytes())
178 .get("/session", self.op(Alg::shop_who).cookie::<Session>().text())
180 .get("/agent", self.op(Alg::shop_agent).in_header::<Agent>().text())
182 .get("/cached", self.op(Alg::shop_cached).json().out_header::<CacheControl>())
184 }
185}
186
187#[derive(Debug, Default, Clone, Copy)]
189pub struct Shop;
190
191impl ShopAlg for Shop {
192 async fn item(&self, id: u32) -> Result<u32, IoError> {
193 match id {
194 1 => Ok(7),
195 _ => Err(IoError::new(ErrorKind::NotFound, "no such reading")),
196 }
197 }
198
199 async fn items(&self) -> Vec<u32> {
200 vec![7]
201 }
202
203 async fn add(&self, value: u32) -> u32 {
204 value
205 }
206
207 async fn note(&self, note: String) -> String {
208 format!("noted {note}")
209 }
210
211 async fn clear(&self) {}
212
213 async fn home(&self) -> String {
214 "/items".to_owned()
215 }
216
217 async fn page(&self) -> String {
218 "<p>7</p>".to_owned()
219 }
220
221 async fn stored(&self) -> Vec<u8> {
222 b"seven".to_vec()
223 }
224
225 async fn who(&self, session: String) -> String {
226 format!("known as {session}")
227 }
228
229 async fn agent(&self, agent: String) -> String {
230 format!("sent by {agent}")
231 }
232
233 async fn cached(&self) -> (String, Vec<u32>) {
234 ("max-age=60".to_owned(), vec![7])
235 }
236}
237
238pub const LABELS: &[&str] = &[
240 "GET /item/{id}",
241 "GET /items",
242 "POST /items",
243 "PUT /items",
244 "PATCH /items",
245 "DELETE /items",
246 "GET /home",
247 "GET /page",
248 "GET /stored",
249 "GET /session",
250 "GET /agent",
251 "GET /cached",
252];
253
254pub trait WideAlg {
256 fn wide(&self, stated: Vec<String>) -> impl Future<Output = String> + Send;
258}
259
260#[ext(name = WideOperationExt, defunc)]
262pub impl<This> This
263where
264 This: WideAlg,
265{
266 #[allow(clippy::too_many_arguments)]
268 async fn wide_all(
269 &self,
270 first: String,
271 second: String,
272 third: String,
273 fourth: String,
274 fifth: String,
275 sixth: String,
276 seventh: String,
277 eighth: String,
278 ninth: String,
279 tenth: String,
280 eleventh: String,
281 twelfth: String,
282 thirteenth: String,
283 fourteenth: String,
284 fifteenth: String,
285 sixteenth: String,
286 ) -> String {
287 let stated = vec![
288 first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth,
289 fourteenth, fifteenth, sixteenth,
290 ];
291
292 self.wide(stated).await
293 }
294}
295
296#[ext(name = WideApiExt, defunc(via = http))]
302pub impl<This> This
303where
304 This: HttpApiAlg + TextOutAlg,
305{
306 fn wide_api<Alg>(&self)
308 where
309 Alg: WideAlg,
310 {
311 self.routes().post(
312 "/wide",
313 self.op(Alg::wide_all)
314 .raw_body::<String>()
315 .raw_body::<String>()
316 .raw_body::<String>()
317 .raw_body::<String>()
318 .raw_body::<String>()
319 .raw_body::<String>()
320 .raw_body::<String>()
321 .raw_body::<String>()
322 .raw_body::<String>()
323 .raw_body::<String>()
324 .raw_body::<String>()
325 .raw_body::<String>()
326 .raw_body::<String>()
327 .raw_body::<String>()
328 .raw_body::<String>()
329 .raw_body::<String>()
330 .text(),
331 )
332 }
333}
334
335impl WideAlg for Shop {
336 async fn wide(&self, stated: Vec<String>) -> String {
337 stated.len().to_string()
338 }
339}
340
341#[derive(Debug, Default)]
346pub struct Ticks {
347 left: Vec<&'static str>,
348}
349
350impl ChunksAlg for Ticks {
351 type Chunk = Vec<u8>;
352 type Error = Infallible;
353
354 async fn next_chunk(&mut self) -> Option<Result<Self::Chunk, Self::Error>> {
355 self.left.pop().map(|tick| Ok(tick.as_bytes().to_vec()))
356 }
357}
358
359pub trait TicksAlg {
361 fn ticks(&self) -> impl Future<Output = Ticks> + Send;
363}
364
365impl TicksAlg for Shop {
366 async fn ticks(&self) -> Ticks {
367 Ticks { left: vec!["three", "two", "one"] }
368 }
369}
370
371#[ext(name = TicksOperationExt, defunc)]
373pub impl<This> This
374where
375 This: TicksAlg,
376{
377 async fn shop_ticks(&self) -> Ticks {
379 self.ticks().await
380 }
381}
382
383#[ext(name = StreamApiExt, defunc(via = http))]
385pub impl<This> This
386where
387 This: HttpApiAlg + StreamOutAlg,
388{
389 fn stream_api<Alg>(&self)
391 where
392 Alg: TicksAlg,
393 {
394 self.routes().get("/ticks", self.op(Alg::shop_ticks).stream())
395 }
396}
397
398pub const ANSWERS_LATE: Duration = Duration::from_secs(30);
403
404pub const ANSWERS_SOON: Duration = Duration::from_secs(1);
409
410pub const SLOW: Duration = ANSWERS_LATE.saturating_add(SETTLE);
412
413pub const PAUSE: Duration = ANSWERS_SOON.saturating_add(SETTLE);
419
420pub trait SlowAlg {
422 fn slow(&self) -> impl Future<Output = String> + Send;
424
425 fn pause(&self) -> impl Future<Output = String> + Send;
427}
428
429impl SlowAlg for Shop {
430 async fn slow(&self) -> String {
431 sleep(SLOW).await;
432 "waited".to_owned()
433 }
434
435 async fn pause(&self) -> String {
436 sleep(PAUSE).await;
437 "paused".to_owned()
438 }
439}
440
441#[ext(name = SlowOperationExt, defunc)]
443pub impl<This> This
444where
445 This: SlowAlg,
446{
447 async fn shop_slow(&self) -> String {
453 self.slow().await
454 }
455
456 async fn shop_pause(&self) -> String {
462 self.pause().await
463 }
464}
465
466#[ext(name = LifecycleApiExt, defunc(via = http))]
473pub impl<This> This
474where
475 This: HttpApiAlg + JsonOutAlg + TextOutAlg,
476{
477 fn lifecycle_api<Alg>(&self)
479 where
480 Alg: ShopAlg + SlowAlg,
481 {
482 self.routes()
483 .get("/items", self.op(Alg::shop_items).json())
485 .get("/slow", self.op(Alg::shop_slow).text())
488 .get("/pause", self.op(Alg::shop_pause).text())
491 }
492}
493
494#[derive(Debug, Default)]
499pub struct Upload {
500 pub stated: Vec<String>,
502}
503
504impl<Parts> FromPartsAlg<Parts> for Upload
505where
506 Parts: ChunksAlg + Send,
507 Parts::Error: Display + Send,
508 Parts::Chunk: PartAlg + Send,
509 <Parts::Chunk as PartAlg>::Content: ChunksAlg<Chunk = Vec<u8>> + Send + 'static,
510 <<Parts::Chunk as PartAlg>::Content as ChunksAlg>::Error: Display,
511{
512 type Error = String;
513
514 async fn from_parts(mut parts: Parts) -> Result<Self, Self::Error> {
515 let mut stated = Vec::new();
516 while let Some(part) = parts.next_chunk().await {
517 let part = part.map_err(|error| error.to_string())?;
518 let name = part.part_name().unwrap_or_default().to_owned();
519 let carried = part.part_content().gathered().await.map_err(|error| error.to_string())?;
520 let carried = String::from_utf8_lossy(&carried.concat()).into_owned();
521 stated.push(format!("{name}={carried}"));
522 }
523
524 Ok(Self { stated })
525 }
526}
527
528pub trait UploadAlg {
530 fn uploaded(&self, stated: Vec<String>) -> impl Future<Output = String> + Send;
532}
533
534impl UploadAlg for Shop {
535 async fn uploaded(&self, stated: Vec<String>) -> String {
536 stated.join(",")
537 }
538}
539
540#[ext(name = UploadOperationExt, defunc)]
542pub impl<This> This
543where
544 This: UploadAlg,
545{
546 async fn shop_upload(&self, upload: Upload) -> String {
548 self.uploaded(upload.stated).await
549 }
550}
551
552#[ext(name = MultipartApiExt, defunc(via = http))]
554pub impl<This> This
555where
556 This: HttpApiAlg + TextOutAlg,
557{
558 fn multipart_api<Alg>(&self)
560 where
561 Alg: UploadAlg,
562 {
563 self.routes().post("/upload", self.op(Alg::shop_upload).multipart::<Upload>().text())
564 }
565}