Skip to main content

shared/
utils.rs

1use compact_str::ToCompactString;
2use garde::Validate;
3
4#[inline]
5pub fn slice_up_to(s: &str, max_len: usize) -> &str {
6    if max_len >= s.len() || s.is_empty() {
7        return s;
8    }
9
10    let mut idx = max_len;
11    while !s.is_char_boundary(idx) {
12        idx -= 1;
13    }
14
15    &s[..idx]
16}
17
18#[inline]
19pub fn truncate_up_to(mut s: String, max_len: usize) -> String {
20    if max_len >= s.len() || s.is_empty() {
21        return s;
22    }
23
24    let mut idx = max_len;
25    while !s.is_char_boundary(idx) {
26        idx -= 1;
27    }
28
29    s.truncate(idx);
30    s
31}
32
33pub fn validate_language(
34    language: &compact_str::CompactString,
35    _context: &(),
36) -> Result<(), garde::Error> {
37    if !crate::FRONTEND_LANGUAGES.contains(language) {
38        return Err(garde::Error::new(compact_str::format_compact!(
39            "invalid language: {language}"
40        )));
41    }
42
43    Ok(())
44}
45
46pub fn validate_time_in_future(
47    time: &chrono::DateTime<chrono::Utc>,
48    _context: &(),
49) -> Result<(), garde::Error> {
50    let now = chrono::Utc::now();
51    if *time <= now {
52        return Err(garde::Error::new("time must be in the future"));
53    }
54
55    Ok(())
56}
57
58#[inline]
59pub fn validate_data<T: Validate>(data: &T) -> Result<(), Vec<String>>
60where
61    T::Context: Default,
62{
63    if let Err(err) = data.validate() {
64        let error_messages = flatten_validation_errors(&err);
65
66        return Err(error_messages);
67    }
68
69    Ok(())
70}
71
72pub fn flatten_validation_errors(errors: &garde::Report) -> Vec<String> {
73    let mut messages = Vec::new();
74
75    for (path, error) in errors.iter() {
76        let full_name = path.to_compact_string();
77
78        messages.push(format!("{full_name}: {}", error.message()));
79    }
80
81    messages
82}