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
|
|
@ -67,14 +67,25 @@ fn emit(flow: &Flow, node: &Node, sink: &TodoSink, fields: &Fields) -> Vec<Actio
|
|||
None => Vec::new(),
|
||||
},
|
||||
};
|
||||
let rendered = expand(&sink.key, fields).and_then(|key| {
|
||||
let summary = match sink.op {
|
||||
Op::Upsert => expand(&sink.text, fields)?,
|
||||
Op::Clear => String::new(),
|
||||
};
|
||||
Ok((key, summary))
|
||||
});
|
||||
let (key, summary) = match rendered {
|
||||
Ok(done) => done,
|
||||
Err(why) => {
|
||||
tracing::warn!(flow = %flow.id, node = %node.id, %why, "notify router: a todo node names a placeholder the event lacks; nothing sent");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let op = RouterTodoOp {
|
||||
op: sink.op,
|
||||
key: expand(&sink.key, fields),
|
||||
key,
|
||||
prefix: sink.op == Op::Clear && sink.prefix,
|
||||
summary: match sink.op {
|
||||
Op::Upsert => expand(&sink.text, fields),
|
||||
Op::Clear => String::new(),
|
||||
},
|
||||
summary,
|
||||
source: format!("router:{}", flow.id),
|
||||
};
|
||||
recipients
|
||||
|
|
@ -96,9 +107,9 @@ fn holds(condition: &Condition, fields: &Fields) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// `template` with each `{field}` replaced by that text field's value. A list
|
||||
/// field is joined with `, `; an unknown name is left as written.
|
||||
pub fn expand(template: &str, fields: &Fields) -> String {
|
||||
/// `template` with each `{field}` replaced by that field's value, a list
|
||||
/// joined with `, `. A name that is no field is an error naming it.
|
||||
pub fn expand(template: &str, fields: &Fields) -> Result<String, String> {
|
||||
let mut out = String::with_capacity(template.len());
|
||||
let mut rest = template;
|
||||
while let Some(open) = rest.find('{') {
|
||||
|
|
@ -106,22 +117,18 @@ pub fn expand(template: &str, fields: &Fields) -> String {
|
|||
let after = &rest[open + 1..];
|
||||
let Some(close) = after.find('}') else {
|
||||
out.push_str(&rest[open..]);
|
||||
return out;
|
||||
return Ok(out);
|
||||
};
|
||||
let name = &after[..close];
|
||||
match fields.get(name) {
|
||||
Some(Value::Text(v)) => out.push_str(v),
|
||||
Some(Value::List(items)) => out.push_str(&items.join(", ")),
|
||||
None => {
|
||||
out.push('{');
|
||||
out.push_str(name);
|
||||
out.push('}');
|
||||
}
|
||||
None => return Err(format!("{{{name}}} is no event field")),
|
||||
}
|
||||
rest = &after[close + 1..];
|
||||
}
|
||||
out.push_str(rest);
|
||||
out
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -288,11 +295,36 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn a_placeholder_expands_to_its_field_and_an_unknown_one_stays() {
|
||||
fn a_placeholder_expands_to_its_field_and_an_unknown_one_is_an_error() {
|
||||
let fields = event("assigned", &["a", "b"], &[], &[]);
|
||||
assert_eq!(
|
||||
expand("{repo}#{number} {assignees_added} {nope} {", &fields),
|
||||
"hyperhive/hyperhive#7 a, b {nope} {"
|
||||
expand("{repo}#{number} {assignees_added} {", &fields),
|
||||
Ok("hyperhive/hyperhive#7 a, b {".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
expand("x {nope}", &fields),
|
||||
Err("{nope} is no event field".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
/// A sink naming a placeholder the event lacks sends nothing, rather
|
||||
/// than a todo under a literal `{name}` key.
|
||||
#[test]
|
||||
fn a_sink_with_an_unknown_placeholder_emits_nothing() {
|
||||
let mut f = flow("assigned");
|
||||
f.nodes[2].kind = NodeKind::Todo(TodoSink {
|
||||
to: Recipients::Agent("triage".to_owned()),
|
||||
op: Op::Upsert,
|
||||
key: "forge:{repo}#{numbr}:triage".to_owned(),
|
||||
prefix: false,
|
||||
text: "t".to_owned(),
|
||||
});
|
||||
let fields = event("assigned", &["atlas"], &[], &[]);
|
||||
assert!(evaluate(&f, "issue_assign", &fields).is_empty());
|
||||
// Control: spelled right, the same flow emits.
|
||||
if let NodeKind::Todo(s) = &mut f.nodes[2].kind {
|
||||
s.key = "forge:{repo}#{number}:triage".to_owned();
|
||||
}
|
||||
assert_eq!(evaluate(&f, "issue_assign", &fields).len(), 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue