Skip to main content

aster_forge_utils/
text.rs

1//! Text length and UTF-8-safe truncation helpers.
2//!
3//! Aster services often need conservative limits for display text, file names, status messages, or
4//! external-system error snippets. This module keeps byte-based truncation UTF-8-safe and provides a
5//! small character-count helper for product validation rules that are expressed in Unicode scalar
6//! values instead of bytes.
7
8/// Returns the number of Unicode scalar values in a string.
9///
10/// This is intentionally not a grapheme-cluster count. Product validation rules that need
11/// user-perceived characters should use a dedicated Unicode segmentation policy at the product
12/// boundary.
13pub fn char_count(value: &str) -> usize {
14    value.chars().count()
15}
16
17/// Truncates a string to at most `max_bytes` bytes without splitting a UTF-8 code point.
18///
19/// If `value` is already within the limit it is returned unchanged as an owned string. A zero limit
20/// always returns an empty string.
21pub fn truncate_utf8_to_max_bytes(value: &str, max_bytes: usize) -> String {
22    if value.len() <= max_bytes {
23        return value.to_string();
24    }
25
26    let mut end = max_bytes;
27    while end > 0 && !value.is_char_boundary(end) {
28        end -= 1;
29    }
30    value[..end].to_string()
31}
32
33#[cfg(test)]
34mod tests {
35    use super::{char_count, truncate_utf8_to_max_bytes};
36
37    #[test]
38    fn char_count_counts_unicode_scalars() {
39        assert_eq!(char_count("Aster"), 5);
40        assert_eq!(char_count("你好世界"), 4);
41        assert_eq!(char_count("e\u{301}"), 2);
42    }
43
44    #[test]
45    fn truncate_utf8_to_max_bytes_keeps_short_ascii_unchanged() {
46        assert_eq!(
47            truncate_utf8_to_max_bytes("AsterYggdrasil", 32),
48            "AsterYggdrasil"
49        );
50    }
51
52    #[test]
53    fn truncate_utf8_to_max_bytes_truncates_ascii_by_bytes() {
54        assert_eq!(truncate_utf8_to_max_bytes("AsterYggdrasil", 5), "Aster");
55    }
56
57    #[test]
58    fn truncate_utf8_to_max_bytes_preserves_char_boundaries() {
59        assert_eq!(truncate_utf8_to_max_bytes("你好世界", 7), "你好");
60        assert_eq!(truncate_utf8_to_max_bytes("éclair", 1), "");
61        assert_eq!(truncate_utf8_to_max_bytes("éclair", 2), "é");
62    }
63
64    #[test]
65    fn truncate_utf8_to_max_bytes_handles_zero_limit() {
66        assert_eq!(truncate_utf8_to_max_bytes("AsterYggdrasil", 0), "");
67        assert_eq!(truncate_utf8_to_max_bytes("你好", 0), "");
68    }
69}