asl/asl/types/core/
execution_path.rs

1use std::fmt::Debug;
2use std::str::FromStr;
3
4use serde_with::DeserializeFromStr;
5use thiserror::Error;
6
7use crate::asl::types::core::jsonpath::{
8    MyJsonPath, MyJsonPathEvaluateError, MyJsonPathParseError,
9};
10use crate::asl::types::core::value_conversions::TryFromValue;
11use crate::asl::types::execution::ExecutionInput;
12
13/// A JSONPath that can handle both Context and Value
14#[derive(DeserializeFromStr, Clone, Debug, PartialEq, Eq)]
15pub enum ExecutionPath<T>
16where
17    T: Clone + Debug + PartialEq + Eq + TryFromValue,
18    MyJsonPath<T>: Clone + Debug + PartialEq + Eq,
19{
20    Value(MyJsonPath<T>),
21    Context(MyJsonPath<T>),
22}
23
24#[derive(Error, Debug)]
25pub enum ExecutionPathEvaluateError {
26    #[error("Can't evaluate path: {0}")]
27    CantEvaluatePath(#[from] MyJsonPathEvaluateError),
28}
29
30#[derive(Error, Debug)]
31pub enum ExecutionPathParseError {
32    #[error("Malformed JsonPath:\n{0}")]
33    MalformedJsonPath(#[from] MyJsonPathParseError),
34
35    #[error(
36        "Invalid field type for field. It can only be a \
37             (1) JSONPath from the input, \
38             (2) null. \
39             Got: {0}"
40    )]
41    InvalidFieldType(&'static str),
42}
43
44impl<T> ExecutionPath<T>
45where
46    T: Clone + Debug + PartialEq + Eq + TryFromValue,
47{
48    pub fn evaluate(
49        &self,
50        input: &ExecutionInput,
51    ) -> Result<Option<T>, ExecutionPathEvaluateError> {
52        let ret = match self {
53            ExecutionPath::Context(path) => path.evaluate(&input.context)?,
54            ExecutionPath::Value(path) => path.evaluate(&input.value)?,
55        };
56        Ok(ret)
57    }
58
59    pub fn path_string(&self) -> &String {
60        match self {
61            ExecutionPath::Context(path) => path.path_string(),
62            ExecutionPath::Value(path) => path.path_string(),
63        }
64    }
65}
66
67impl<T> FromStr for ExecutionPath<T>
68where
69    T: Clone + Debug + PartialEq + Eq + TryFromValue,
70{
71    type Err = ExecutionPathParseError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        let s = s.trim();
75        let ret = if s.starts_with("$$") {
76            let s = &s[1..];
77            Self::Context(s.parse::<MyJsonPath<T>>()?)
78        } else {
79            Self::Value(s.parse::<MyJsonPath<T>>()?)
80        };
81        Ok(ret)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use rstest::rstest;
88    use serde_json::{Number, Value};
89    use similar_asserts::assert_eq;
90    use testresult::TestResult;
91
92    use crate::asl::fixtures::execution_input;
93    use crate::asl::types::execution::ExecutionInput;
94
95    use super::*;
96
97    #[rstest]
98    #[case("$.path", execution_input().value.get("path").cloned())]
99    #[case("$.number", execution_input().value.get("number").cloned())]
100    #[case("$.number-float", execution_input().value.get("number-float").cloned())]
101    #[case("$.null", execution_input().value.get("null").cloned())]
102    #[case("$.inexistent-field", None)]
103    #[case("$$", Some(execution_input().context.clone()))]
104    #[case("$$.path", execution_input().context.get("path").cloned())]
105    #[case("$$.inexistent-field", None)]
106    fn value(
107        #[case] path_string: &str,
108        #[case] expected: Option<Value>,
109        execution_input: ExecutionInput,
110    ) -> TestResult {
111        let input_path = path_string.parse::<ExecutionPath<Value>>()?;
112        let actual = input_path.evaluate(&execution_input)?;
113        assert_eq!(actual, expected);
114        Ok(())
115    }
116
117    #[rstest]
118    #[case("$.number", Some(Number::from(42)))]
119    #[case("$.number-float", Some(Number::from_f64(42.5).unwrap()))]
120    #[case("$.inexistent-field", None)]
121    fn number(
122        #[case] path_string: &str,
123        #[case] expected: Option<Number>,
124        execution_input: ExecutionInput,
125    ) -> TestResult {
126        let input_path = path_string.parse::<ExecutionPath<Number>>()?;
127        let actual = input_path.evaluate(&execution_input)?;
128        assert_eq!(actual, expected);
129        Ok(())
130    }
131
132    #[rstest]
133    #[case("$.boolean-true")]
134    fn boolean(#[case] path_string: &str, execution_input: ExecutionInput) -> TestResult {
135        let input_path = path_string.parse::<ExecutionPath<bool>>()?;
136        let actual = input_path.evaluate(&execution_input)?.unwrap();
137        assert!(actual);
138        Ok(())
139    }
140
141    #[rstest]
142    #[case("$")]
143    #[case("$.path")]
144    #[case("$.array_of_numbers")]
145    #[case("$$")]
146    #[case("$$.path")]
147    fn number_error(#[case] path_string: &str, execution_input: ExecutionInput) -> TestResult {
148        let input_path = path_string.parse::<ExecutionPath<Number>>()?;
149        let _ = input_path.evaluate(&execution_input).unwrap_err();
150        Ok(())
151    }
152}