Skip to main content

appcore_args/
raw.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: raw.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/19 12:52:57 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/19 13:34:54 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11use crate::{CliError, CliErrorKind};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct ArgLimits {
15    pub max_words: usize,
16    pub max_word_bytes: usize,
17    pub max_total_bytes: usize,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct RawArgs {
22    words: Vec<String>,
23}
24
25impl Default for ArgLimits {
26    fn default() -> Self {
27        Self {
28            max_words: 1024,
29            max_word_bytes: 64 * 1024,
30            max_total_bytes: 1024 * 1024,
31        }
32    }
33}
34
35impl RawArgs {
36    pub fn from_env() -> Result<Self, CliError> {
37        let mut words = Vec::new();
38        for (index, word) in std::env::args_os().skip(1).enumerate() {
39            let word = word.into_string().map_err(|_| {
40                CliError::new(
41                    CliErrorKind::InvalidInput,
42                    format!("argument {} is not valid UTF-8", index + 1),
43                )
44            })?;
45            words.push(word);
46        }
47        Self::parse_with_limits(words, ArgLimits::default())
48    }
49
50    pub fn parse<I>(args: I) -> Result<Self, CliError>
51    where
52        I: IntoIterator,
53        I::Item: Into<String>,
54    {
55        Self::parse_with_limits(args, ArgLimits::default())
56    }
57
58    pub fn parse_with_limits<I>(args: I, limits: ArgLimits) -> Result<Self, CliError>
59    where
60        I: IntoIterator,
61        I::Item: Into<String>,
62    {
63        let mut words = Vec::new();
64        let mut total_bytes = 0usize;
65        for arg in args {
66            let value = arg.into();
67            validate_raw_word(&value, words.len(), total_bytes, limits)?;
68            total_bytes = total_bytes.saturating_add(value.len());
69            words.push(value);
70        }
71        Ok(Self { words })
72    }
73
74    pub fn words(&self) -> &[String] {
75        &self.words
76    }
77}
78
79fn validate_raw_word(
80    value: &str,
81    count: usize,
82    total: usize,
83    limits: ArgLimits,
84) -> Result<(), CliError> {
85    if count >= limits.max_words {
86        return Err(CliError::new(
87            CliErrorKind::InvalidInput,
88            format!("argument count exceeds limit of {}", limits.max_words),
89        ));
90    }
91    if value.len() > limits.max_word_bytes {
92        return Err(CliError::new(
93            CliErrorKind::InvalidInput,
94            format!(
95                "argument {} exceeds byte limit of {}",
96                count + 1,
97                limits.max_word_bytes
98            ),
99        ));
100    }
101    if total.saturating_add(value.len()) > limits.max_total_bytes {
102        return Err(CliError::new(
103            CliErrorKind::InvalidInput,
104            format!(
105                "total argument bytes exceed limit of {}",
106                limits.max_total_bytes
107            ),
108        ));
109    }
110    if value.contains('\0') {
111        return Err(CliError::new(
112            CliErrorKind::InvalidInput,
113            format!("argument {} contains a NUL byte", count + 1),
114        ));
115    }
116    Ok(())
117}