use alloc::collections::BTreeSet;
use alloc::string::ToString as _;
use alloc::vec;
use crate::value::Value;
use super::actions;
use super::common;
use super::evaluator::{RbacBuiltinContext, RbacBuiltinError};
#[cfg(feature = "time")]
use chrono::{DateTime, Duration};
pub(super) fn to_lower(value: &Value) -> Result<Value, RbacBuiltinError> {
let text = common::ensure_string(value, "ToLower")?;
Ok(Value::String(text.to_ascii_lowercase().into()))
}
pub(super) fn to_upper(value: &Value) -> Result<Value, RbacBuiltinError> {
let text = common::ensure_string(value, "ToUpper")?;
Ok(Value::String(text.to_ascii_uppercase().into()))
}
pub(super) fn trim(value: &Value) -> Result<Value, RbacBuiltinError> {
let text = common::ensure_string(value, "Trim")?;
Ok(Value::String(text.trim().to_string().into()))
}
pub(super) fn normalize_set(value: &Value) -> Result<Value, RbacBuiltinError> {
match *value {
Value::Set(_) => Ok(value.clone()),
Value::Array(ref list) => {
let set: BTreeSet<Value> = list.iter().cloned().collect();
Ok(Value::from(set))
}
_ => {
let mut set = BTreeSet::new();
set.insert(value.clone());
Ok(Value::from(set))
}
}
}
pub(super) fn normalize_list(value: &Value) -> Result<Value, RbacBuiltinError> {
match *value {
Value::Array(ref list) => Ok(Value::Array(list.clone())),
Value::Set(ref set) => Ok(Value::from(
set.iter().cloned().collect::<alloc::vec::Vec<_>>(),
)),
_ => Ok(Value::from(vec![value.clone()])),
}
}
pub(super) fn add_days(date_value: &Value, days_value: &Value) -> Result<Value, RbacBuiltinError> {
let date_str = common::ensure_string(date_value, "AddDays")?;
let days = common::value_as_i64(days_value, "AddDays")?;
#[cfg(feature = "time")]
{
let dt = DateTime::parse_from_rfc3339(date_str)
.map_err(|err| RbacBuiltinError::new(err.to_string()))?;
let updated = dt
.checked_add_signed(Duration::days(days))
.ok_or_else(|| RbacBuiltinError::new("AddDays overflow"))?;
Ok(Value::String(updated.to_rfc3339().into()))
}
#[cfg(not(feature = "time"))]
{
let _ = (date_str, days);
Err(RbacBuiltinError::new(
"AddDays builtin has not been enabled",
))
}
}
pub(super) fn to_time(value: &Value) -> Result<Value, RbacBuiltinError> {
match *value {
Value::String(_) => Ok(value.clone()),
_ => Err(RbacBuiltinError::new("ToTime expects a string")),
}
}
pub(super) fn action_matches(
args: &[Value],
ctx: &RbacBuiltinContext<'_>,
) -> Result<Value, RbacBuiltinError> {
let (action, pattern) = match args.len() {
1 => (
ctx.evaluation
.action
.as_deref()
.ok_or_else(|| RbacBuiltinError::new("Missing action in context"))?,
common::ensure_string(
args.first().ok_or_else(|| {
RbacBuiltinError::new("ActionMatches expects 1 or 2 arguments")
})?,
"ActionMatches",
)?,
),
2 => (
common::ensure_string(
args.first().ok_or_else(|| {
RbacBuiltinError::new("ActionMatches expects 1 or 2 arguments")
})?,
"ActionMatches",
)?,
common::ensure_string(
args.get(1).ok_or_else(|| {
RbacBuiltinError::new("ActionMatches expects 1 or 2 arguments")
})?,
"ActionMatches",
)?,
),
_ => {
return Err(RbacBuiltinError::new(
"ActionMatches expects 1 or 2 arguments",
))
}
};
Ok(Value::Bool(actions::action_matches_pattern(
action, pattern,
)))
}
pub(super) fn suboperation_matches(
args: &[Value],
ctx: &RbacBuiltinContext<'_>,
) -> Result<Value, RbacBuiltinError> {
let (subop, pattern) = match args.len() {
1 => (
ctx.evaluation
.suboperation
.as_deref()
.ok_or_else(|| RbacBuiltinError::new("Missing suboperation in context"))?,
common::ensure_string(
args.first().ok_or_else(|| {
RbacBuiltinError::new("SubOperationMatches expects 1 or 2 arguments")
})?,
"SubOperationMatches",
)?,
),
2 => (
common::ensure_string(
args.first().ok_or_else(|| {
RbacBuiltinError::new("SubOperationMatches expects 1 or 2 arguments")
})?,
"SubOperationMatches",
)?,
common::ensure_string(
args.get(1).ok_or_else(|| {
RbacBuiltinError::new("SubOperationMatches expects 1 or 2 arguments")
})?,
"SubOperationMatches",
)?,
),
_ => {
return Err(RbacBuiltinError::new(
"SubOperationMatches expects 1 or 2 arguments",
))
}
};
Ok(Value::Bool(subop == pattern))
}