Skip to main content

amethystate/observability/
introspect.rs

1//! Walking a declared struct's fields at runtime.
2//!
3//! [`InspectorBackend`](crate::store::InspectorBackend) asks the store what is
4//! on disk, from outside the program that wrote it. This asks the other side,
5//! from inside: what the declaration says, and what the struct is holding right
6//! now.
7//!
8//! Nothing here is typed. A caller that knows the type reads the field; this is
9//! for the callers that do not - a screen that draws a row per setting, a dump
10//! in a bug report, a terminal that lists what an application has.
11
12use std::fmt;
13
14use crate::migration::fields::Role;
15use amethystate_core::path::StorePath;
16
17/// One field, as somebody looking at it sees it.
18pub struct FieldView<'a> {
19    /// The name in the source: what `state.font_size` is called in Rust.
20    pub declared: &'static str,
21
22    /// The name it is stored under, which is `declared` unless `path` or
23    /// `rename_all` said otherwise. A dot in it is a level.
24    pub stored: &'static str,
25
26    /// Where it is, whole. What a person editing the file is looking at.
27    pub at: StorePath,
28
29    /// The type it was declared as, written the way it was written.
30    pub type_name: &'static str,
31
32    /// The doc comment on the field, empty where there was none.
33    ///
34    /// **A description, not a label.** It is in whatever language it was
35    /// written in and fixed when the crate was compiled, so it belongs in a
36    /// dump, a developer's terminal or generated documentation - not in a
37    /// shipped interface. What a settings screen wants is a string from its
38    /// own catalogue, and what it should look that string up by is
39    /// [`at`](FieldView::at): the stored path survives a Rust rename, and
40    /// changes only when the data moves, which is a migration either way.
41    pub described: &'static str,
42
43    pub role: Role,
44
45    /// Whether it is kept in memory and never stored.
46    pub volatile: bool,
47
48    /// What it holds, rendered: the value for a leaf, how many entries for a
49    /// map, and `<opaque>` where the type cannot be printed at all.
50    ///
51    /// Rendered rather than borrowed because a field hands its value back by
52    /// value - there is no reference to lend - and this is for showing.
53    pub shown: String,
54
55    /// What the store and this field disagree about, if anything.
56    pub disagreement: Option<Disagreement>,
57
58    /// The fields under this one, for a `nested` field.
59    pub inside: Option<&'a dyn Inspect>,
60}
61
62/// What a field and the store do not agree about.
63///
64/// The value is not what the field reports: either the store holds something
65/// the field cannot read, or a declared rule refused it. Either way the field
66/// is reporting its default or the last thing it held, and the stored bytes are
67/// where they were - so it can be looked at, and fixed.
68#[derive(Debug, Clone)]
69pub struct Disagreement {
70    pub at: StorePath,
71    pub reason: Reason,
72}
73
74/// `non_exhaustive` for the same reason [`StorageError`] is: it is a list of
75/// what the store can be found disagreeing about, it grows, and it is read
76/// rather than dispatched on.
77///
78/// [`StorageError`]: crate::store::StorageError
79#[derive(Debug, Clone)]
80#[non_exhaustive]
81pub enum Reason {
82    /// What is stored will not read back as the declared type.
83    WillNotRead(std::sync::Arc<str>),
84
85    /// It read, and a declared rule refused it.
86    Refused(std::sync::Arc<str>),
87
88    /// The field never wrote its default, because the store already held
89    /// something at its path that seeding would have destroyed - so the two
90    /// have disagreed from the first moment the field existed.
91    Occupied(std::sync::Arc<str>),
92
93    /// The store has let go of its file, so what the field holds is the last
94    /// thing it heard rather than what is on disk.
95    Closed,
96
97    /// A declared rule put a stored value right, and the store would not take
98    /// the correction back: the field holds what the rule made of it, the store
99    /// what it was given.
100    NotWrittenBack(std::sync::Arc<str>),
101}
102
103impl fmt::Display for Reason {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Reason::WillNotRead(why) => write!(f, "will not read: {why}"),
107            Reason::Refused(why) => write!(f, "refused: {why}"),
108            Reason::Occupied(why) => write!(f, "already held something else: {why}"),
109            Reason::Closed => f.write_str("the store was closed, so this is the last it heard"),
110            Reason::NotWrittenBack(why) => {
111                write!(
112                    f,
113                    "put right by its rule, and the store would not take it back: {why}"
114                )
115            }
116        }
117    }
118}
119
120impl fmt::Display for Disagreement {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        let Self { at, reason } = self;
123        write!(f, "{at} {reason}")
124    }
125}
126
127impl std::error::Error for Disagreement {}
128
129/// A struct that can list its own fields.
130///
131/// Implemented by `#[amethystate]` for every struct it generates.
132pub trait Inspect {
133    /// Where this struct's fields hang.
134    fn at(&self) -> StorePath;
135
136    /// How many fields were declared.
137    fn field_count(&self) -> usize;
138
139    /// One of them, by position. `None` past the end.
140    fn field_at(&self, index: usize) -> Option<FieldView<'_>>;
141
142    /// All of them, in the order they were declared.
143    fn fields(&self) -> Fields<'_>
144    where
145        Self: Sized,
146    {
147        Fields {
148            of: self,
149            next: 0,
150            left: self.field_count(),
151        }
152    }
153
154    /// One of them by its name in the source.
155    fn field(&self, declared: &str) -> Option<FieldView<'_>>
156    where
157        Self: Sized,
158    {
159        (0..self.field_count())
160            .filter_map(|i| self.field_at(i))
161            .find(|view| view.declared == declared)
162    }
163}
164
165pub struct Fields<'a> {
166    of: &'a dyn Inspect,
167    next: usize,
168    left: usize,
169}
170
171impl<'a> Iterator for Fields<'a> {
172    type Item = FieldView<'a>;
173
174    fn next(&mut self) -> Option<Self::Item> {
175        let view = self.of.field_at(self.next)?;
176        self.next += 1;
177        self.left -= 1;
178        Some(view)
179    }
180
181    fn size_hint(&self) -> (usize, Option<usize>) {
182        (self.left, Some(self.left))
183    }
184}
185
186impl ExactSizeIterator for Fields<'_> {}