winget_types/locale/
release_notes.rs

1use std::{borrow::Cow, str::FromStr};
2
3use derive_more::{Deref, Display};
4use serde::Serialize;
5use serde_with::DeserializeFromStr;
6use thiserror::Error;
7
8#[derive(
9    Clone,
10    Debug,
11    Default,
12    Deref,
13    Display,
14    Eq,
15    PartialEq,
16    Ord,
17    PartialOrd,
18    Hash,
19    Serialize,
20    DeserializeFromStr,
21)]
22pub struct ReleaseNotes(String);
23
24#[derive(Error, Debug, Eq, PartialEq)]
25pub enum ReleaseNotesError {
26    #[error("Release notes cannot be empty")]
27    Empty,
28}
29
30impl ReleaseNotes {
31    pub const MAX_CHAR_LENGTH: usize = 10000;
32
33    pub fn new<S: AsRef<str>>(value: S) -> Result<Self, ReleaseNotesError> {
34        let result = truncate_with_lines::<{ Self::MAX_CHAR_LENGTH }>(value.as_ref().trim());
35        if result.is_empty() {
36            Err(ReleaseNotesError::Empty)
37        } else {
38            Ok(Self(result.into_owned()))
39        }
40    }
41}
42
43impl FromStr for ReleaseNotes {
44    type Err = ReleaseNotesError;
45
46    fn from_str(s: &str) -> Result<Self, Self::Err> {
47        Self::new(s)
48    }
49}
50
51fn truncate_with_lines<const N: usize>(value: &str) -> Cow<str> {
52    if value.chars().count() <= N {
53        return Cow::Borrowed(value);
54    }
55
56    let mut result = String::with_capacity(N);
57    let mut current_size = 0;
58
59    for (iter_count, line) in value.lines().enumerate() {
60        let prospective_size = current_size + line.chars().count() + "\n".len();
61        if prospective_size > N {
62            break;
63        }
64        if iter_count != 0 {
65            result.push('\n');
66        }
67        result.push_str(line);
68        current_size = prospective_size;
69    }
70
71    Cow::Owned(result)
72}
73
74#[cfg(test)]
75mod tests {
76    use crate::locale::release_notes::truncate_with_lines;
77
78    #[test]
79    fn test_truncate_to_lines() {
80        use std::fmt::Write;
81
82        const CHAR_LIMIT: usize = 100;
83
84        let mut buffer = String::new();
85        let mut line_count = 0;
86        while buffer.chars().count() <= CHAR_LIMIT {
87            line_count += 1;
88            writeln!(buffer, "Line {line_count}").unwrap();
89        }
90        let formatted = truncate_with_lines::<CHAR_LIMIT>(&buffer);
91        let formatted_char_count = formatted.chars().count();
92        assert!(formatted_char_count < buffer.chars().count());
93        assert_eq!(formatted.trim().chars().count(), formatted_char_count);
94    }
95}