1use crate::SharedString;
2use anyhow::{anyhow, Context, Result};
3use collections::{HashMap, HashSet};
4use serde::Deserialize;
5use std::any::{type_name, Any};
6
7pub trait Action: Any + Send {
8 fn qualified_name() -> SharedString
9 where
10 Self: Sized;
11 fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>>
12 where
13 Self: Sized;
14
15 fn partial_eq(&self, action: &dyn Action) -> bool;
16 fn boxed_clone(&self) -> Box<dyn Action>;
17 fn as_any(&self) -> &dyn Any;
18}
19
20impl<A> Action for A
21where
22 A: for<'a> Deserialize<'a> + PartialEq + Any + Send + Clone + Default,
23{
24 fn qualified_name() -> SharedString {
25 type_name::<A>().into()
26 }
27
28 fn build(params: Option<serde_json::Value>) -> Result<Box<dyn Action>>
29 where
30 Self: Sized,
31 {
32 let action = if let Some(params) = params {
33 serde_json::from_value(params).context("failed to deserialize action")?
34 } else {
35 Self::default()
36 };
37 Ok(Box::new(action))
38 }
39
40 fn partial_eq(&self, action: &dyn Action) -> bool {
41 action
42 .as_any()
43 .downcast_ref::<Self>()
44 .map_or(false, |a| self == a)
45 }
46
47 fn boxed_clone(&self) -> Box<dyn Action> {
48 Box::new(self.clone())
49 }
50
51 fn as_any(&self) -> &dyn Any {
52 self
53 }
54}
55
56#[derive(Clone, Debug, Default, Eq, PartialEq)]
57pub struct DispatchContext {
58 set: HashSet<SharedString>,
59 map: HashMap<SharedString, SharedString>,
60}
61
62impl<'a> TryFrom<&'a str> for DispatchContext {
63 type Error = anyhow::Error;
64
65 fn try_from(value: &'a str) -> Result<Self> {
66 Self::parse(value)
67 }
68}
69
70impl DispatchContext {
71 pub fn parse(source: &str) -> Result<Self> {
72 let mut context = Self::default();
73 let source = skip_whitespace(source);
74 Self::parse_expr(&source, &mut context)?;
75 Ok(context)
76 }
77
78 fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
79 if source.is_empty() {
80 return Ok(());
81 }
82
83 let key = source
84 .chars()
85 .take_while(|c| is_identifier_char(*c))
86 .collect::<String>();
87 source = skip_whitespace(&source[key.len()..]);
88 if let Some(suffix) = source.strip_prefix('=') {
89 source = skip_whitespace(suffix);
90 let value = source
91 .chars()
92 .take_while(|c| is_identifier_char(*c))
93 .collect::<String>();
94 source = skip_whitespace(&source[value.len()..]);
95 context.set(key, value);
96 } else {
97 context.insert(key);
98 }
99
100 Self::parse_expr(source, context)
101 }
102
103 pub fn is_empty(&self) -> bool {
104 self.set.is_empty() && self.map.is_empty()
105 }
106
107 pub fn clear(&mut self) {
108 self.set.clear();
109 self.map.clear();
110 }
111
112 pub fn extend(&mut self, other: &Self) {
113 for v in &other.set {
114 self.set.insert(v.clone());
115 }
116 for (k, v) in &other.map {
117 self.map.insert(k.clone(), v.clone());
118 }
119 }
120
121 pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
122 self.set.insert(identifier.into());
123 }
124
125 pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
126 self.map.insert(key.into(), value.into());
127 }
128}
129
130#[derive(Clone, Debug, Eq, PartialEq, Hash)]
131pub enum DispatchContextPredicate {
132 Identifier(SharedString),
133 Equal(SharedString, SharedString),
134 NotEqual(SharedString, SharedString),
135 Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
136 Not(Box<DispatchContextPredicate>),
137 And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
138 Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
139}
140
141impl DispatchContextPredicate {
142 pub fn parse(source: &str) -> Result<Self> {
143 let source = skip_whitespace(source);
144 let (predicate, rest) = Self::parse_expr(source, 0)?;
145 if let Some(next) = rest.chars().next() {
146 Err(anyhow!("unexpected character {next:?}"))
147 } else {
148 Ok(predicate)
149 }
150 }
151
152 pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
153 let Some(context) = contexts.last() else {
154 return false;
155 };
156 match self {
157 Self::Identifier(name) => context.set.contains(name),
158 Self::Equal(left, right) => context
159 .map
160 .get(left)
161 .map(|value| value == right)
162 .unwrap_or(false),
163 Self::NotEqual(left, right) => context
164 .map
165 .get(left)
166 .map(|value| value != right)
167 .unwrap_or(true),
168 Self::Not(pred) => !pred.eval(contexts),
169 Self::Child(parent, child) => {
170 parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
171 }
172 Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
173 Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
174 }
175 }
176
177 fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
178 type Op = fn(
179 DispatchContextPredicate,
180 DispatchContextPredicate,
181 ) -> Result<DispatchContextPredicate>;
182
183 let (mut predicate, rest) = Self::parse_primary(source)?;
184 source = rest;
185
186 'parse: loop {
187 for (operator, precedence, constructor) in [
188 (">", PRECEDENCE_CHILD, Self::new_child as Op),
189 ("&&", PRECEDENCE_AND, Self::new_and as Op),
190 ("||", PRECEDENCE_OR, Self::new_or as Op),
191 ("==", PRECEDENCE_EQ, Self::new_eq as Op),
192 ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
193 ] {
194 if source.starts_with(operator) && precedence >= min_precedence {
195 source = skip_whitespace(&source[operator.len()..]);
196 let (right, rest) = Self::parse_expr(source, precedence + 1)?;
197 predicate = constructor(predicate, right)?;
198 source = rest;
199 continue 'parse;
200 }
201 }
202 break;
203 }
204
205 Ok((predicate, source))
206 }
207
208 fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
209 let next = source
210 .chars()
211 .next()
212 .ok_or_else(|| anyhow!("unexpected eof"))?;
213 match next {
214 '(' => {
215 source = skip_whitespace(&source[1..]);
216 let (predicate, rest) = Self::parse_expr(source, 0)?;
217 if rest.starts_with(')') {
218 source = skip_whitespace(&rest[1..]);
219 Ok((predicate, source))
220 } else {
221 Err(anyhow!("expected a ')'"))
222 }
223 }
224 '!' => {
225 let source = skip_whitespace(&source[1..]);
226 let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
227 Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
228 }
229 _ if is_identifier_char(next) => {
230 let len = source
231 .find(|c: char| !is_identifier_char(c))
232 .unwrap_or(source.len());
233 let (identifier, rest) = source.split_at(len);
234 source = skip_whitespace(rest);
235 Ok((
236 DispatchContextPredicate::Identifier(identifier.to_string().into()),
237 source,
238 ))
239 }
240 _ => Err(anyhow!("unexpected character {next:?}")),
241 }
242 }
243
244 fn new_or(self, other: Self) -> Result<Self> {
245 Ok(Self::Or(Box::new(self), Box::new(other)))
246 }
247
248 fn new_and(self, other: Self) -> Result<Self> {
249 Ok(Self::And(Box::new(self), Box::new(other)))
250 }
251
252 fn new_child(self, other: Self) -> Result<Self> {
253 Ok(Self::Child(Box::new(self), Box::new(other)))
254 }
255
256 fn new_eq(self, other: Self) -> Result<Self> {
257 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
258 Ok(Self::Equal(left, right))
259 } else {
260 Err(anyhow!("operands must be identifiers"))
261 }
262 }
263
264 fn new_neq(self, other: Self) -> Result<Self> {
265 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
266 Ok(Self::NotEqual(left, right))
267 } else {
268 Err(anyhow!("operands must be identifiers"))
269 }
270 }
271}
272
273const PRECEDENCE_CHILD: u32 = 1;
274const PRECEDENCE_OR: u32 = 2;
275const PRECEDENCE_AND: u32 = 3;
276const PRECEDENCE_EQ: u32 = 4;
277const PRECEDENCE_NOT: u32 = 5;
278
279fn is_identifier_char(c: char) -> bool {
280 c.is_alphanumeric() || c == '_' || c == '-'
281}
282
283fn skip_whitespace(source: &str) -> &str {
284 let len = source
285 .find(|c: char| !c.is_whitespace())
286 .unwrap_or(source.len());
287 &source[len..]
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use DispatchContextPredicate::*;
294
295 #[test]
296 fn test_parse_context() {
297 let mut expected = DispatchContext::default();
298 expected.set("foo", "bar");
299 expected.insert("baz");
300 assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
301 assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
302 assert_eq!(
303 DispatchContext::parse(" baz foo = bar baz").unwrap(),
304 expected
305 );
306 assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
307 }
308
309 #[test]
310 fn test_parse_identifiers() {
311 // Identifiers
312 assert_eq!(
313 DispatchContextPredicate::parse("abc12").unwrap(),
314 Identifier("abc12".into())
315 );
316 assert_eq!(
317 DispatchContextPredicate::parse("_1a").unwrap(),
318 Identifier("_1a".into())
319 );
320 }
321
322 #[test]
323 fn test_parse_negations() {
324 assert_eq!(
325 DispatchContextPredicate::parse("!abc").unwrap(),
326 Not(Box::new(Identifier("abc".into())))
327 );
328 assert_eq!(
329 DispatchContextPredicate::parse(" ! ! abc").unwrap(),
330 Not(Box::new(Not(Box::new(Identifier("abc".into())))))
331 );
332 }
333
334 #[test]
335 fn test_parse_equality_operators() {
336 assert_eq!(
337 DispatchContextPredicate::parse("a == b").unwrap(),
338 Equal("a".into(), "b".into())
339 );
340 assert_eq!(
341 DispatchContextPredicate::parse("c!=d").unwrap(),
342 NotEqual("c".into(), "d".into())
343 );
344 assert_eq!(
345 DispatchContextPredicate::parse("c == !d")
346 .unwrap_err()
347 .to_string(),
348 "operands must be identifiers"
349 );
350 }
351
352 #[test]
353 fn test_parse_boolean_operators() {
354 assert_eq!(
355 DispatchContextPredicate::parse("a || b").unwrap(),
356 Or(
357 Box::new(Identifier("a".into())),
358 Box::new(Identifier("b".into()))
359 )
360 );
361 assert_eq!(
362 DispatchContextPredicate::parse("a || !b && c").unwrap(),
363 Or(
364 Box::new(Identifier("a".into())),
365 Box::new(And(
366 Box::new(Not(Box::new(Identifier("b".into())))),
367 Box::new(Identifier("c".into()))
368 ))
369 )
370 );
371 assert_eq!(
372 DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
373 Or(
374 Box::new(And(
375 Box::new(Identifier("a".into())),
376 Box::new(Identifier("b".into()))
377 )),
378 Box::new(And(
379 Box::new(Identifier("c".into())),
380 Box::new(Identifier("d".into()))
381 ))
382 )
383 );
384 assert_eq!(
385 DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
386 Or(
387 Box::new(And(
388 Box::new(Equal("a".into(), "b".into())),
389 Box::new(Identifier("c".into()))
390 )),
391 Box::new(And(
392 Box::new(Equal("d".into(), "e".into())),
393 Box::new(Identifier("f".into()))
394 ))
395 )
396 );
397 assert_eq!(
398 DispatchContextPredicate::parse("a && b && c && d").unwrap(),
399 And(
400 Box::new(And(
401 Box::new(And(
402 Box::new(Identifier("a".into())),
403 Box::new(Identifier("b".into()))
404 )),
405 Box::new(Identifier("c".into())),
406 )),
407 Box::new(Identifier("d".into()))
408 ),
409 );
410 }
411
412 #[test]
413 fn test_parse_parenthesized_expressions() {
414 assert_eq!(
415 DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
416 And(
417 Box::new(Identifier("a".into())),
418 Box::new(Or(
419 Box::new(Equal("b".into(), "c".into())),
420 Box::new(NotEqual("d".into(), "e".into())),
421 )),
422 ),
423 );
424 assert_eq!(
425 DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
426 Or(
427 Box::new(Identifier("a".into())),
428 Box::new(Identifier("b".into())),
429 )
430 );
431 }
432}