Skip to main content

persistant_core/
row.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2use crate::{CapabilityId, Config, ConfigError, IdError};
3use std::collections::BTreeMap;
4use std::fmt;
5
6/// Why a capability is off.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[non_exhaustive]
9pub enum Reason {
10    /// The backend does not give it (or the test found nothing there).
11    Missing,
12    /// The backend gives something else: the result broke the contract.
13    Wrong,
14    /// No test ran for this `(backend, version, config)`.
15    Untested,
16}
17
18impl Reason {
19    /// The word used in the matrix file.
20    pub fn as_str(self) -> &'static str {
21        match self {
22            Reason::Missing => "missing",
23            Reason::Wrong => "wrong",
24            Reason::Untested => "untested",
25        }
26    }
27}
28
29impl fmt::Display for Reason {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.write_str(self.as_str())
32    }
33}
34
35/// Why matrix text could not be read.
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[non_exhaustive]
38pub enum ParseError {
39    /// No header line.
40    NoHeader,
41    /// A required column is not in the header.
42    MissingColumn(&'static str),
43    /// A line has a different number of tab-separated fields than the header.
44    FieldCount {
45        line: usize,
46        want: usize,
47        got: usize,
48    },
49    /// `subject` is not a valid id.
50    BadId { line: usize, error: IdError },
51    /// `config` or `metadata` is not `k=v;k=v`.
52    BadConfig {
53        line: usize,
54        column: &'static str,
55        error: ConfigError,
56    },
57    /// `enabled` is not `yes` or `no`.
58    BadEnabled { line: usize, value: String },
59    /// `reason` is not `missing`, `wrong` or `untested`.
60    BadReason { line: usize, value: String },
61    /// `enabled=no` without a reason, or `enabled=yes` with one.
62    ReasonMismatch { line: usize },
63    /// The header names a column twice.
64    DuplicateColumn(String),
65    /// `probed_at` is not `YYYY-MM-DDTHH:MM:SSZ`.
66    BadTimestamp { line: usize, value: String },
67    /// Any other refusal by the file reader, in its words.
68    Other(String),
69}
70
71impl fmt::Display for ParseError {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            ParseError::NoHeader => write!(f, "matrix has no header line"),
75            ParseError::MissingColumn(c) => write!(f, "matrix header lacks column {c:?}"),
76            ParseError::FieldCount { line, want, got } => {
77                write!(f, "line {line}: {got} fields, header has {want}")
78            }
79            ParseError::BadId { line, error } => write!(f, "line {line}: subject: {error}"),
80            ParseError::BadConfig {
81                line,
82                column,
83                error,
84            } => write!(f, "line {line}: {column}: {error}"),
85            ParseError::BadEnabled { line, value } => {
86                write!(f, "line {line}: enabled is {value:?}, want yes or no")
87            }
88            ParseError::BadReason { line, value } => {
89                write!(f, "line {line}: reason {value:?} is unknown")
90            }
91            ParseError::ReasonMismatch { line } => {
92                write!(
93                    f,
94                    "line {line}: a no needs a reason and a yes must have none"
95                )
96            }
97            ParseError::DuplicateColumn(c) => write!(f, "matrix header names column {c:?} twice"),
98            ParseError::BadTimestamp { line, value } => {
99                write!(
100                    f,
101                    "line {line}: probed_at {value:?} is not YYYY-MM-DDTHH:MM:SSZ"
102                )
103            }
104            ParseError::Other(m) => f.write_str(m),
105        }
106    }
107}
108
109impl std::error::Error for ParseError {}
110
111/// One recorded fact: a test ran, for this subject, on this backend, version and config.
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct Row {
114    /// `capability`, `construct` or another word the writer chose; not interpreted here.
115    pub subject_kind: String,
116    /// The leaf id.
117    pub subject: CapabilityId,
118    /// The driver or dialect crate's backend name.
119    pub backend: String,
120    /// The backend (server) version string. No row for a version means off.
121    pub version: String,
122    /// Facts the gate can read at open, under which this row holds.
123    pub config: Config,
124    /// Boolean outcome.
125    pub enabled: bool,
126    /// Present exactly when `enabled` is false.
127    pub reason: Option<Reason>,
128    /// Environment, trial parameters, test id, tier, suite (`k=v;k=v`).
129    pub metadata: Config,
130    /// Free text; may carry `allowance=<name>` first.
131    pub note: String,
132    /// Columns this crate does not know (UniSQL writes `from`), kept so a rewrite loses nothing.
133    pub extra: BTreeMap<String, String>,
134}