mod.rsannotatedmod.rssource69 lines · 2.8 KB · raw
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}