lib.rsannotatedlib.rssource128 lines · 4.6 KB · raw
1//! What Whiskers' service does with a request, generic over the capabilities it runs on. See README.md.
2
3#![forbid(unsafe_code)]
4
5mod http;
6pub mod routes;
7
8pub use http::{Body, Content, MAX_BODY, Method, Request, Response};
9
10use ::log::warn;
11use routes::{Check, ChatSync, Embed, HouseholdSync, Journal, MemorySync, Messages, Pictures, PickIcon, Rerank, Route, Routes, Speak, Then, Usage};
12use whiskers_ports::Backend;
13
14/// Whiskers' service for one household: an adapter, and the routes it has been given.
15///
16/// A new service serves nothing. Routes are given with [`serve`](Self::serve), and a route can be given only
17/// to an adapter that has the capabilities it needs, which the compiler checks. An adapter that holds a
18/// memory hub can be given `/memory/sync`:
19///
20/// ```
21/// use whiskers_conformance::memory::MemMemoryHub;
22/// use whiskers_ports::HasMemory;
23/// use whiskers_service::{Service, routes::MemorySync};
24///
25/// struct Hubs(MemMemoryHub);
26/// impl HasMemory for Hubs {
27///     type Memory = MemMemoryHub;
28///     fn memory(&self) -> &MemMemoryHub { &self.0 }
29/// }
30///
31/// let _ = Service::new(Hubs(MemMemoryHub::default())).serve(MemorySync);
32/// ```
33///
34/// One that has only a clock cannot. There is no "not configured" answer to forget: the route does not exist.
35///
36/// ```compile_fail
37/// use whiskers_ports::{HasClock, ManualClock};
38/// use whiskers_service::{Service, routes::MemorySync};
39///
40/// struct OnlyAClock(ManualClock);
41/// impl HasClock for OnlyAClock {
42///     type Clock = ManualClock;
43///     fn clock(&self) -> &ManualClock { &self.0 }
44/// }
45///
46/// // error[E0277]: the trait bound `OnlyAClock: HasMemory` is not satisfied
47/// let _ = Service::new(OnlyAClock(ManualClock::default())).serve(MemorySync);
48/// ```
49///
50/// [`Service::complete`] gives every route to an adapter that has every capability (a [`Backend`]).
51pub struct Service<C, R = ()> {
52    adapter: C,
53    routes: R,
54}
55
56impl<C> Service<C> {
57    /// A service on `adapter` that serves no route yet.
58    pub fn new(adapter: C) -> Self {
59        Self { adapter, routes: () }
60    }
61}
62
63/// Every route, in the order [`Service::complete`] gives them. Naming it is how code that holds a complete
64/// service (`whiskersd`) writes its type.
65pub type Complete = Then<PickIcon, Then<Messages, Then<Speak, Then<Usage, Then<Pictures, Then<Journal, Then<ChatSync, Then<HouseholdSync, Then<MemorySync, Then<Rerank, Then<Embed, Then<Check, ()>>>>>>>>>>>>;
66
67impl<C: Backend> Service<C, Complete> {
68    /// The service with every route, which only an adapter with every capability can have. A capability added
69    /// to [`Backend`] is a compile error here until the adapter has it.
70    pub fn complete(adapter: C) -> Self {
71        Service::<C>::new(adapter)
72            .serve(Check)
73            .serve(Embed)
74            .serve(Rerank)
75            .serve(MemorySync)
76            .serve(HouseholdSync)
77            .serve(ChatSync)
78            .serve(Journal)
79            .serve(Pictures)
80            .serve(Usage)
81            .serve(Speak::default())
82            .serve(Messages)
83            .serve(PickIcon)
84    }
85}
86
87impl<C, R> Service<C, R> {
88    /// Gives the service `route`. The route's own `Route<C>` impl names the capabilities it needs, so this
89    /// does not compile for an adapter that lacks one of them.
90    pub fn serve<X: Route<C>>(self, route: X) -> Service<C, Then<X, R>> {
91        Service { adapter: self.adapter, routes: Then(route, self.routes) }
92    }
93
94    /// The adapter the service runs on.
95    pub fn backend(&self) -> &C {
96        &self.adapter
97    }
98}
99
100impl<C, R: Routes<C>> Service<C, R> {
101    /// Answers one request. Every outcome is a [`Response`]; nothing here panics on what a device sends. A
102    /// path no given route answers is a 404.
103    pub async fn handle(&self, request: Request) -> Response {
104        let Request { method, path, body } = request;
105        if method != Method::Post {
106            warn!("{method:?} {path} is not a POST");
107            return Response::empty(405);
108        }
109        let body = match body {
110            Body::Text(text) => text,
111            Body::Unreadable(why) => {
112                warn!("{path}: the request body could not be read (a device that dropped off wifi?): {why}");
113                return Response::empty(400);
114            }
115        };
116        if body.len() > MAX_BODY {
117            warn!("{path}: the request body is over the {MAX_BODY} byte limit");
118            return Response::empty(400);
119        }
120        match self.routes.dispatch(&self.adapter, &path, &body).await {
121            Some(response) => response,
122            None => {
123                warn!("no such route: {path}");
124                Response::empty(404)
125            }
126        }
127    }
128}