Skip to main content

wdl_format/config/
max_line_length.rs

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