Skip to main content

use_accessible_label/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive accessible label helpers.
3//!
4//! The crate keeps label handling narrow: normalize whitespace, reject empty
5//! labels where needed, and offer simple word-count helpers.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_accessible_label::{
11//!     AccessibleLabel, has_accessible_label, is_label_too_long, label_word_count,
12//!     normalize_accessible_label,
13//! };
14//!
15//! let label = AccessibleLabel::new("  Submit   order ").unwrap();
16//!
17//! assert_eq!(label.text(), "Submit order");
18//! assert!(!label.is_empty());
19//! assert_eq!(label.word_count(), 2);
20//! assert!(has_accessible_label("  Submit   order "));
21//! assert_eq!(normalize_accessible_label("  Submit   order "), "Submit order");
22//! assert_eq!(label_word_count("Submit order now"), 3);
23//! assert!(!is_label_too_long("Submit order", 3).unwrap());
24//! ```
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct AccessibleLabel {
28    text: String,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum AccessibleLabelError {
33    EmptyLabel,
34    InvalidMaxWords,
35}
36
37#[must_use]
38pub fn normalize_accessible_label(label: &str) -> String {
39    label.split_whitespace().collect::<Vec<_>>().join(" ")
40}
41
42#[must_use]
43pub fn has_accessible_label(label: &str) -> bool {
44    !normalize_accessible_label(label).is_empty()
45}
46
47#[must_use]
48pub fn label_word_count(label: &str) -> usize {
49    normalize_accessible_label(label).split_whitespace().count()
50}
51
52impl AccessibleLabel {
53    pub fn new(text: impl Into<String>) -> Result<Self, AccessibleLabelError> {
54        let text = normalize_accessible_label(&text.into());
55
56        if text.is_empty() {
57            return Err(AccessibleLabelError::EmptyLabel);
58        }
59
60        Ok(Self { text })
61    }
62
63    #[must_use]
64    pub fn text(&self) -> &str {
65        self.text.as_str()
66    }
67
68    #[must_use]
69    pub fn is_empty(&self) -> bool {
70        self.text.is_empty()
71    }
72
73    #[must_use]
74    pub fn word_count(&self) -> usize {
75        self.text.split_whitespace().count()
76    }
77}
78
79pub fn is_label_too_long(label: &str, max_words: usize) -> Result<bool, AccessibleLabelError> {
80    if !has_accessible_label(label) {
81        return Err(AccessibleLabelError::EmptyLabel);
82    }
83
84    if max_words == 0 {
85        return Err(AccessibleLabelError::InvalidMaxWords);
86    }
87
88    Ok(label_word_count(label) > max_words)
89}
90
91#[cfg(test)]
92mod tests {
93    use super::{
94        AccessibleLabel, AccessibleLabelError, has_accessible_label, is_label_too_long,
95        label_word_count, normalize_accessible_label,
96    };
97
98    #[test]
99    fn normalizes_and_counts_labels() {
100        let label = AccessibleLabel::new("  Submit   order ").unwrap();
101
102        assert_eq!(label.text(), "Submit order");
103        assert!(!label.is_empty());
104        assert_eq!(label.word_count(), 2);
105        assert!(has_accessible_label("  Submit   order "));
106        assert_eq!(
107            normalize_accessible_label("  Submit   order "),
108            "Submit order"
109        );
110        assert_eq!(label_word_count("Submit order now"), 3);
111    }
112
113    #[test]
114    fn rejects_empty_labels_and_checks_length() {
115        assert_eq!(
116            AccessibleLabel::new("   "),
117            Err(AccessibleLabelError::EmptyLabel)
118        );
119        assert_eq!(
120            is_label_too_long("   ", 3),
121            Err(AccessibleLabelError::EmptyLabel)
122        );
123        assert_eq!(
124            is_label_too_long("Submit order now", 0),
125            Err(AccessibleLabelError::InvalidMaxWords)
126        );
127        assert!(is_label_too_long("Submit order now", 2).unwrap());
128        assert!(!is_label_too_long("Submit order", 3).unwrap());
129    }
130}