github_actions_models/common/
expr.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, PartialEq, Serialize)]
7pub struct ExplicitExpr(String);
8
9impl ExplicitExpr {
10    pub fn from_curly(expr: impl Into<String>) -> Option<Self> {
15        let expr = expr.into();
18        let trimmed = expr.trim();
19        if !trimmed.starts_with("${{") || !trimmed.ends_with("}}") {
20            return None;
21        }
22
23        Some(ExplicitExpr(expr))
24    }
25
26    pub fn as_raw(&self) -> &str {
29        &self.0
30    }
31
32    pub fn as_curly(&self) -> &str {
37        self.as_raw().trim()
38    }
39
40    pub fn as_bare(&self) -> &str {
44        self.as_curly()
45            .strip_prefix("${{")
46            .and_then(|e| e.strip_suffix("}}"))
47            .map(|e| e.trim())
48            .expect("invariant violated: ExplicitExpr must be an expression")
49    }
50}
51
52impl<'de> Deserialize<'de> for ExplicitExpr {
53    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54    where
55        D: serde::Deserializer<'de>,
56    {
57        let raw = String::deserialize(deserializer)?;
58
59        let Some(expr) = Self::from_curly(raw) else {
60            return Err(serde::de::Error::custom(
61                "invalid expression: expected '${{' and '}}' delimiters",
62            ));
63        };
64
65        Ok(expr)
66    }
67}
68
69#[derive(Deserialize, Serialize, Debug, PartialEq)]
73#[serde(untagged)]
74pub enum LoE<T> {
75    Expr(ExplicitExpr),
79    Literal(T),
80}
81
82impl<T> Default for LoE<T>
83where
84    T: Default,
85{
86    fn default() -> Self {
87        Self::Literal(T::default())
88    }
89}
90
91pub type BoE = LoE<bool>;
93
94#[cfg(test)]
95mod tests {
96    use super::{ExplicitExpr, LoE};
97
98    #[test]
99    fn test_expr_invalid() {
100        let cases = &[
101            "not an expression",
102            "${{ missing end ",
103            "missing beginning }}",
104        ];
105
106        for case in cases {
107            let case = format!("\"{case}\"");
108            assert!(serde_yaml::from_str::<ExplicitExpr>(&case).is_err());
109        }
110    }
111
112    #[test]
113    fn test_expr() {
114        let expr = "\"  ${{ foo }} \\t \"";
115        let expr: ExplicitExpr = serde_yaml::from_str(expr).unwrap();
116        assert_eq!(expr.as_bare(), "foo");
117    }
118
119    #[test]
120    fn test_loe() {
121        let lit = "\"normal string\"";
122        assert_eq!(
123            serde_yaml::from_str::<LoE<String>>(lit).unwrap(),
124            LoE::Literal("normal string".to_string())
125        );
126
127        let expr = "\"${{ expr }}\"";
128        assert!(matches!(
129            serde_yaml::from_str::<LoE<String>>(expr).unwrap(),
130            LoE::Expr(_)
131        ));
132
133        let invalid = "\"${{ invalid \"";
135        assert_eq!(
136            serde_yaml::from_str::<LoE<String>>(invalid).unwrap(),
137            LoE::Literal("${{ invalid ".to_string())
138        );
139    }
140}