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}