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
18pub fn validate_language(
19    language: &compact_str::CompactString,
20    _context: &(),
21) -> Result<(), garde::Error> {
22    if !crate::FRONTEND_LANGUAGES.contains(language) {
23        return Err(garde::Error::new(compact_str::format_compact!(
24            "invalid language: {language}"
25        )));
26    }
27
28    Ok(())
29}
30
31pub fn validate_time_in_future(
32    time: &chrono::DateTime<chrono::Utc>,
33    _context: &(),
34) -> Result<(), garde::Error> {
35    let now = chrono::Utc::now();
36    if *time <= now {
37        return Err(garde::Error::new("time must be in the future"));
38    }
39
40    Ok(())
41}
42
43#[inline]
44pub fn validate_data<T: Validate>(data: &T) -> Result<(), Vec<String>>
45where
46    T::Context: Default,
47{
48    if let Err(err) = data.validate() {
49        let error_messages = flatten_validation_errors(&err);
50
51        return Err(error_messages);
52    }
53
54    Ok(())
55}
56
57pub fn flatten_validation_errors(errors: &garde::Report) -> Vec<String> {
58    let mut messages = Vec::new();
59
60    for (path, error) in errors.iter() {
61        let full_name = path.to_compact_string();
62
63        messages.push(format!("{full_name}: {}", error.message()));
64    }
65
66    messages
67}