Skip to main content

aion_package/declared_command/
template.rs

1//! The template shapes an emitted command is made of, and the values a
2//! caller binds into them.
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use super::error::RenderError;
8
9/// One piece of a fill template.
10///
11/// A hole names a declared parameter and nothing else: there is no
12/// expression, no default written inline, and no escape that could make a
13/// value decide the shape around it.
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(tag = "kind", rename_all = "snake_case")]
16pub enum FillPiece {
17    /// Literal text, used exactly as written.
18    Literal {
19        /// The text itself.
20        text: String,
21    },
22    /// A named parameter's value, substituted whole.
23    Hole {
24        /// The parameter this hole reads.
25        parameter: String,
26    },
27}
28
29/// A fill template: literal pieces and named holes, in order.
30///
31/// Concatenating the pieces produces ONE string. The single exception is a
32/// template that is exactly one hole bound to a list, which becomes one argv
33/// element per item — see [`super::DeclaredCommandContract::render`].
34#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
35pub struct FillTemplate {
36    /// The pieces, in order.
37    pub pieces: Vec<FillPiece>,
38}
39
40impl FillTemplate {
41    /// A template of one literal piece.
42    #[must_use]
43    pub fn literal(text: impl Into<String>) -> Self {
44        Self {
45            pieces: vec![FillPiece::Literal { text: text.into() }],
46        }
47    }
48
49    /// The parameter this template reads, when it is exactly one hole and
50    /// nothing else.
51    ///
52    /// This is the only shape a list may fill: a list written inside a larger
53    /// word has no single rendering, which is why the AWL checker refuses one
54    /// before it can reach here.
55    #[must_use]
56    pub fn sole_hole(&self) -> Option<&str> {
57        match self.pieces.as_slice() {
58            [FillPiece::Hole { parameter }] => Some(parameter.as_str()),
59            _ => None,
60        }
61    }
62
63    /// Every parameter this template reads, in order of appearance.
64    #[must_use]
65    pub fn holes(&self) -> Vec<&str> {
66        self.pieces
67            .iter()
68            .filter_map(|piece| match piece {
69                FillPiece::Literal { .. } => None,
70                FillPiece::Hole { parameter } => Some(parameter.as_str()),
71            })
72            .collect()
73    }
74}
75
76/// One value supplied for a declared parameter.
77///
78/// Two shapes, because a command's parameters have two shapes: a single value
79/// becomes one argv element, and a list becomes one element per item in the
80/// operand that carries it. Nothing here joins a list into a string — joining
81/// exists only to survive a re-split, and no re-split happens.
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub enum ArgumentValue {
84    /// A single value, rendered as written.
85    Scalar(String),
86    /// A list of values, one argv element each.
87    List(Vec<String>),
88}
89
90impl ArgumentValue {
91    /// A scalar from anything string-like.
92    pub fn scalar(value: impl Into<String>) -> Self {
93        Self::Scalar(value.into())
94    }
95
96    /// A list from an iterator of string-likes.
97    pub fn list<I, S>(values: I) -> Self
98    where
99        I: IntoIterator<Item = S>,
100        S: Into<String>,
101    {
102        Self::List(values.into_iter().map(Into::into).collect())
103    }
104
105    /// Whether this value is a list.
106    #[must_use]
107    pub const fn is_list(&self) -> bool {
108        matches!(self, Self::List(_))
109    }
110
111    /// How a diagnostic names this value, without pretending a list is a
112    /// string.
113    #[must_use]
114    pub fn describe(&self) -> String {
115        match self {
116            Self::Scalar(text) => text.clone(),
117            Self::List(items) => format!("[{}]", items.join(", ")),
118        }
119    }
120
121    /// The argument value a JSON dispatch input carries for `parameter`.
122    ///
123    /// Strings, numbers and booleans have one obvious argument spelling and
124    /// take it. An array becomes a list, provided every item is itself one of
125    /// those three. `null` and an object have no single obvious spelling —
126    /// guessing one (empty string? JSON text? space-joined?) would make the
127    /// command mean something the author never wrote — so each is refused by
128    /// name.
129    ///
130    /// # Errors
131    ///
132    /// Returns [`RenderError::UnrepresentableValue`] for a value with no
133    /// unambiguous argument form, and [`RenderError::InteriorNul`] for a
134    /// string carrying a NUL byte, which the kernel would silently truncate
135    /// the argument at.
136    pub fn from_json(parameter: &str, value: &Value) -> Result<Self, RenderError> {
137        if let Value::Array(items) = value {
138            let mut rendered = Vec::with_capacity(items.len());
139            for item in items {
140                rendered.push(scalar_text(parameter, item)?);
141            }
142            return Ok(Self::List(rendered));
143        }
144        Ok(Self::Scalar(scalar_text(parameter, value)?))
145    }
146}
147
148/// One JSON value's argument text, or the refusal that it has none.
149fn scalar_text(parameter: &str, value: &Value) -> Result<String, RenderError> {
150    let unrepresentable = |kind: &'static str| RenderError::UnrepresentableValue {
151        parameter: parameter.to_owned(),
152        kind,
153    };
154    match value {
155        Value::String(text) => {
156            if text.contains('\0') {
157                return Err(RenderError::InteriorNul {
158                    parameter: parameter.to_owned(),
159                });
160            }
161            Ok(text.clone())
162        }
163        Value::Number(number) => Ok(number.to_string()),
164        Value::Bool(flag) => Ok(if *flag { "true" } else { "false" }.to_owned()),
165        Value::Null => Err(unrepresentable("null")),
166        Value::Array(_) => Err(unrepresentable("a nested array")),
167        Value::Object(_) => Err(unrepresentable("an object")),
168    }
169}