wire/frames/ask.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! A question and its answer, paired by the number the asking side
7//! minted rather than by what the answer happens to contain.
8//!
9//! The server echoes an [`AskId`] and never judges it: pairing is the
10//! page's concern, and a server that checked uniqueness would be a second
11//! place deciding which answer belongs to which question.
12
13use kernel::{AxError, Seq};
14use serde::{Deserialize, Serialize};
15
16use super::{Answer, Query};
17
18/// The asking side's own number for one question, minted monotonically
19/// per connection and echoed on the answer.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(transparent)]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23pub struct AskId(pub u32);
24
25/// One question, carrying the number its answer will come back under.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28pub struct Ask {
29 pub ask_id: AskId,
30 pub query: Query,
31}
32
33/// The reply to one [`Ask`].
34///
35/// `as_of` is the first seq the answer does not reflect: the answer holds
36/// every record before it and none from it on. It and the answer are read
37/// under the same view lock, and a view that has folded nothing answers
38/// with `Seq::FIRST`, so genesis is never mistaken for already folded.
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
41pub struct Answered {
42 pub ask_id: AskId,
43 pub as_of: Seq,
44 pub outcome: AskOutcome,
45}
46
47/// Whether the question was answered or refused. A refusal of a question
48/// travels here rather than as a bare `Refusal` frame, so the page knows
49/// which question fell through.
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case")]
52#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
53pub enum AskOutcome {
54 Answer(Answer),
55 Refusal(AxError),
56}
57
58#[cfg(test)]
59#[allow(clippy::unwrap_used, reason = "test code")]
60mod tests {
61 use super::super::{ClientFrame, Query};
62 use super::{Ask, AskId};
63
64 #[test]
65 fn a_client_frame_round_trips_through_json() {
66 let frame = ClientFrame::Ask(Ask {
67 ask_id: AskId(7),
68 query: Query::CityView,
69 });
70 let text = serde_json::to_string(&frame).unwrap();
71 let back: ClientFrame = serde_json::from_str(&text).unwrap();
72 assert_eq!(frame, back);
73 }
74}