Skip to main content

vantage_cmd/
condition.rs

1//! Conditions for command-backed tables.
2//!
3//! Conditions are peeled into `{ field, op, value }` shapes and handed to
4//! the Rhai script, which decides how (or whether) to translate each into
5//! a CLI flag. As a safety net, plain `Eq` conditions are also re-applied
6//! client-side against the returned rows (see `table_source`), mirroring
7//! `vantage-aws`.
8//!
9//! `Deferred` exists only to make `with_one` / `with_many` traversal work:
10//! it is resolved to `Eq` / `In` (by awaiting the embedded subquery) in
11//! `list_table_values`, *before* the script ever sees the conditions.
12
13use ciborium::Value as CborValue;
14use vantage_expressions::Expression;
15
16/// A filter on a command-backed table.
17#[derive(Clone)]
18pub enum CmdCondition {
19    /// `field == value`.
20    Eq { field: String, value: CborValue },
21    /// `field in values` — a parent → child traversal usually yields one.
22    In {
23        field: String,
24        values: Vec<CborValue>,
25    },
26    /// `field == value` where the value comes from another query. Resolved
27    /// at read time before the script runs.
28    Deferred {
29        field: String,
30        source: Expression<CborValue>,
31    },
32}
33
34// Manual Debug — `Expression<CborValue>` doesn't impl Debug (ciborium's
35// Value has no Display), so we render structurally.
36impl std::fmt::Debug for CmdCondition {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Self::Eq { field, value } => f
40                .debug_struct("Eq")
41                .field("field", field)
42                .field("value", value)
43                .finish(),
44            Self::In { field, values } => f
45                .debug_struct("In")
46                .field("field", field)
47                .field("values", values)
48                .finish(),
49            Self::Deferred { field, source } => f
50                .debug_struct("Deferred")
51                .field("field", field)
52                .field("source.template", &source.template)
53                .finish(),
54        }
55    }
56}
57
58impl CmdCondition {
59    pub fn eq(field: impl Into<String>, value: impl Into<CborValue>) -> Self {
60        Self::Eq {
61            field: field.into(),
62            value: value.into(),
63        }
64    }
65
66    pub fn in_<I, V>(field: impl Into<String>, values: I) -> Self
67    where
68        I: IntoIterator<Item = V>,
69        V: Into<CborValue>,
70    {
71        Self::In {
72            field: field.into(),
73            values: values.into_iter().map(Into::into).collect(),
74        }
75    }
76
77    pub fn field(&self) -> &str {
78        match self {
79            Self::Eq { field, .. } | Self::In { field, .. } | Self::Deferred { field, .. } => field,
80        }
81    }
82
83    /// The operator name exposed to Rhai (`"eq"` / `"in"`). Never called on
84    /// `Deferred` — those are resolved before the script sees them.
85    pub(crate) fn op(&self) -> &'static str {
86        match self {
87            Self::Eq { .. } => "eq",
88            Self::In { .. } => "in",
89            Self::Deferred { .. } => "eq",
90        }
91    }
92
93    /// The value exposed to Rhai, as JSON: a scalar for `Eq`, an array for `In`.
94    pub(crate) fn json_value(&self) -> serde_json::Value {
95        match self {
96            Self::Eq { value, .. } => crate::types::cbor_to_json(value),
97            Self::In { values, .. } => {
98                serde_json::Value::Array(values.iter().map(crate::types::cbor_to_json).collect())
99            }
100            Self::Deferred { .. } => serde_json::Value::Null,
101        }
102    }
103}
104
105/// `field == value`. Shorthand for [`CmdCondition::eq`].
106///
107/// ```
108/// # use vantage_cmd::eq;
109/// let cond = eq("logGroupNamePrefix", "/aws/lambda/");
110/// ```
111pub fn eq(field: impl Into<String>, value: impl Into<CborValue>) -> CmdCondition {
112    CmdCondition::eq(field, value)
113}