Skip to main content

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;