notify flows: refuse unknown placeholders and require the revision read on every save
This commit is contained in:
parent
7795da44f8
commit
ada364a482
7 changed files with 203 additions and 57 deletions
|
|
@ -291,6 +291,21 @@ fn is_field(name: &str) -> bool {
|
|||
FIELDS.contains(&name)
|
||||
}
|
||||
|
||||
/// The `{name}` placeholders in `template`, in order. A `{` with no `}` after
|
||||
/// it is plain text.
|
||||
#[must_use]
|
||||
pub fn placeholders(template: &str) -> Vec<&str> {
|
||||
let mut names = Vec::new();
|
||||
let mut rest = template;
|
||||
while let Some(open) = rest.find('{') {
|
||||
let after = &rest[open + 1..];
|
||||
let Some(close) = after.find('}') else { break };
|
||||
names.push(&after[..close]);
|
||||
rest = &after[close + 1..];
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
impl Flow {
|
||||
/// Check the flow is a tree within the caps, and that every node reads
|
||||
/// only what exists.
|
||||
|
|
@ -435,6 +450,11 @@ impl TodoSink {
|
|||
if self.text.len() > MAX_TEXT {
|
||||
return Err(format!("text is longer than {MAX_TEXT} bytes"));
|
||||
}
|
||||
for (what, template) in [("key", &self.key), ("text", &self.text)] {
|
||||
if let Some(bad) = placeholders(template).into_iter().find(|n| !is_field(n)) {
|
||||
return Err(format!("{what} has {{{bad}}}, which is no event field"));
|
||||
}
|
||||
}
|
||||
match self.op {
|
||||
Op::Upsert if self.text.trim().is_empty() => Err("an upsert with no text".to_owned()),
|
||||
Op::Upsert if self.prefix => Err("prefix is for a clear".to_owned()),
|
||||
|
|
@ -676,6 +696,35 @@ mod tests {
|
|||
assert_eq!(custom("c").validate(), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_placeholder_must_name_an_event_field() {
|
||||
let with = |key: &str, text: &str| {
|
||||
let mut f = custom("c");
|
||||
f.nodes[2].kind = NodeKind::Todo(TodoSink {
|
||||
to: Recipients::Agent("triage".to_owned()),
|
||||
op: Op::Upsert,
|
||||
key: key.to_owned(),
|
||||
prefix: false,
|
||||
text: text.to_owned(),
|
||||
});
|
||||
f.validate()
|
||||
};
|
||||
assert_eq!(
|
||||
with("forge:{repo}#{number}:triage", "{title} {url}"),
|
||||
Ok(())
|
||||
);
|
||||
assert!(
|
||||
with("forge:{repo}#{numbr}:triage", "t")
|
||||
.unwrap_err()
|
||||
.contains("numbr")
|
||||
);
|
||||
assert!(with("k", "see {ulr}").unwrap_err().contains("ulr"));
|
||||
assert!(with("k{}", "t").is_err(), "an empty name");
|
||||
// An unclosed brace is text.
|
||||
assert_eq!(with("k", "set {x"), Ok(()));
|
||||
assert_eq!(placeholders("a{b}c{d} {e"), ["b", "d"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sink_must_say_what_its_op_needs() {
|
||||
let sink = |op, prefix, text: &str| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue