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}