1//! One file per family of routes. Each route is a type that says, in its `Route` impl, which capabilities 2//! it needs; the service serves it only for an adapter that has them (see `crate::Service::serve`). 3 4mod icon; 5mod jev; 6mod journal; 7mod messages; 8mod pictures; 9mod speak; 10mod sync; 11mod usage; 12 13use crate::Response; 14 15pub use icon::PickIcon; 16pub use jev::{Check, Embed, Rerank}; 17pub use journal::Journal; 18pub use messages::Messages; 19pub use pictures::Pictures; 20pub use speak::Speak; 21pub use sync::{ChatSync, HouseholdSync, MemorySync}; 22pub use usage::Usage; 23 24mod sealed { 25 pub trait Sealed {} 26} 27 28/// A route, or a family of routes that share their work, and what it takes to serve it. 29/// 30/// `impl<C: HasJudge + HasClock> Route<C> for Check` is the whole statement of what `/check` needs: a 31/// service on an adapter without those capabilities cannot be given the route, so cannot answer it. The 32/// trait is sealed; a route is added to this crate, with its laws in `whiskers-conformance` and its 33/// capability in `whiskers-ports`. 34#[expect(async_fn_in_trait, reason = "a Worker's futures hold JavaScript values and cannot be Send, so no Send bound may be required here")] 35pub trait Route<C>: sealed::Sealed { 36 /// The paths this route answers. Routes do not share a path. 37 const PATHS: &'static [&'static str]; 38 39 /// Answers a request for one of `PATHS`, which the service has already found to be a readable POST. 40 async fn answer(&self, adapter: &C, path: &str, body: &str) -> Response; 41} 42 43/// The routes a service has been given: built by [`crate::Service::serve`], never written out. 44#[expect(async_fn_in_trait, reason = "a Worker's futures hold JavaScript values and cannot be Send, so no Send bound may be required here")] 45pub trait Routes<C> { 46 /// The response of the route that answers `path`, or `None` if no route given to the service does. 47 async fn dispatch(&self, adapter: &C, path: &str, body: &str) -> Option<Response>; 48} 49 50impl<C> Routes<C> for () { 51 async fn dispatch(&self, _: &C, _: &str, _: &str) -> Option<Response> { 52 None 53 } 54} 55 56/// A route given to the service, and the routes given before it. 57pub struct Then<R, Rest>(pub(crate) R, pub(crate) Rest); 58 59impl<C, R: Route<C>, Rest: Routes<C>> Routes<C> for Then<R, Rest> { 60 async fn dispatch(&self, adapter: &C, path: &str, body: &str) -> Option<Response> { 61 if R::PATHS.contains(&path) { Some(self.0.answer(adapter, path, body).await) } else { self.1.dispatch(adapter, path, body).await } 62 } 63} 64 65/// Why a body did not parse, without a word of it: serde's own message quotes the offending value 66/// (`invalid type: string "..."`), and a body here is what a child said or what Whiskers remembers. 67pub(crate) fn kind_of(e: &serde_json::Error) -> String { 68 format!("{:?} at line {} column {}", e.classify(), e.line(), e.column()) 69}