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();
16 if !expr.starts_with("${{") || !expr.ends_with("}}") {
17 return None;
18 }
19
20 Some(ExplicitExpr(expr))
21 }
22
23 pub fn as_raw(&self) -> &str {
26 &self.0
27 }
28
29 pub fn as_curly(&self) -> &str {
34 self.as_raw().trim()
35 }
36
37 pub fn as_bare(&self) -> &str {
41 self.as_curly()
42 .strip_prefix("${{")
43 .and_then(|e| e.strip_suffix("}}"))
44 .map(|e| e.trim())
45 .expect("invariant violated: ExplicitExpr must be an expression")
46 }
47}
48
49impl<'de> Deserialize<'de> for ExplicitExpr {
50 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51 where
52 D: serde::Deserializer<'de>,
53 {
54 let raw = String::deserialize(deserializer)?;
55
56 let Some(expr) = Self::from_curly(raw) else {
57 return Err(serde::de::Error::custom(
58 "invalid expression: expected '${{' and '}}' delimiters",
59 ));
60 };
61
62 Ok(expr)
63 }
64}
65
66#[derive(Deserialize, Serialize, Debug, PartialEq)]
70#[serde(untagged)]
71pub enum LoE<T> {
72 Expr(ExplicitExpr),
76 Literal(T),
77}
78
79impl<T> Default for LoE<T>
80where
81 T: Default,
82{
83 fn default() -> Self {
84 Self::Literal(T::default())
85 }
86}
87
88pub type BoE = LoE<bool>;
90
91#[cfg(test)]
92mod tests {
93 use super::{ExplicitExpr, LoE};
94
95 #[test]
96 fn test_expr_invalid() {
97 let cases = &[
98 "not an expression",
99 "${{ missing end ",
100 "missing beginning }}",
101 " ${{ leading whitespace }}",
102 "${{ trailing whitespace }} ",
103 ];
104
105 for case in cases {
106 let case = format!("\"{case}\"");
107 assert!(serde_yaml::from_str::<ExplicitExpr>(&case).is_err());
108 }
109 }
110
111 #[test]
112 fn test_expr() {
113 for (case, expected) in &[
114 ("${{ foo }}", "foo"),
115 ("${{ foo.bar }}", "foo.bar"),
116 ("${{ foo['bar'] }}", "foo['bar']"),
117 ("${{foo}}", "foo"),
118 ("${{ foo}}", "foo"),
119 ("${{ foo }}", "foo"),
120 ] {
121 let case = format!("\"{case}\"");
122 let expr: ExplicitExpr = serde_yaml::from_str(&case).unwrap();
123 assert_eq!(expr.as_bare(), *expected);
124 }
125 }
126
127 #[test]
128 fn test_loe() {
129 let lit = "\"normal string\"";
130 assert_eq!(
131 serde_yaml::from_str::<LoE<String>>(lit).unwrap(),
132 LoE::Literal("normal string".to_string())
133 );
134
135 let expr = "\"${{ expr }}\"";
136 assert!(matches!(
137 serde_yaml::from_str::<LoE<String>>(expr).unwrap(),
138 LoE::Expr(_)
139 ));
140
141 let invalid = "\"${{ invalid \"";
143 assert_eq!(
144 serde_yaml::from_str::<LoE<String>>(invalid).unwrap(),
145 LoE::Literal("${{ invalid ".to_string())
146 );
147 }
148}