use std::collections::BTreeMap;
use crate::error::{self, Result};
pub(crate) fn parse(spec: &str) -> Result<(String, String)> {
let Some((key, value)) = spec.split_once('=') else {
return Err(error::invalid(format!(
"{spec:?} is not a label; a label is spelled KEY=VALUE"
)));
};
check(key, value)?;
Ok((key.to_owned(), value.to_owned()))
}
pub(crate) fn parse_all(specs: &[String]) -> Result<BTreeMap<String, String>> {
let mut labels = BTreeMap::new();
for spec in specs {
let (key, value) = parse(spec)?;
if labels.contains_key(&key) {
return Err(error::invalid(format!(
"the label key {key:?} is given twice; a session carries one value per key"
)));
}
labels.insert(key, value);
}
Ok(labels)
}
pub(crate) fn validate(labels: &BTreeMap<String, String>) -> Result<()> {
for (key, value) in labels {
check(key, value)?;
}
Ok(())
}
pub(crate) fn matches(
carried: &BTreeMap<String, String>,
wanted: &BTreeMap<String, String>,
) -> bool {
wanted
.iter()
.all(|(key, value)| carried.get(key) == Some(value))
}
fn check(key: &str, value: &str) -> Result<()> {
if key.is_empty()
|| !key
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err(error::invalid(format!(
"{key:?} is not a label key; a key is one or more of A-Z, a-z, 0-9, `_` and `-`"
)));
}
if value.contains('\n') {
return Err(error::invalid(format!(
"the value of label {key:?} holds a newline; a label's value is one line"
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_value_may_hold_an_equals_sign_and_a_key_may_not() {
assert_eq!(
parse("run=a=b").expect("the first `=` splits"),
("run".to_owned(), "a=b".to_owned())
);
assert!(parse("no separator").is_err());
assert!(parse("=value").is_err());
assert!(parse("bad key=value").is_err());
assert!(parse("run=two\nlines").is_err());
}
#[test]
fn a_repeated_key_is_refused_and_an_empty_value_is_not() {
assert!(parse_all(&["run=1".to_owned(), "run=2".to_owned()]).is_err());
let labels = parse_all(&["run=".to_owned(), "node=x".to_owned()]).expect("two labels");
assert_eq!(labels.get("run").map(String::as_str), Some(""));
assert!(matches(
&labels,
&BTreeMap::from([("node".to_owned(), "x".to_owned())])
));
assert!(!matches(
&labels,
&BTreeMap::from([("node".to_owned(), "y".to_owned())])
));
assert!(matches(&labels, &BTreeMap::new()));
}
}