#![allow(clippy::pattern_type_mismatch)]
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use anyhow::{anyhow, bail, Result};
use crate::languages::azure_policy::ast::{Expr, ExprLiteral, JsonValue, ObjectEntry};
use crate::Value;
pub(super) fn extract_string_literal(expr: &Expr) -> Result<String> {
match expr {
Expr::Literal {
value: ExprLiteral::String(value),
..
} => Ok(value.clone()),
other => bail!("expected string literal argument, found {:?}", other),
}
}
pub(super) fn split_count_wildcard_path(path: &str) -> Result<(String, Option<String>)> {
let wildcard_index = path
.find("[*]")
.ok_or_else(|| anyhow!("wildcard path must contain [*]: {}", path))?;
let (prefix_str, rest) = path.split_at(wildcard_index);
let prefix = prefix_str.trim_end_matches('.');
if prefix.is_empty() {
bail!(
"wildcard path must have a non-empty prefix before [*]: {}",
path
);
}
let after_wildcard = rest.strip_prefix("[*]").ok_or_else(|| {
anyhow!(
"wildcard path could not be parsed after [*] split: {}",
path
)
})?;
let suffix_str = after_wildcard.trim_start_matches('.');
let suffix = if suffix_str.is_empty() {
None
} else {
Some(suffix_str.to_string())
};
Ok((prefix.to_string(), suffix))
}
pub(super) fn split_path_without_wildcards(path: &str) -> Result<Vec<String>> {
if path.trim().is_empty() {
bail!("empty path");
}
if path.contains("[*]") {
bail!(
"wildcard field paths are not supported in this context: {}",
path
);
}
if path.ends_with('.') {
bail!("path must not end with '.': {}", path);
}
let mut parts = Vec::new();
let mut token = String::new();
let mut bracket = String::new();
let mut in_bracket = false;
let mut after_bracket = false;
for ch in path.chars() {
match ch {
'.' if !in_bracket => {
let t = token.trim();
if t.is_empty() && !after_bracket {
bail!("empty segment in path: {}", path);
}
if !t.is_empty() {
parts.push(t.to_string());
}
token.clear();
after_bracket = false;
}
'[' => {
if in_bracket {
bail!("nested brackets in path: {}", path);
}
in_bracket = true;
after_bracket = false;
let t = token.trim();
if !t.is_empty() {
parts.push(t.to_string());
}
token.clear();
}
']' => {
if !in_bracket {
bail!("unexpected closing bracket in path: {}", path);
}
in_bracket = false;
after_bracket = true;
let cleaned = bracket
.trim()
.trim_matches('"')
.trim_matches('\'')
.to_string();
if cleaned.is_empty() {
bail!("empty bracket in path: {}", path);
}
parts.push(cleaned);
bracket.clear();
}
_ => {
if in_bracket {
bracket.push(ch);
} else {
if after_bracket {
bail!("expected '.' or '[' after bracket in path: {}", path);
}
token.push(ch);
}
}
}
}
if in_bracket {
bail!("unclosed bracket in path: {}", path);
}
let t = token.trim();
if !t.is_empty() {
parts.push(t.to_string());
}
Ok(parts)
}
pub(super) fn json_value_to_runtime(value: &JsonValue) -> Result<Value> {
match value {
JsonValue::Null(_) => Ok(Value::Null),
JsonValue::Bool(_, b) => Ok(Value::Bool(*b)),
JsonValue::Number(_, raw) => {
Value::from_numeric_string(raw).map_err(|_| anyhow!("invalid number literal: {}", raw))
}
JsonValue::Str(_, s) => {
s.strip_prefix("[[").map_or_else(
|| Ok(Value::from(s.clone())),
|unescaped| Ok(Value::from(alloc::format!("[{unescaped}"))),
)
}
JsonValue::Array(_, items) => {
let mut out = Vec::with_capacity(items.len());
for item in items {
out.push(json_value_to_runtime(item)?);
}
Ok(Value::from(out))
}
JsonValue::Object(_, entries) => {
let mut obj = Value::new_object();
let map = obj.as_object_mut()?;
for ObjectEntry {
key,
value: entry_value,
..
} in entries
{
map.insert(
Value::from(key.clone()),
json_value_to_runtime(entry_value)?,
);
}
Ok(obj)
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use alloc::vec;
use crate::lexer::Source;
use super::*;
fn dummy_span() -> crate::lexer::Span {
let source = Source::from_contents("test".into(), " ".into()).unwrap();
crate::lexer::Span {
source,
line: 1,
col: 1,
start: 0,
end: 0,
}
}
#[test]
fn extract_string_literal_ok() {
let expr = Expr::Literal {
span: dummy_span(),
value: ExprLiteral::String("hello".into()),
};
assert_eq!(extract_string_literal(&expr).unwrap(), "hello");
}
#[test]
fn extract_string_literal_number_err() {
let expr = Expr::Literal {
span: dummy_span(),
value: ExprLiteral::Number("42".into()),
};
extract_string_literal(&expr).unwrap_err();
}
#[test]
fn extract_string_literal_ident_err() {
let expr = Expr::Ident {
span: dummy_span(),
name: "x".into(),
};
extract_string_literal(&expr).unwrap_err();
}
#[test]
fn json_null() {
let v = json_value_to_runtime(&JsonValue::Null(dummy_span())).unwrap();
assert_eq!(v, Value::Null);
}
#[test]
fn json_bool() {
let v = json_value_to_runtime(&JsonValue::Bool(dummy_span(), true)).unwrap();
assert_eq!(v, Value::Bool(true));
}
#[test]
fn json_number_int() {
let v = json_value_to_runtime(&JsonValue::Number(dummy_span(), "42".into())).unwrap();
assert_eq!(v, Value::from(42_i64));
}
#[test]
fn json_number_float() {
let v = json_value_to_runtime(&JsonValue::Number(dummy_span(), "1.5".into())).unwrap();
assert_eq!(v, Value::from(1.5_f64));
}
#[test]
fn json_number_invalid() {
json_value_to_runtime(&JsonValue::Number(dummy_span(), "abc".into())).unwrap_err();
}
#[test]
fn json_string() {
let v = json_value_to_runtime(&JsonValue::Str(dummy_span(), "hello".into())).unwrap();
assert_eq!(v, Value::from("hello".to_string()));
}
#[test]
fn json_string_double_bracket_escape() {
let v = json_value_to_runtime(&JsonValue::Str(dummy_span(), "[[escaped]".into())).unwrap();
assert_eq!(v, Value::from("[escaped]".to_string()));
}
#[test]
fn json_array() {
let arr = JsonValue::Array(
dummy_span(),
vec![
JsonValue::Bool(dummy_span(), true),
JsonValue::Null(dummy_span()),
],
);
let v = json_value_to_runtime(&arr).unwrap();
let items = v.as_array().unwrap();
assert_eq!(items.len(), 2);
}
#[test]
fn json_object() {
let obj = JsonValue::Object(
dummy_span(),
vec![ObjectEntry {
key_span: dummy_span(),
key: "k".into(),
value: JsonValue::Bool(dummy_span(), false),
}],
);
let v = json_value_to_runtime(&obj).unwrap();
let map = v.as_object().unwrap();
assert_eq!(map.len(), 1);
}
#[test]
fn wildcard_simple() {
let (prefix, suffix) = split_count_wildcard_path("a.b[*].c").unwrap();
assert_eq!(prefix, "a.b");
assert_eq!(suffix.as_deref(), Some("c"));
}
#[test]
fn wildcard_no_suffix() {
let (prefix, suffix) = split_count_wildcard_path("a[*]").unwrap();
assert_eq!(prefix, "a");
assert_eq!(suffix, None);
}
#[test]
fn wildcard_trailing_dot_prefix() {
let (prefix, _) = split_count_wildcard_path("a.[*].c").unwrap();
assert_eq!(prefix, "a");
}
#[test]
fn wildcard_missing() {
split_count_wildcard_path("a.b.c").unwrap_err();
}
#[test]
fn wildcard_empty_prefix() {
split_count_wildcard_path("[*].c").unwrap_err();
}
#[test]
fn wildcard_nested() {
let (prefix, suffix) = split_count_wildcard_path("a[*].b[*].c").unwrap();
assert_eq!(prefix, "a");
assert_eq!(suffix.as_deref(), Some("b[*].c"));
}
#[test]
fn path_simple_dotted() {
assert_eq!(
split_path_without_wildcards("a.b.c").unwrap(),
vec!["a", "b", "c"]
);
}
#[test]
fn path_bracket_quoted() {
assert_eq!(
split_path_without_wildcards("tags['key']").unwrap(),
vec!["tags", "key"]
);
}
#[test]
fn path_bracket_numeric() {
assert_eq!(
split_path_without_wildcards("a[0].b").unwrap(),
vec!["a", "0", "b"]
);
}
#[test]
fn path_single_segment() {
assert_eq!(split_path_without_wildcards("name").unwrap(), vec!["name"]);
}
#[test]
fn path_empty() {
split_path_without_wildcards("").unwrap_err();
}
#[test]
fn path_whitespace_only() {
split_path_without_wildcards(" ").unwrap_err();
}
#[test]
fn path_trailing_dot() {
split_path_without_wildcards("a.").unwrap_err();
}
#[test]
fn path_leading_dot() {
split_path_without_wildcards(".a").unwrap_err();
}
#[test]
fn path_consecutive_dots() {
split_path_without_wildcards("a..b").unwrap_err();
}
#[test]
fn path_wildcard_rejected() {
split_path_without_wildcards("a[*].b").unwrap_err();
}
#[test]
fn path_nested_brackets() {
split_path_without_wildcards("a[[0]]").unwrap_err();
}
#[test]
fn path_stray_close_bracket() {
split_path_without_wildcards("a]b").unwrap_err();
}
#[test]
fn path_unclosed_bracket() {
split_path_without_wildcards("a[0").unwrap_err();
}
#[test]
fn path_empty_bracket() {
split_path_without_wildcards("a[]").unwrap_err();
}
#[test]
fn path_char_after_bracket_without_separator() {
split_path_without_wildcards("a[0]b").unwrap_err();
}
}