Skip to main content

wdl_format/config/
max_line_length.rs

1//! Configuration for max line length formatting.
2
3use toml_spanner::Arena;
4use toml_spanner::Context;
5use toml_spanner::Failed;
6use toml_spanner::FromToml;
7use toml_spanner::Item;
8use toml_spanner::ToToml;
9use toml_spanner::ToTomlError;
10
11/// Error while creating a max line length configuration.
12#[derive(thiserror::Error, Debug)]
13pub enum MaxLineLengthError {
14    /// Supplied number outside allowed range.
15    #[error(
16        "`{0}` is outside the allowed range for the max line length ({min}-{max})",
17        min = MIN_MAX_LINE_LENGTH,
18        max = MAX_MAX_LINE_LENGTH
19    )]
20    OutsideAllowedRange(usize),
21}
22
23/// The default maximum line length.
24pub const DEFAULT_MAX_LINE_LENGTH: usize = 90;
25/// The minimum maximum line length.
26pub const MIN_MAX_LINE_LENGTH: usize = 60;
27/// The maximum maximum line length.
28pub const MAX_MAX_LINE_LENGTH: usize = 240;
29/// The max line length sentinel value meaning "no maximum".
30const SENTINEL: &str = "none";
31
32/// The maximum line length.
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct MaxLineLength(Option<usize>);
35
36impl MaxLineLength {
37    /// Attempts to create a new `MaxLineLength` with the provided value.
38    pub fn try_new(value: Option<usize>) -> Result<Self, MaxLineLengthError> {
39        match value {
40            None => Ok(Self(None)),
41            Some(value) if (MIN_MAX_LINE_LENGTH..=MAX_MAX_LINE_LENGTH).contains(&value) => {
42                Ok(Self(Some(value)))
43            }
44            Some(value) => Err(MaxLineLengthError::OutsideAllowedRange(value)),
45        }
46    }
47
48    /// Gets the maximum line length. A value of `None` indicates no maximum.
49    pub fn get(&self) -> Option<usize> {
50        self.0
51    }
52}
53
54impl Default for MaxLineLength {
55    fn default() -> Self {
56        Self(Some(DEFAULT_MAX_LINE_LENGTH))
57    }
58}
59
60impl<'de> FromToml<'de> for MaxLineLength {
61    fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed> {
62        if let Some(SENTINEL) = item.as_str() {
63            return Ok(Self(None));
64        }
65
66        if let Some(n) = item.as_u64().and_then(|n| usize::try_from(n).ok())
67            && (MIN_MAX_LINE_LENGTH..=MAX_MAX_LINE_LENGTH).contains(&n)
68        {
69            return Ok(Self(Some(n)));
70        }
71
72        Err(ctx.report_custom_error(
73            format!(
74                "expected a positive integer between {MIN_MAX_LINE_LENGTH} and \
75                 {MAX_MAX_LINE_LENGTH} or `{SENTINEL}` for max line length value"
76            ),
77            item,
78        ))
79    }
80}
81
82impl ToToml for MaxLineLength {
83    fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
84        match &self.0 {
85            Some(n) => Ok(i64::try_from(*n)
86                .map_err(|e| ToTomlError {
87                    message: format!("invalid max line length: {e}").into(),
88                })?
89                .into()),
90            None => Ok(Item::string(SENTINEL)),
91        }
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use std::collections::HashMap;
98
99    use super::*;
100
101    #[test]
102    fn serialization() {
103        let map: HashMap<&str, MaxLineLength> =
104            HashMap::from_iter([("value", MaxLineLength(None))]);
105        assert_eq!(
106            toml_spanner::to_string(&map).unwrap(),
107            format!("value = \"{SENTINEL}\"\n")
108        );
109
110        let map: HashMap<&str, MaxLineLength> =
111            HashMap::from_iter([("value", MaxLineLength(Some(123)))]);
112        assert_eq!(toml_spanner::to_string(&map).unwrap(), "value = 123\n");
113    }
114
115    #[test]
116    fn deserialization() {
117        let map: HashMap<String, MaxLineLength> =
118            toml_spanner::from_str(&format!("value = '{SENTINEL}'")).unwrap();
119        assert_eq!(map["value"], MaxLineLength(None));
120
121        let map: HashMap<String, MaxLineLength> = toml_spanner::from_str("value = 80").unwrap();
122        assert_eq!(map["value"], MaxLineLength(Some(80)));
123
124        let expected_error = format!(
125            "expected a positive integer between {MIN_MAX_LINE_LENGTH} and {MAX_MAX_LINE_LENGTH} \
126             or `{SENTINEL}` for max line length value at `value`"
127        );
128
129        let error = toml_spanner::from_str::<HashMap<String, MaxLineLength>>("value = 'wrong'")
130            .unwrap_err();
131        assert_eq!(error.to_string(), expected_error);
132
133        let error =
134            toml_spanner::from_str::<HashMap<String, MaxLineLength>>("value = 1234").unwrap_err();
135        assert_eq!(error.to_string(), expected_error);
136
137        let error =
138            toml_spanner::from_str::<HashMap<String, MaxLineLength>>("value = -10").unwrap_err();
139        assert_eq!(error.to_string(), expected_error);
140    }
141}