remote_settings/
jexl_filter.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::RemoteSettingsContext;
6use firefox_versioning::compare::version_compare;
7use jexl_eval::Evaluator;
8use serde_json::{json, Value};
9use thiserror::Error;
10
11#[derive(Error, Debug)]
12pub(crate) enum ParseError {
13    #[error("Evaluation error: {0}")]
14    EvaluationError(String),
15    #[error("Invalid result type")]
16    InvalidResultType,
17}
18
19/// The JEXL filter is getting instatiated when a new `RemoteSettingsClient` is being created.
20pub struct JexlFilter {
21    /// a JEXL `Evaluator` to run transforms and evaluations on.
22    evaluator: Evaluator<'static>,
23    /// The transformed `RemoteSettingsContext` as a `serde_json::Value`
24    context: Value,
25}
26
27impl JexlFilter {
28    /// Creating a new `JEXL` filter. If no `context` is set, all future `records` are being
29    /// evaluated as `true` by default.
30    pub(crate) fn new(context: Option<RemoteSettingsContext>) -> Self {
31        let env_context = match context {
32            Some(ctx) => json!({ "env": ctx.into_env() }),
33            None => json!({ "env": {} }),
34        };
35
36        Self {
37            evaluator: Evaluator::new()
38                .with_transform("versionCompare", |args| Ok(version_compare(args)?)),
39            context: env_context,
40        }
41    }
42
43    /// Evaluates the given filter expression in the provided context.
44    /// Returns `Ok(true)` if the expression evaluates to true, `Ok(false)` otherwise.
45    pub(crate) fn evaluate(&self, filter_expr: &str) -> Result<bool, ParseError> {
46        if filter_expr.trim().is_empty() {
47            return Ok(true);
48        }
49
50        let result = self
51            .evaluator
52            .eval_in_context(filter_expr, &self.context)
53            .map_err(|e| {
54                ParseError::EvaluationError(format!("Failed to evaluate '{}': {}", filter_expr, e))
55            })?;
56
57        result.as_bool().ok_or(ParseError::InvalidResultType)
58    }
59}