use crate::{CapabilityId, Config, ConfigError, IdError};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Reason {
Missing,
Wrong,
Untested,
}
impl Reason {
pub fn as_str(self) -> &'static str {
match self {
Reason::Missing => "missing",
Reason::Wrong => "wrong",
Reason::Untested => "untested",
}
}
}
impl fmt::Display for Reason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
NoHeader,
MissingColumn(&'static str),
FieldCount {
line: usize,
want: usize,
got: usize,
},
BadId { line: usize, error: IdError },
BadConfig {
line: usize,
column: &'static str,
error: ConfigError,
},
BadEnabled { line: usize, value: String },
BadReason { line: usize, value: String },
ReasonMismatch { line: usize },
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::NoHeader => write!(f, "matrix has no header line"),
ParseError::MissingColumn(c) => write!(f, "matrix header lacks column {c:?}"),
ParseError::FieldCount { line, want, got } => {
write!(f, "line {line}: {got} fields, header has {want}")
}
ParseError::BadId { line, error } => write!(f, "line {line}: subject: {error}"),
ParseError::BadConfig {
line,
column,
error,
} => write!(f, "line {line}: {column}: {error}"),
ParseError::BadEnabled { line, value } => {
write!(f, "line {line}: enabled is {value:?}, want yes or no")
}
ParseError::BadReason { line, value } => {
write!(f, "line {line}: reason {value:?} is unknown")
}
ParseError::ReasonMismatch { line } => {
write!(
f,
"line {line}: a no needs a reason and a yes must have none"
)
}
}
}
}
impl std::error::Error for ParseError {}
pub(crate) const COLUMNS: [&str; 9] = [
"subject_kind",
"subject",
"backend",
"version",
"config",
"enabled",
"reason",
"metadata",
"note",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Row {
pub subject_kind: String,
pub subject: CapabilityId,
pub backend: String,
pub version: String,
pub config: Config,
pub enabled: bool,
pub reason: Option<Reason>,
pub metadata: Config,
pub note: String,
pub extra: BTreeMap<String, String>,
}
impl Row {
pub(crate) fn parse_fields(
header: &[&str],
fields: &[&str],
line: usize,
) -> Result<Row, ParseError> {
let idx = |name: &'static str| -> Result<usize, ParseError> {
header
.iter()
.position(|h| *h == name)
.ok_or(ParseError::MissingColumn(name))
};
let get = |name: &'static str| -> Result<&str, ParseError> { Ok(fields[idx(name)?]) };
let cfg = |name: &'static str| -> Result<Config, ParseError> {
Config::parse(get(name)?).map_err(|error| ParseError::BadConfig {
line,
column: name,
error,
})
};
let enabled = match get("enabled")? {
"yes" => true,
"no" => false,
other => {
return Err(ParseError::BadEnabled {
line,
value: other.to_string(),
});
}
};
let reason = match get("reason")? {
"" => None,
"missing" => Some(Reason::Missing),
"wrong" => Some(Reason::Wrong),
"untested" => Some(Reason::Untested),
other => {
return Err(ParseError::BadReason {
line,
value: other.to_string(),
});
}
};
if enabled == reason.is_some() {
return Err(ParseError::ReasonMismatch { line });
}
let extra = header
.iter()
.zip(fields)
.filter(|(h, _)| !COLUMNS.contains(h))
.map(|(h, v)| (h.to_string(), v.to_string()))
.collect();
Ok(Row {
subject_kind: get("subject_kind")?.to_string(),
subject: CapabilityId::new(get("subject")?)
.map_err(|error| ParseError::BadId { line, error })?,
backend: get("backend")?.to_string(),
version: get("version")?.to_string(),
config: cfg("config")?,
enabled,
reason,
metadata: cfg("metadata")?,
note: get("note")?.to_string(),
extra,
})
}
pub(crate) fn field(&self, column: &str) -> String {
match column {
"subject_kind" => self.subject_kind.clone(),
"subject" => self.subject.to_string(),
"backend" => self.backend.clone(),
"version" => self.version.clone(),
"config" => self.config.to_string(),
"enabled" => if self.enabled { "yes" } else { "no" }.to_string(),
"reason" => self
.reason
.map(|r| r.as_str().to_string())
.unwrap_or_default(),
"metadata" => self.metadata.to_string(),
"note" => self.note.clone(),
other => self.extra.get(other).cloned().unwrap_or_default(),
}
}
}