Skip to main content

zenkey_fleet/report/
asked.rs

1//! The absence vocabulary every domain below shares.
2//!
3//! [`Asked`] carries one distinction and nothing else: was the question put?
4//! It is here rather than in a domain file because every domain needs it and
5//! none of them owns it — a `topic` field and a `doctor` field must spell
6//! "not asked" the same way or the RFC 09 §5.1 O4 split is only local.
7//!
8//! [`u64_is_zero`] keeps company with it for the same reason: it is the
9//! `skip_serializing_if` predicate behind "absent at zero", the append rule
10//! that lets a counter be added to a shipped document without changing it
11//! for consumers who never see the counter fire.
12
13use serde::Serialize;
14
15/// "Was the question even put?" — the RFC 09 §5.1 O4 split (#246 / P1),
16/// made nominal (RFC 13, v1.24).
17///
18/// A generation of report fields spelled "not asked" as `Option::None`,
19/// which conflated it with every *other* absence the moment a field also had
20/// an asked-but-absent reading. This type carries exactly the not-asked
21/// distinction and nothing else:
22///
23/// * [`Asked::NotAsked`] — the flag was not passed, the sweep was not made,
24///   the question does not exist for this subject. On the wire it is
25///   **absence** (the field carries `skip_serializing_if` +
26///   `default`), byte-identical to the `Option` it replaced.
27/// * [`Asked::Asked`] — the question was put; the payload is the answer,
28///   serialized transparently (again exactly as `Some` did).
29///
30/// The split is the point: a field whose `None` means *asked and nothing
31/// was there* (an unstamped sample's age, a producer with no served slice)
32/// **stays `Option`** — wrapping it here would re-conflate in the other
33/// direction.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub enum Asked<T> {
36    /// The question was not put. Serializes as absence — not zero, not null,
37    /// not `[]` (RFC 09 §5.1 O4).
38    #[default]
39    NotAsked,
40    /// The question was put, and this is what came back — an empty answer
41    /// (`Asked(vec![])`, `Asked(0)`) is a real answer, distinct from
42    /// `NotAsked` on the wire and in the type.
43    Asked(T),
44}
45
46impl<T> Asked<T> {
47    /// The `skip_serializing_if` predicate: not-asked is absence.
48    pub fn is_not_asked(&self) -> bool {
49        matches!(self, Asked::NotAsked)
50    }
51
52    pub fn is_asked(&self) -> bool {
53        !self.is_not_asked()
54    }
55
56    /// The answer, if the question was put.
57    pub fn as_option(&self) -> Option<&T> {
58        match self {
59            Asked::NotAsked => None,
60            Asked::Asked(v) => Some(v),
61        }
62    }
63
64    pub fn into_option(self) -> Option<T> {
65        match self {
66            Asked::NotAsked => None,
67            Asked::Asked(v) => Some(v),
68        }
69    }
70
71    /// `Asked<Vec<T>>` → `Option<&[T]>` and friends, mirroring
72    /// `Option::as_deref`.
73    pub fn as_deref(&self) -> Option<&T::Target>
74    where
75        T: std::ops::Deref,
76    {
77        self.as_option().map(|v| v.deref())
78    }
79}
80
81impl<T: Copy> Asked<T> {
82    /// The answer by value, for `Copy` payloads.
83    pub fn get(&self) -> Option<T> {
84        self.as_option().copied()
85    }
86}
87
88/// `Option`'s not-asked reading, named: `None` → `NotAsked`, `Some` →
89/// `Asked` — the mechanical migration step for gated facts built with
90/// `flag.then(...)`.
91impl<T> From<Option<T>> for Asked<T> {
92    fn from(o: Option<T>) -> Asked<T> {
93        match o {
94            None => Asked::NotAsked,
95            Some(v) => Asked::Asked(v),
96        }
97    }
98}
99
100impl<T: Serialize> Serialize for Asked<T> {
101    /// `Asked` is transparent; `NotAsked` serializes as `null` — reached
102    /// only if a field forgets its `skip_serializing_if`, in which case it
103    /// degrades exactly as the `Option` it replaced would have.
104    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
105        match self {
106            Asked::NotAsked => s.serialize_none(),
107            Asked::Asked(v) => v.serialize(s),
108        }
109    }
110}
111
112/// `skip_serializing_if` helper: a zero here is "nothing dropped", which the
113/// absent field already says.
114pub(crate) fn u64_is_zero(n: &u64) -> bool {
115    *n == 0
116}