use std::collections::HashMap;
use super::flow::{ParamDecl, ParamKind};
use crate::i18n::{Strings, fill};
pub type ParamValues = HashMap<String, String>;
pub fn value_for(decl: &ParamDecl, chosen: &ParamValues, s: &Strings) -> Result<String, String> {
let value = match chosen.get(&decl.name) {
Some(v) => v.clone(),
None => match &decl.default {
Some(d) => d.clone(),
None => return Err(fill(s.param_required, &[&decl.prompt(), &decl.name])),
},
};
check(decl, &value, s)?;
Ok(value)
}
pub fn effective(decls: &[&ParamDecl], chosen: &ParamValues) -> ParamValues {
decls
.iter()
.filter_map(|d| {
let v = chosen.get(&d.name).or(d.default.as_ref())?;
Some((d.name.clone(), v.clone()))
})
.collect()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParamRow {
pub name: String,
pub prompt: String,
pub kind: ParamKind,
pub value: String,
pub problem: Option<String>,
}
pub fn rows(decls: &[&ParamDecl], chosen: &ParamValues, s: &Strings) -> Vec<ParamRow> {
decls
.iter()
.map(|p| {
let value = chosen
.get(&p.name)
.cloned()
.or_else(|| p.default.clone())
.unwrap_or_default();
ParamRow {
name: p.name.clone(),
prompt: p.prompt(),
kind: p.kind.clone(),
problem: check(p, &value, s).err().or_else(|| {
(value.trim().is_empty() && p.default.is_none())
.then(|| s.param_row_required.to_string())
}),
value,
}
})
.collect()
}
pub fn check(decl: &ParamDecl, value: &str, s: &Strings) -> Result<(), String> {
let trimmed = value.trim();
if trimmed.contains("{{") {
return Ok(());
}
match &decl.kind {
ParamKind::Choice(options) if !options.is_empty() => {
if !options.iter().any(|o| o == trimmed) {
return Err(fill(
s.param_not_a_choice,
&[&decl.prompt(), trimmed, &options.join(", ")],
));
}
}
ParamKind::Number => {
if trimmed.is_empty() || trimmed.parse::<f64>().is_err() {
return Err(fill(s.param_not_a_number, &[&decl.prompt(), trimmed]));
}
}
_ => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::report::parser::parse_flow;
fn decl(src: &str) -> ParamDecl {
let flow = parse_flow(&format!("{src}\n")).expect("parses");
match &flow.nodes[0] {
super::super::flow::FlowNode::Param(p) => p.clone(),
other => panic!("expected Param, got {other:?}"),
}
}
#[test]
fn a_required_parameter_stops_the_run_rather_than_running_empty() {
let s = Strings::english();
let d = decl("PARAM TEXT TICKET_REF");
let err = value_for(&d, &ParamValues::new(), s).unwrap_err();
assert!(err.contains("Ticket ref"), "{err}");
let mut chosen = ParamValues::new();
chosen.insert("TICKET_REF".into(), "T-1".into());
assert_eq!(value_for(&d, &chosen, s).unwrap(), "T-1");
}
#[test]
fn a_chosen_value_is_held_to_the_same_rules_as_a_default() {
let s = Strings::english();
let d = decl("PARAM CHOICE(\"v4.2\", \"v4.3\") VERSION = \"v4.3\"");
let mut chosen = ParamValues::new();
chosen.insert("VERSION".into(), "v9".into());
let err = value_for(&d, &chosen, s).unwrap_err();
assert!(err.contains("v9") && err.contains("v4.2, v4.3"), "{err}");
let n = decl("PARAM NUMBER TRIES = \"3\"");
chosen.insert("TRIES".into(), "lots".into());
assert!(value_for(&n, &chosen, s).is_err());
chosen.insert("TRIES".into(), "5".into());
assert_eq!(value_for(&n, &chosen, s).unwrap(), "5");
}
#[test]
fn a_supplied_value_beats_the_declared_default() {
let s = Strings::english();
let d = decl("PARAM ENV TARGET = \"staging\"");
assert_eq!(value_for(&d, &ParamValues::new(), s).unwrap(), "staging");
let mut chosen = ParamValues::new();
chosen.insert("TARGET".into(), "prod".into());
assert_eq!(value_for(&d, &chosen, s).unwrap(), "prod");
}
}