1use std::cmp::Ordering;
11use std::fmt;
12
13use qubit_datatype::NumericComparisonPolicy;
14use qubit_redact::Redact;
15use qubit_redact::RedactionWriter;
16use qubit_redact::Redactor;
17use qubit_redact::Sensitivity;
18use qubit_value::Value;
19use qubit_value::ValueRef;
20use qubit_value::ValueWirePayloadRefV1;
21
22use super::internal::MatchOutcome;
23use crate::FilterLimitKind;
24use crate::FilterLimits;
25use crate::Metadata;
26use crate::MetadataError;
27use crate::MetadataResult;
28
29#[derive(Clone, PartialEq)]
48#[non_exhaustive]
49pub enum Condition {
50 Equal {
52 key: String,
54 value: Value,
56 },
57 NotEqual {
59 key: String,
61 value: Value,
63 },
64 Less {
66 key: String,
68 value: Value,
70 },
71 LessEqual {
73 key: String,
75 value: Value,
77 },
78 Greater {
80 key: String,
82 value: Value,
84 },
85 GreaterEqual {
87 key: String,
89 value: Value,
91 },
92 In {
94 key: String,
96 values: Vec<Value>,
98 },
99 NotIn {
101 key: String,
103 values: Vec<Value>,
105 },
106 Exists {
111 key: String,
113 },
114 NotExists {
119 key: String,
121 },
122}
123
124impl Condition {
125 #[cfg(feature = "json")]
127 pub(crate) fn visit_operands<E>(&self, visitor: &mut impl FnMut(&Value) -> Result<(), E>) -> Result<(), E> {
128 match self {
129 Self::Equal { value, .. }
130 | Self::NotEqual { value, .. }
131 | Self::Less { value, .. }
132 | Self::LessEqual { value, .. }
133 | Self::Greater { value, .. }
134 | Self::GreaterEqual { value, .. } => visitor(value),
135 Self::In { values, .. } | Self::NotIn { values, .. } => {
136 for value in values {
137 visitor(value)?;
138 }
139 Ok(())
140 }
141 Self::Exists { .. } | Self::NotExists { .. } => Ok(()),
142 }
143 }
144
145 pub(crate) fn validate_operands(&self) -> MetadataResult<()> {
153 match self {
154 Self::Equal { value, .. } => validate_operand("eq", value),
155 Self::NotEqual { value, .. } => validate_operand("ne", value),
156 Self::Less { value, .. } => validate_operand("lt", value),
157 Self::LessEqual { value, .. } => validate_operand("le", value),
158 Self::Greater { value, .. } => validate_operand("gt", value),
159 Self::GreaterEqual { value, .. } => validate_operand("ge", value),
160 Self::In { values, .. } => validate_operands("in_set", values),
161 Self::NotIn { values, .. } => validate_operands("not_in_set", values),
162 Self::Exists { .. } | Self::NotExists { .. } => Ok(()),
163 }
164 }
165
166 pub(crate) fn validate_limits(&self, limits: FilterLimits) -> MetadataResult<()> {
181 let key = self.key();
182 if key.len() > limits.max_key_bytes() {
183 return Err(MetadataError::FilterLimitExceeded {
184 kind: FilterLimitKind::KeyBytes,
185 value: key.len(),
186 maximum: limits.max_key_bytes(),
187 });
188 }
189 let values = match self {
190 Self::In { values, .. } | Self::NotIn { values, .. } => values,
191 _ => return Ok(()),
192 };
193 if values.len() > limits.max_set_values() {
194 return Err(MetadataError::FilterLimitExceeded {
195 kind: FilterLimitKind::SetValues,
196 value: values.len(),
197 maximum: limits.max_set_values(),
198 });
199 }
200 Ok(())
201 }
202
203 pub(crate) fn evaluate(&self, meta: &Metadata, numeric_comparison_policy: NumericComparisonPolicy) -> MatchOutcome {
216 match self {
217 Condition::Equal { key, value } => evaluate_concrete(meta, key, |stored| {
218 values_equal(stored, value, numeric_comparison_policy)
219 .map_or(MatchOutcome::Unknown, MatchOutcome::from_bool)
220 }),
221 Condition::NotEqual { key, value } => evaluate_concrete(meta, key, |stored| {
222 values_equal(stored, value, numeric_comparison_policy)
223 .map_or(MatchOutcome::Unknown, |equal| MatchOutcome::from_bool(!equal))
224 }),
225 Condition::Less { key, value } => evaluate_concrete(meta, key, |stored| {
226 compare_values(stored, value, numeric_comparison_policy).map_or(MatchOutcome::Unknown, |ordering| {
227 MatchOutcome::from_bool(ordering == Ordering::Less)
228 })
229 }),
230 Condition::LessEqual { key, value } => evaluate_concrete(meta, key, |stored| {
231 compare_values(stored, value, numeric_comparison_policy).map_or(MatchOutcome::Unknown, |ordering| {
232 MatchOutcome::from_bool(matches!(ordering, Ordering::Less | Ordering::Equal))
233 })
234 }),
235 Condition::Greater { key, value } => evaluate_concrete(meta, key, |stored| {
236 compare_values(stored, value, numeric_comparison_policy).map_or(MatchOutcome::Unknown, |ordering| {
237 MatchOutcome::from_bool(ordering == Ordering::Greater)
238 })
239 }),
240 Condition::GreaterEqual { key, value } => evaluate_concrete(meta, key, |stored| {
241 compare_values(stored, value, numeric_comparison_policy).map_or(MatchOutcome::Unknown, |ordering| {
242 MatchOutcome::from_bool(matches!(ordering, Ordering::Greater | Ordering::Equal))
243 })
244 }),
245 Condition::In { key, values } => evaluate_concrete(meta, key, |stored| {
246 evaluate_membership(stored, values, numeric_comparison_policy, false)
247 }),
248 Condition::NotIn { key, values } => evaluate_concrete(meta, key, |stored| {
249 evaluate_membership(stored, values, numeric_comparison_policy, true)
250 }),
251 Condition::Exists { key } => MatchOutcome::from_bool(concrete_value(meta, key).is_some()),
252 Condition::NotExists { key } => MatchOutcome::from_bool(concrete_value(meta, key).is_none()),
253 }
254 }
255
256 #[inline]
258 fn key(&self) -> &str {
259 match self {
260 Self::Equal { key, .. }
261 | Self::NotEqual { key, .. }
262 | Self::Less { key, .. }
263 | Self::LessEqual { key, .. }
264 | Self::Greater { key, .. }
265 | Self::GreaterEqual { key, .. }
266 | Self::In { key, .. }
267 | Self::NotIn { key, .. }
268 | Self::Exists { key }
269 | Self::NotExists { key } => key,
270 }
271 }
272}
273
274fn validate_operand(operator: &'static str, value: &Value) -> MetadataResult<()> {
286 if value.is_unset() {
287 return Err(MetadataError::InvalidFilterOperand {
288 operator,
289 data_type: value.data_type(),
290 message: "filter operands must be concrete values".to_owned(),
291 });
292 }
293 if ValueWirePayloadRefV1::try_from(value).is_err() {
294 return Err(MetadataError::InvalidFilterOperand {
295 operator,
296 data_type: value.data_type(),
297 message: "filter operands must be representable by the V1 wire format".to_owned(),
298 });
299 }
300 Ok(())
301}
302
303fn validate_operands(operator: &'static str, values: &[Value]) -> MetadataResult<()> {
314 values.iter().try_for_each(|value| validate_operand(operator, value))
315}
316
317impl Redact for Condition {
318 fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
334 let (operator, operand): (&str, Option<&dyn fmt::Debug>) = match self {
335 Self::Equal { value, .. } => ("equal", Some(value)),
336 Self::NotEqual { value, .. } => ("not_equal", Some(value)),
337 Self::Less { value, .. } => ("less", Some(value)),
338 Self::LessEqual { value, .. } => ("less_equal", Some(value)),
339 Self::Greater { value, .. } => ("greater", Some(value)),
340 Self::GreaterEqual { value, .. } => ("greater_equal", Some(value)),
341 Self::In { values, .. } => ("in", Some(values)),
342 Self::NotIn { values, .. } => ("not_in", Some(values)),
343 Self::Exists { .. } => ("exists", None),
344 Self::NotExists { .. } => ("not_exists", None),
345 };
346 writer.record("Condition", |fields| {
347 fields.unredacted("operator", || operator);
348 fields.unredacted("key", || self.key());
349 if let Some(operand) = operand {
350 fields.sensitive_at_least(Sensitivity::Secret, "value", || operand);
351 }
352 });
353 }
354}
355
356impl fmt::Debug for Condition {
357 #[inline]
359 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
360 let output = Redactor::strict().redact_text(self);
361 let text = output.text_or_marker("<redaction incomplete>");
362 formatter.write_str(text.as_ref())
363 }
364}
365
366#[inline]
379fn evaluate_concrete<F>(meta: &Metadata, key: &str, predicate: F) -> MatchOutcome
380where
381 F: FnOnce(&Value) -> MatchOutcome,
382{
383 concrete_value(meta, key).map_or(MatchOutcome::Unknown, predicate)
384}
385
386fn evaluate_membership(
400 stored: &Value,
401 candidates: &[Value],
402 numeric_comparison_policy: NumericComparisonPolicy,
403 negated: bool,
404) -> MatchOutcome {
405 let mut unknown = false;
406 for candidate in candidates {
407 match values_equal(stored, candidate, numeric_comparison_policy) {
408 Some(true) => return MatchOutcome::from_bool(!negated),
409 Some(false) => {}
410 None => unknown = true,
411 }
412 }
413 if unknown {
414 MatchOutcome::Unknown
415 } else {
416 MatchOutcome::from_bool(negated)
417 }
418}
419
420#[inline]
432fn concrete_value<'a>(meta: &'a Metadata, key: &str) -> Option<&'a Value> {
433 meta.get_raw(key).filter(|value| !value.is_unset())
434}
435
436#[inline]
449fn values_equal(left: &Value, right: &Value, numeric_comparison_policy: NumericComparisonPolicy) -> Option<bool> {
450 if left.is_numeric() && right.is_numeric() {
451 return left
452 .numeric_cmp(right, numeric_comparison_policy)
453 .ok()
454 .map(|ordering| ordering == Ordering::Equal);
455 }
456 if left.data_type() != right.data_type() {
457 return None;
458 }
459 Some(left == right)
460}
461
462#[inline]
474fn compare_values(left: &Value, right: &Value, numeric_comparison_policy: NumericComparisonPolicy) -> Option<Ordering> {
475 if left.is_numeric() && right.is_numeric() {
476 return left.numeric_cmp(right, numeric_comparison_policy).ok();
477 }
478 match (left.view(), right.view()) {
479 (ValueRef::String(left), ValueRef::String(right)) => left.partial_cmp(right),
480 _ => None,
481 }
482}