lib.rsannotatedlib.rssource128 lines · 4.6 KB · raw

What Whiskers' service does with a request, generic over the capabilities it runs on. See README.md.

3#![forbid(unsafe_code)]
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;

Whiskers' service for one household: an adapter, and the routes it has been given.

A new service serves nothing. Routes are given with serve, and a route can be given only to an adapter that has the capabilities it needs, which the compiler checks. An adapter that holds a memory hub can be given /memory/sync:

use whiskers_conformance::memory::MemMemoryHub;
use whiskers_ports::HasMemory;
use whiskers_service::{Service, routes::MemorySync};

struct Hubs(MemMemoryHub);
impl HasMemory for Hubs {
    type Memory = MemMemoryHub;
    fn memory(&self) -> &MemMemoryHub { &self.0 }
}

let _ = Service::new(Hubs(MemMemoryHub::default())).serve(MemorySync);

One that has only a clock cannot. There is no "not configured" answer to forget: the route does not exist.

use whiskers_ports::{HasClock, ManualClock};
use whiskers_service::{Service, routes::MemorySync};

struct OnlyAClock(ManualClock);
impl HasClock for OnlyAClock {
    type Clock = ManualClock;
    fn clock(&self) -> &ManualClock { &self.0 }
}

// error[E0277]: the trait bound `OnlyAClock: HasMemory` is not satisfied
let _ = Service::new(OnlyAClock(ManualClock::default())).serve(MemorySync);

[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}
56impl<C> Service<C> {

A service on adapter that serves no route yet.

58    pub fn new(adapter: C) -> Self {
59        Self { adapter, routes: () }
60    }
61}

Every route, in the order [Service::complete] gives them. Naming it is how code that holds a complete 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, ()>>>>>>>>>>>>;
67impl<C: Backend> Service<C, Complete> {

The service with every route, which only an adapter with every capability can have. A capability added 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}
87impl<C, R> Service<C, R> {

Gives the service route. The route's own Route<C> impl names the capabilities it needs, so this 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    }

The adapter the service runs on.

95    pub fn backend(&self) -> &C {
96        &self.adapter
97    }
98}
100impl<C, R: Routes<C>> Service<C, R> {

Answers one request. Every outcome is a [Response]; nothing here panics on what a device sends. A 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}