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