//! Pure helpers for pipeline item ID parsing. //! //! Pipeline item IDs are numeric strings, e.g. `"42"`, `"730"`. Legacy items //! may use the old `{number}_{type}_{slug}` format (e.g. `"42_story_foo"`). //! The functions here extract or validate the leading numeric segment without //! performing any I/O. /// Extract the numeric prefix from a pipeline item ID. /// /// Works for both numeric-only IDs (`"42"` → `"42"`) and legacy slug-format /// IDs (`"42_story_foo"` → `"42"`). /// Returns `None` if the ID has no leading digit sequence. pub fn extract_item_number(item_id: &str) -> Option<&str> { item_id .split('_') .next() .filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit())) } #[allow(dead_code)] /// Return `true` if `item_id` starts with a numeric prefix. /// /// Valid: `"42"`, `"42_story_foo"`, `"1_bug_bar"`. /// Invalid: `"story_without_number"`, `""`, `"abc_story"`. pub fn has_valid_id_prefix(item_id: &str) -> bool { extract_item_number(item_id).is_some() } // ── Tests ───────────────────────────────────────────────────────────────────── #[cfg(test)] mod tests { use super::*; #[test] fn extract_item_number_extracts_prefix() { assert_eq!(extract_item_number("42_story_foo"), Some("42")); assert_eq!(extract_item_number("1_bug_bar"), Some("1")); assert_eq!(extract_item_number("100_refactor_baz"), Some("100")); assert_eq!( extract_item_number("261_story_bot_notifications"), Some("261") ); assert_eq!(extract_item_number("1_spike_research"), Some("1")); } #[test] fn extract_item_number_works_for_numeric_only_ids() { // Numeric-only IDs (the new canonical format). assert_eq!(extract_item_number("42"), Some("42")); assert_eq!(extract_item_number("730"), Some("730")); assert_eq!(extract_item_number("1"), Some("1")); } #[test] fn has_valid_id_prefix_returns_true_for_numeric_only() { assert!(has_valid_id_prefix("42")); assert!(has_valid_id_prefix("730")); } #[test] fn extract_item_number_returns_none_for_no_numeric_prefix() { assert_eq!(extract_item_number("story_without_number"), None); assert_eq!(extract_item_number("abc_story"), None); assert_eq!(extract_item_number("abc_story_thing"), None); assert_eq!(extract_item_number(""), None); } #[test] fn extract_item_number_returns_none_for_empty_first_segment() { // Leading underscore: first segment is "". assert_eq!(extract_item_number("_story_thing"), None); } #[test] fn has_valid_id_prefix_returns_true_for_valid_ids() { assert!(has_valid_id_prefix("42_story_foo")); assert!(has_valid_id_prefix("1_bug_bar")); } #[test] fn has_valid_id_prefix_returns_false_for_invalid_ids() { assert!(!has_valid_id_prefix("story_no_number")); assert!(!has_valid_id_prefix("")); } }