ic_host_artifacts/batch/mod.rs
1//! Pure ordered label admission for caller-owned batches.
2
3use std::collections::HashMap;
4
5/// Invalid labels, rejected before the operation performs work.
6#[non_exhaustive]
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub enum BatchLabelError {
9 /// An input label was empty.
10 Empty {
11 /// Zero-based position of the empty label.
12 index: usize,
13 },
14 /// Two inputs used the same label.
15 Duplicate {
16 /// Repeated label.
17 label: String,
18 /// Position of the first occurrence.
19 first_index: usize,
20 /// Position of the repeated occurrence.
21 duplicate_index: usize,
22 },
23}
24
25/// Reject empty or repeated labels before a caller starts batch work.
26///
27/// Labels are compared exactly, without trimming, normalization or case folding.
28/// Empty batches are valid. The first invalid entry in input order is returned;
29/// duplicate errors retain the original and repeated positions. This function
30/// performs no batch work and selects no label or operation policy beyond those
31/// structural requirements.
32/// The iterator is consumed only through the first invalid entry. Callers must
33/// keep iteration free of batch effects and finish admission before starting work.
34///
35/// # Errors
36/// Returns the first empty or duplicate label with its zero-based position.
37pub fn validate_labels<'a>(
38 labels: impl IntoIterator<Item = &'a str>,
39) -> Result<(), BatchLabelError> {
40 let mut seen = HashMap::new();
41 for (index, label) in labels.into_iter().enumerate() {
42 if label.is_empty() {
43 return Err(BatchLabelError::Empty { index });
44 }
45 if let Some(first_index) = seen.insert(label, index) {
46 return Err(BatchLabelError::Duplicate {
47 label: label.to_owned(),
48 first_index,
49 duplicate_index: index,
50 });
51 }
52 }
53 Ok(())
54}
55
56impl std::fmt::Display for BatchLabelError {
57 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 Self::Empty { index } => write!(formatter, "label at index {index} is empty"),
60 Self::Duplicate {
61 label,
62 first_index,
63 duplicate_index,
64 } => write!(
65 formatter,
66 "label {label:?} at index {duplicate_index} duplicates index {first_index}"
67 ),
68 }
69 }
70}
71
72impl std::error::Error for BatchLabelError {}
73
74#[cfg(test)]
75mod tests;