1//! What the tablet and the service say to each other on the Jev-backed routes (`/check`, `/embed`, 2//! `/rerank`). It lives in the core, not in the guard, because both ends need it and the service 3//! must be able to build for a Worker, which the guard's HTTP client cannot. 4 5use serde::{Deserialize, Serialize}; 6 7use crate::profile::Age; 8use crate::types::{Direction, Verdict}; 9 10/// What the tablet sends the guard service: one message, which way it is going, and the age of 11/// the child it is judged for. There is no name on the wire: the service does not need one. 12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 13pub struct CheckRequest { 14 pub direction: Direction, 15 /// Optional; absent, the service judges for the youngest supported age, the strictest. An age outside the supported range does not parse, and so is refused. 16 #[serde(default)] 17 pub age: Option<Age>, 18 pub text: String, 19} 20 21impl CheckRequest { 22 pub fn age(&self) -> Age { 23 self.age.unwrap_or(Age::YOUNGEST) 24 } 25} 26 27/// The service answers a verdict, or says it could not decide. "Could not 28/// decide" is not an `Allow`: the client turns it into a `GuardError`. 29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 30pub enum CheckReply { 31 Verdict(Verdict), 32 Unavailable(String), 33} 34 35/// Texts to embed. 36#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 37pub struct EmbedRequest { 38 pub texts: Vec<String>, 39} 40 41#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 42pub enum EmbedReply { 43 Vectors(Vec<Vec<f32>>), 44 Unavailable(String), 45} 46 47/// A message and the shortlist of memories to order for it. 48#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 49pub struct RankRequest { 50 pub query: String, 51 pub candidates: Vec<String>, 52} 53 54#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 55pub enum RankReply { 56 Probabilities(Vec<f32>), 57 Unavailable(String), 58} 59 60/// The text of one fact, to be given a picture. A fact is what Whiskers remembers about the child, so the 61/// request carries no age and no name: the picture is chosen for the words alone. 62#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 63pub struct IconRequest { 64 pub text: String, 65} 66 67/// The picture chosen for a fact, or that none of the admitted ones fits, or that it could not be decided. 68/// "Could not decide" is not "none fits": the first is tried again later, the second is an answer. 69#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 70pub enum IconReply { 71 Icon(whiskers_icons::IconId), 72 NoneFits, 73 Unavailable(String), 74} 75 76#[cfg(test)] 77mod tests { 78 use super::*; 79 80 #[test] 81 fn an_icon_reply_names_an_admitted_icon_or_it_does_not_parse() { 82 let r: IconReply = serde_json::from_str(r#"{"Icon":"cat"}"#).unwrap(); 83 assert_eq!(r, IconReply::Icon(whiskers_icons::IconId::new("cat").unwrap())); 84 assert_eq!(serde_json::from_str::<IconReply>(r#""NoneFits""#).unwrap(), IconReply::NoneFits); 85 assert!(serde_json::from_str::<IconReply>(r#"{"Icon":"beer"}"#).is_err()); 86 assert_eq!(serde_json::to_string(&IconRequest { text: "x".into() }).unwrap(), r#"{"text":"x"}"#); 87 } 88 89 #[test] 90 fn a_request_without_an_age_is_judged_for_the_youngest() { 91 let r: CheckRequest = serde_json::from_str(r#"{"direction":"FromChild","text":"hi"}"#).unwrap(); 92 assert_eq!(r.age(), Age::YOUNGEST); 93 } 94 95 #[test] 96 fn an_age_outside_the_range_does_not_parse() { 97 assert!(serde_json::from_str::<CheckRequest>(r#"{"direction":"FromChild","age":2,"text":"hi"}"#).is_err()); 98 assert!(serde_json::from_str::<CheckRequest>(r#"{"direction":"FromChild","age":200,"text":"hi"}"#).is_err()); 99 } 100 101 #[test] 102 fn the_request_carries_the_age_and_nothing_that_could_be_a_name() { 103 let r = CheckRequest { direction: Direction::ToChild, age: Some(Age::new(6).unwrap()), text: "hi".into() }; 104 assert_eq!(serde_json::to_string(&r).unwrap(), r#"{"direction":"ToChild","age":6,"text":"hi"}"#); 105 } 106}