newton-regorus 0.2.0

A fast, lightweight Rego (OPA policy language) interpreter with Newton extensions
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#![allow(clippy::pattern_type_mismatch)]

use crate::{
    ast::{Expr, Ref},
    builtins,
    builtins::utils::{ensure_args_count, ensure_string, ensure_string_collection},
    lexer::Span,
    value::Value,
    *,
};

use anyhow::{bail, Result};
use globset::{GlobBuilder, GlobMatcher};

pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
    m.insert("glob.match", (glob_match, 3));
    m.insert("glob.quote_meta", (quote_meta, 1));
}

const PLACE_HOLDER: &str = "\0";

fn suppress_unix_style_delimiter(s: &str) -> Result<String> {
    // Replace unix-style delimiter with placeholder so that no delimiter is
    // encountered during glob matching.
    Ok(s.replace('/', PLACE_HOLDER))
}

fn make_delimiters_unix_style(s: &str, delimiters: &[char]) -> Result<String> {
    if s.contains(PLACE_HOLDER) {
        bail!("string contains internal glob placeholder");
    }

    let has_unix_style = delimiters.contains(&'/');

    let mut s = if !has_unix_style {
        suppress_unix_style_delimiter(s)?
    } else {
        s.to_string()
    };

    for d in delimiters {
        if *d == ':' {
            s = s.replace(*d, PLACE_HOLDER);
        } else if *d != '/' {
            // Insert / before occurances of delimiter.
            s = s.replace(*d, format!("/{}/", *d).as_str());
        }
    }

    Ok(s)
}

fn make_glob(pattern: &str, span: &Span) -> Result<GlobMatcher> {
    Ok(GlobBuilder::new(pattern)
        .literal_separator(true)
        .build()
        .or_else(|_| bail!(span.error("invalid glob")))?
        .compile_matcher())
}

fn glob_match(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
    let name = "glob.match";
    ensure_args_count(span, name, params, args, 3)?;

    let pattern = ensure_string(name, &params[0], &args[0])?;
    let value = ensure_string(name, &params[2], &args[2])?;

    let pattern = pattern.as_ref();
    let value = value.as_ref();

    if let Value::Null = &args[1] {
        // Ensure that / is not treated as a delimiter.
        let value = suppress_unix_style_delimiter(value)?;
        let pattern = suppress_unix_style_delimiter(pattern)?;

        let glob = make_glob(&pattern, params[0].span())?;
        return Ok(Value::Bool(glob.is_match(&value[..])));
    }

    let delimiters = if let Value::Array(_) = &args[1] {
        ensure_string_collection(name, &params[1], &args[1])?
    } else {
        bail!(params[1]
            .span()
            .error(format!("{name} requires string array").as_str()));
    };

    if delimiters.iter().any(|d| d.len() > 1) {
        bail!(params[1]
            .span()
            .error("delimiters must be single character"));
    }

    let mut delimiters: Vec<char> = delimiters.iter().filter_map(|d| d.chars().next()).collect();

    if delimiters.is_empty() {
        delimiters.push('.');
    }

    let pattern = make_delimiters_unix_style(pattern, &delimiters)?;
    let value = make_delimiters_unix_style(value, &delimiters)?;

    let glob = make_glob(&pattern, params[0].span())?;
    Ok(Value::Bool(glob.is_match(&value[..])))
}

fn quote_meta(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
    let name = "glob.quote_meta";
    ensure_args_count(span, name, params, args, 1)?;

    let pattern = ensure_string(name, &params[0], &args[0])?;
    // Ensure that the glob is valid.
    let _ = make_glob(&pattern, params[0].span())?;
    Ok(Value::String(pattern.as_ref().replace('*', "\\*").into()))
}