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}