Skip to main content

objectiveai_sdk/functions/expression/
expression.rs

1//! Core expression types for JMESPath and Starlark evaluation.
2
3use super::special::FromSpecial;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize, de::DeserializeOwned};
6
7/// Result of an expression that may produce one or many values.
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
9#[serde(untagged)]
10#[schemars(rename = "functions.expression.OneOrMany.{T}")]
11pub enum OneOrMany<T> {
12    /// A single value.
13    #[schemars(title = "One")]
14    One(T),
15    /// Multiple values (from array expressions).
16    #[schemars(title = "Many")]
17    Many(Vec<T>),
18}
19
20/// An expression that can be either JMESPath or Starlark.
21///
22/// Serializes as `{"$jmespath": "..."}` or `{"$starlark": "..."}` in JSON.
23///
24/// # Examples
25///
26/// JMESPath:
27/// ```json
28/// {"$jmespath": "input.items[0].name"}
29/// ```
30///
31/// Starlark:
32/// ```json
33/// {"$starlark": "input['items'][0]['name']"}
34/// ```
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
36#[serde(deny_unknown_fields)]
37#[schemars(rename = "functions.expression.Expression")]
38pub enum Expression {
39    /// A JMESPath expression.
40    #[schemars(title = "JMESPath")]
41    #[serde(rename = "$jmespath")]
42    JMESPath(String),
43    /// A Starlark expression.
44    #[schemars(title = "Starlark")]
45    #[serde(rename = "$starlark")]
46    Starlark(String),
47    /// A predefined special expression variant.
48    #[schemars(title = "Special")]
49    #[serde(rename = "$special")]
50    Special(super::Special),
51}
52
53impl Expression {
54    /// Compiles the expression, allowing array results.
55    ///
56    /// Returns `OneOrMany::One` for single values or `OneOrMany::Many` for arrays.
57    /// Null values are filtered out from array results.
58    /// A Single Null value is treated as an empty array.
59    pub fn compile_one_or_many<T>(
60        &self,
61        params: &super::Params,
62    ) -> Result<OneOrMany<T>, super::ExpressionError>
63    where
64        T: DeserializeOwned
65            + super::starlark::FromStarlarkValue
66            + super::special::FromSpecial,
67    {
68        match self {
69            Expression::JMESPath(jmespath) => {
70                let expr = super::JMESPATH_RUNTIME.compile(jmespath)?;
71                let value = expr.search(params)?;
72                let json = serde_json::to_value(value)?;
73                Self::deserialize_result(json)
74            }
75            Expression::Starlark(starlark) => {
76                OneOrMany::<T>::from_starlark(starlark, params)
77            }
78            Expression::Special(special) => {
79                OneOrMany::<T>::from_special(special, params)
80            }
81        }
82    }
83
84    /// Deserialize expression result to the expected type.
85    fn deserialize_result<T>(
86        value: serde_json::Value,
87    ) -> Result<OneOrMany<T>, super::ExpressionError>
88    where
89        T: DeserializeOwned,
90    {
91        let value: Option<OneOrMany<Option<T>>> = serde_json::from_value(value)
92            .map_err(super::ExpressionError::DeserializationError)?;
93        Ok(match value {
94            Some(OneOrMany::One(Some(v))) => OneOrMany::One(v),
95            Some(OneOrMany::One(None)) => OneOrMany::Many(Vec::new()),
96            Some(OneOrMany::Many(mut vs)) => {
97                vs.retain(|v| v.is_some());
98                if vs.is_empty() {
99                    OneOrMany::Many(Vec::new())
100                } else if vs.len() == 1 {
101                    OneOrMany::One(vs.into_iter().flatten().next().unwrap())
102                } else {
103                    OneOrMany::Many(vs.into_iter().flatten().collect())
104                }
105            }
106            None => OneOrMany::Many(Vec::new()),
107        })
108    }
109
110    /// Compiles the expression, expecting exactly one value.
111    ///
112    /// Accepts a single value or an array with exactly one element.
113    /// Returns an error if the expression evaluates to null, an empty array,
114    /// or an array with more than one element.
115    pub fn compile_one<T>(
116        &self,
117        params: &super::Params,
118    ) -> Result<T, super::ExpressionError>
119    where
120        T: DeserializeOwned
121            + super::starlark::FromStarlarkValue
122            + super::special::FromSpecial,
123    {
124        let result = self.compile_one_or_many(params)?;
125        match result {
126            OneOrMany::One(value) => Ok(value),
127            OneOrMany::Many(_) => {
128                Err(super::ExpressionError::ExpectedOneValueFoundMany)
129            }
130        }
131    }
132}
133
134/// A value that can be either a literal or an expression.
135///
136/// This allows Function definitions to mix static values with dynamic
137/// expressions. During compilation, expressions are evaluated while
138/// literal values pass through unchanged.
139///
140/// # Example
141///
142/// Literal value:
143/// ```json
144/// "hello world"
145/// ```
146///
147/// JMESPath expression:
148/// ```json
149/// {"$jmespath": "input.greeting"}
150/// ```
151///
152/// Starlark expression:
153/// ```json
154/// {"$starlark": "input['greeting']"}
155/// ```
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
157#[serde(untagged)]
158#[schemars(rename = "functions.expression.WithExpression.{T}")]
159pub enum WithExpression<T> {
160    /// An expression (JMESPath or Starlark) to evaluate.
161    #[schemars(title = "Expression")]
162    Expression(Expression),
163    /// A literal value.
164    #[schemars(title = "Value")]
165    Value(T),
166}
167
168impl<T> std::default::Default for WithExpression<T>
169where
170    T: Default,
171{
172    fn default() -> Self {
173        WithExpression::Value(T::default())
174    }
175}
176
177impl<T> WithExpression<T>
178where
179    T: DeserializeOwned
180        + super::starlark::FromStarlarkValue
181        + super::special::FromSpecial,
182{
183    /// Compiles the value, allowing array results from expressions.
184    ///
185    /// Literal values always return `OneOrMany::One`. Expressions may return
186    /// either one or many values.
187    pub fn compile_one_or_many(
188        self,
189        params: &super::Params,
190    ) -> Result<OneOrMany<T>, super::ExpressionError> {
191        match self {
192            WithExpression::Expression(expr) => {
193                expr.compile_one_or_many(params)
194            }
195            WithExpression::Value(value) => Ok(OneOrMany::One(value)),
196        }
197    }
198
199    /// Compiles the value, expecting exactly one result.
200    ///
201    /// Literal values pass through unchanged. Expressions must evaluate to
202    /// a single value or an array with exactly one element.
203    pub fn compile_one(
204        self,
205        params: &super::Params,
206    ) -> Result<T, super::ExpressionError> {
207        match self {
208            WithExpression::Expression(expr) => expr.compile_one(params),
209            WithExpression::Value(value) => Ok(value),
210        }
211    }
212}
213
214impl<T: super::starlark::FromStarlarkValue> super::starlark::FromStarlarkValue
215    for WithExpression<T>
216{
217    fn from_starlark_value(
218        value: &starlark::values::Value,
219    ) -> Result<Self, super::ExpressionError> {
220        T::from_starlark_value(value).map(WithExpression::Value)
221    }
222}
223
224impl<T: super::special::FromSpecial> FromSpecial
225    for super::expression::WithExpression<T>
226{
227    fn from_special(
228        special: &super::special::Special,
229        params: &super::Params,
230    ) -> Result<Self, super::ExpressionError> {
231        Ok(super::expression::WithExpression::Value(T::from_special(
232            special, params,
233        )?))
234    }
235}
236
237impl<T> WithExpression<Option<T>> {
238    pub fn is_none(&self) -> bool {
239        matches!(self, WithExpression::Value(None))
240    }
241}
242