asl/asl/types/core/
execution_path.rs1use 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#[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}