2026-09-24 14:35:05 +03:00
|
|
|
//! Read-only audit presentation; persisted event payloads are never rewritten.
|
|
|
|
|
use crate::domain::WorkspaceDocument;
|
|
|
|
|
use serde_json::{Value, json};
|
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
|
|
|
|
pub type Names = BTreeMap<String, String>;
|
|
|
|
|
|
|
|
|
|
pub fn remember(names: &mut Names, doc: &WorkspaceDocument) {
|
|
|
|
|
for person in &doc.people {
|
|
|
|
|
names.insert(person.id.to_string(), person.display_name.clone());
|
2026-10-07 13:28:43 +01:00
|
|
|
for alias in &person.aliases {
|
|
|
|
|
names.insert(alias.to_string(), person.display_name.clone());
|
|
|
|
|
}
|
2026-09-24 14:35:05 +03:00
|
|
|
if let Some(id) = person.user_id {
|
|
|
|
|
names.insert(id.to_string(), person.display_name.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (id, name) in doc
|
|
|
|
|
.schedules
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| (s.id, &s.name))
|
|
|
|
|
.chain(doc.escalations.iter().map(|s| (s.id, &s.name)))
|
|
|
|
|
.chain(doc.routes.iter().map(|s| (s.id, &s.name)))
|
|
|
|
|
.chain(doc.generation.rules.iter().map(|s| (s.id, &s.name)))
|
|
|
|
|
.chain(doc.generation.variables.iter().map(|s| (s.id, &s.name)))
|
|
|
|
|
{
|
|
|
|
|
names.insert(id.to_string(), name.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn resolve(value: &Value, names: &Names) -> Value {
|
|
|
|
|
match value {
|
|
|
|
|
Value::String(id) => names
|
|
|
|
|
.get(id)
|
|
|
|
|
.map(|name| json!({"name":name,"id":id}))
|
|
|
|
|
.unwrap_or_else(|| value.clone()),
|
|
|
|
|
Value::Array(items) => Value::Array(items.iter().map(|v| resolve(v, names)).collect()),
|
|
|
|
|
Value::Object(fields) => Value::Object(
|
|
|
|
|
fields
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(key, value)| {
|
|
|
|
|
// Free text must remain verbatim, even if a comment happens to equal a UUID.
|
|
|
|
|
let reference = key == "id"
|
|
|
|
|
|| key.ends_with("_id")
|
|
|
|
|
|| matches!(
|
|
|
|
|
key.as_str(),
|
|
|
|
|
"from_user"
|
|
|
|
|
| "to_user"
|
|
|
|
|
| "requested_by"
|
|
|
|
|
| "forced_by"
|
|
|
|
|
| "approvals"
|
|
|
|
|
| "required_approvers"
|
|
|
|
|
| "participants"
|
|
|
|
|
| "primary"
|
|
|
|
|
| "secondary"
|
|
|
|
|
| "fallback"
|
|
|
|
|
| "user"
|
|
|
|
|
| "redirect_to"
|
|
|
|
|
| "escalation"
|
|
|
|
|
| "variable"
|
|
|
|
|
| "iteration"
|
|
|
|
|
| "output"
|
|
|
|
|
);
|
|
|
|
|
let resolved = if reference || value.is_object() || value.is_array() {
|
|
|
|
|
resolve(value, names)
|
|
|
|
|
} else {
|
|
|
|
|
value.clone()
|
|
|
|
|
};
|
|
|
|
|
(key.clone(), resolved)
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
|
|
|
|
),
|
|
|
|
|
_ => value.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
#[test]
|
|
|
|
|
fn references_expand_without_changing_comments_unknown_ids_or_raw_details() {
|
|
|
|
|
let id = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
let names = Names::from([(id.clone(), "Alice".into())]);
|
|
|
|
|
let raw = json!({"from_user":id,"approvals":[id],"reason":id,"unknown_id":"missing","legs":[{"to_user":id}]});
|
|
|
|
|
let resolved = resolve(&raw, &names);
|
|
|
|
|
assert_eq!(resolved["from_user"]["name"], "Alice");
|
|
|
|
|
assert_eq!(resolved["approvals"][0]["id"], id);
|
|
|
|
|
assert_eq!(resolved["legs"][0]["to_user"]["name"], "Alice");
|
|
|
|
|
assert_eq!(resolved["reason"], id);
|
|
|
|
|
assert_eq!(resolved["unknown_id"], "missing");
|
|
|
|
|
assert_eq!(raw["from_user"], id);
|
|
|
|
|
}
|
2026-10-07 13:28:43 +01:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn merged_contact_ids_keep_resolving_to_the_person() {
|
|
|
|
|
let (old, new) = (uuid::Uuid::new_v4(), uuid::Uuid::new_v4());
|
|
|
|
|
let mut doc = WorkspaceDocument::new("Team".into());
|
|
|
|
|
doc.people.push(crate::domain::Person {
|
|
|
|
|
id: new,
|
|
|
|
|
user_id: Some(new),
|
|
|
|
|
display_name: "Dana".into(),
|
|
|
|
|
email: Some("dana@example.com".into()),
|
|
|
|
|
country: None,
|
|
|
|
|
aliases: vec![old],
|
|
|
|
|
});
|
|
|
|
|
let mut names = Names::new();
|
|
|
|
|
remember(&mut names, &doc);
|
|
|
|
|
assert_eq!(names[&old.to_string()], "Dana");
|
|
|
|
|
assert_eq!(names[&new.to_string()], "Dana");
|
|
|
|
|
}
|
2026-09-24 14:35:05 +03:00
|
|
|
}
|