@@ -126,7 +126,12 @@ fn parse_fields(line: &str) -> Result<HashMap<&str, &str>, String> { |
| 126 | 126 | let (key, value) = token |
| 127 | 127 | .split_once('=') |
| 128 | 128 | .ok_or_else(|| format!("invalid token: {token}"))?; |
| 129 | | - fields.insert(key, value); |
| 129 | + if !matches!(key, "id" | "sender" | "app_id" | "parent" | "state") { |
| 130 | + return Err(format!("unknown field: {key}")); |
| 131 | + } |
| 132 | + if fields.insert(key, value).is_some() { |
| 133 | + return Err(format!("duplicate field: {key}")); |
| 134 | + } |
| 130 | 135 | } |
| 131 | 136 | Ok(fields) |
| 132 | 137 | } |
@@ -213,6 +218,33 @@ mod tests { |
| 213 | 218 | let _ = fs::remove_file(path); |
| 214 | 219 | } |
| 215 | 220 | |
| 221 | + #[test] |
| 222 | + fn duplicate_fields_fail_to_load() { |
| 223 | + let path = unique_temp_file(); |
| 224 | + fs::write(&path, "id=req-1\tid=req-2\tsender=:1.2\tstate=pending\n") |
| 225 | + .expect("test file should be written"); |
| 226 | + |
| 227 | + let loaded = load_registry(&path, Duration::from_secs(5)); |
| 228 | + assert!(loaded.is_err()); |
| 229 | + |
| 230 | + let _ = fs::remove_file(path); |
| 231 | + } |
| 232 | + |
| 233 | + #[test] |
| 234 | + fn unknown_fields_fail_to_load() { |
| 235 | + let path = unique_temp_file(); |
| 236 | + fs::write( |
| 237 | + &path, |
| 238 | + "id=req-1\tsender=:1.2\tstate=pending\tunexpected=1\n", |
| 239 | + ) |
| 240 | + .expect("test file should be written"); |
| 241 | + |
| 242 | + let loaded = load_registry(&path, Duration::from_secs(5)); |
| 243 | + assert!(loaded.is_err()); |
| 244 | + |
| 245 | + let _ = fs::remove_file(path); |
| 246 | + } |
| 247 | + |
| 216 | 248 | #[test] |
| 217 | 249 | fn persist_overwrites_previous_contents() { |
| 218 | 250 | let path = unique_temp_file(); |