Skip to main content

compose_lens/model/
pids.rs

1//! Raw-preserving service PID limits.
2
3use super::Located;
4
5/// A service-level Compose `pids_limit` value with its authored scalar retained.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct PidsLimit {
8    raw: Located<String>,
9    kind: PidsLimitKind,
10}
11
12impl PidsLimit {
13    pub(crate) fn parse(raw: Located<String>) -> Self {
14        let kind = match raw.value().as_str() {
15            "-1" => PidsLimitKind::Unlimited,
16            value if value.contains('$') => PidsLimitKind::Expression,
17            value if decimal_digits(value) => {
18                if value.bytes().all(|byte| byte == b'0') {
19                    PidsLimitKind::Zero
20                } else {
21                    PidsLimitKind::Finite {
22                        decimal: value.to_owned(),
23                    }
24                }
25            }
26            _ => PidsLimitKind::Other,
27        };
28        Self { raw, kind }
29    }
30
31    /// Returns the complete authored scalar and its source span.
32    #[must_use]
33    pub const fn raw(&self) -> &Located<String> {
34        &self.raw
35    }
36
37    /// Returns the non-destructive semantic classification.
38    #[must_use]
39    pub const fn kind(&self) -> &PidsLimitKind {
40        &self.kind
41    }
42}
43
44/// The semantic family of a service-level Compose PID limit.
45#[derive(Debug, Clone, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum PidsLimitKind {
48    /// No PID limit, authored as `-1`.
49    Unlimited,
50    /// A positive integral decimal retained without fixed-width parsing or normalization.
51    Finite {
52        /// Exact positive decimal spelling.
53        decimal: String,
54    },
55    /// An all-zero integral spelling retained as an ambiguous and unportable native state.
56    Zero,
57    /// A scalar that still contains a Compose interpolation marker.
58    Expression,
59    /// A fractional, signed, exponent, or otherwise unsupported scalar retained for diagnostics.
60    Other,
61}
62
63pub(crate) fn valid_positive_pids_decimal(value: &str) -> bool {
64    decimal_digits(value) && value.bytes().any(|byte| byte != b'0')
65}
66
67fn decimal_digits(value: &str) -> bool {
68    !value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit())
69}
70
71#[cfg(test)]
72mod tests {
73    use super::{PidsLimit, PidsLimitKind, valid_positive_pids_decimal};
74    use crate::model::Located;
75    use crate::source::{SourceId, SourceSpan};
76
77    #[test]
78    fn classifies_without_fixed_width_integer_parsing_or_normalization() -> Result<(), &'static str> {
79        let cases = [
80            ("-1", PidsLimitKind::Unlimited),
81            (
82                "00042",
83                PidsLimitKind::Finite {
84                    decimal: "00042".to_owned(),
85                },
86            ),
87            (
88                "18446744073709551616000000000000000000000000000000",
89                PidsLimitKind::Finite {
90                    decimal: "18446744073709551616000000000000000000000000000000".to_owned(),
91                },
92            ),
93            ("000", PidsLimitKind::Zero),
94            ("${PIDS_LIMIT:-64}", PidsLimitKind::Expression),
95            ("1.5", PidsLimitKind::Other),
96            ("1e3", PidsLimitKind::Other),
97            ("+1", PidsLimitKind::Other),
98        ];
99        for (value, expected) in cases {
100            let span = SourceSpan::new(SourceId::new(1), 0, value.len()).ok_or("valid test span expected")?;
101            let limit = PidsLimit::parse(Located::new(value.to_owned(), span));
102            assert_eq!(limit.raw().value(), value);
103            assert_eq!(limit.kind(), &expected);
104        }
105        Ok(())
106    }
107
108    #[test]
109    fn validates_generated_positive_decimals_without_overflow() {
110        for value in ["1", "0001", "18446744073709551616000000000000000000000000000000"] {
111            assert!(valid_positive_pids_decimal(value));
112        }
113        for value in ["", "0", "000", "-1", "+1", "1.0", "1e3", "64MiB"] {
114            assert!(!valid_positive_pids_decimal(value));
115        }
116    }
117}