use crate::runtime::model::RuntimeValue;
pub fn call_function(callee: &str, args: &[RuntimeValue]) -> Result<RuntimeValue, String> {
match callee {
"dtcs:lower" => {
let value = args.first().ok_or("dtcs:lower requires one argument")?;
match value {
RuntimeValue::Null => Ok(RuntimeValue::Null),
RuntimeValue::String(s) => Ok(RuntimeValue::String(s.to_lowercase())),
other => Err(format!("dtcs:lower requires string, got {other:?}")),
}
}
"dtcs:upper" => {
let value = args.first().ok_or("dtcs:upper requires one argument")?;
match value {
RuntimeValue::Null => Ok(RuntimeValue::Null),
RuntimeValue::String(s) => Ok(RuntimeValue::String(s.to_uppercase())),
other => Err(format!("dtcs:upper requires string, got {other:?}")),
}
}
"dtcs:concat" => {
if args.len() < 2 {
return Err("dtcs:concat requires at least two arguments".into());
}
let mut out = String::new();
for arg in args {
let RuntimeValue::String(s) = arg else {
return Err("dtcs:concat requires string arguments".into());
};
out.push_str(s);
}
Ok(RuntimeValue::String(out))
}
"dtcs:substr" => {
let s = args
.first()
.ok_or("dtcs:substr requires at least two arguments")?;
let start = args
.get(1)
.and_then(RuntimeValue::as_integer)
.ok_or("dtcs:substr start must be integer")?;
if start < 0 {
return Err("dtcs:substr start must be non-negative".into());
}
match s {
RuntimeValue::Null => Ok(RuntimeValue::Null),
RuntimeValue::String(text) => {
let chars: Vec<char> = text.chars().collect();
let start = start as usize;
if start >= chars.len() {
return Ok(RuntimeValue::String(String::new()));
}
let slice = if let Some(len_arg) = args.get(2) {
let len = len_arg
.as_integer()
.ok_or("dtcs:substr length must be integer")?;
if len < 0 {
return Err("dtcs:substr length must be non-negative".into());
}
let end = (start + len as usize).min(chars.len());
&chars[start..end]
} else {
&chars[start..]
};
Ok(RuntimeValue::String(slice.iter().collect()))
}
other => Err(format!("dtcs:substr requires string, got {other:?}")),
}
}
"dtcs:replace" => {
if args.len() < 3 {
return Err("dtcs:replace requires three arguments".into());
}
let RuntimeValue::String(text) = &args[0] else {
return Err("dtcs:replace requires string haystack".into());
};
let RuntimeValue::String(from) = &args[1] else {
return Err("dtcs:replace requires string needle".into());
};
let RuntimeValue::String(to) = &args[2] else {
return Err("dtcs:replace requires string replacement".into());
};
Ok(RuntimeValue::String(text.replace(from, to)))
}
"dtcs:coalesce" => {
for arg in args {
if !arg.is_null() {
return Ok(arg.clone());
}
}
Ok(RuntimeValue::Null)
}
"dtcs:length" => {
let value = args.first().ok_or("dtcs:length requires one argument")?;
let len = match value {
RuntimeValue::Null => {
return Err("dtcs:length does not accept null".into());
}
RuntimeValue::String(s) => s.chars().count() as i64,
RuntimeValue::Binary(b) => b.len() as i64,
other => return Err(format!("dtcs:length unsupported type {other:?}")),
};
Ok(RuntimeValue::Integer(len))
}
"dtcs:to_string" => {
let value = args.first().ok_or("dtcs:to_string requires one argument")?;
Ok(RuntimeValue::String(match value {
RuntimeValue::Null => return Ok(RuntimeValue::Null),
RuntimeValue::String(s) => s.clone(),
RuntimeValue::Boolean(b) => b.to_string(),
RuntimeValue::Integer(i) => i.to_string(),
RuntimeValue::Decimal(d) => d.to_string(),
RuntimeValue::Binary(b) => b.clone(),
other => return Err(format!("dtcs:to_string unsupported type {other:?}")),
}))
}
"dtcs:to_integer" => {
let value = args
.first()
.ok_or("dtcs:to_integer requires one argument")?;
match value {
RuntimeValue::Null => Ok(RuntimeValue::Null),
RuntimeValue::Integer(i) => Ok(RuntimeValue::Integer(*i)),
RuntimeValue::Decimal(d) => {
if d.fract() != 0.0 {
return Err("dtcs:to_integer requires integer-valued decimal".into());
}
Ok(RuntimeValue::Integer(*d as i64))
}
RuntimeValue::String(s) => s
.parse::<i64>()
.map(RuntimeValue::Integer)
.map_err(|_| "dtcs:to_integer parse failed".to_string()),
other => Err(format!("dtcs:to_integer unsupported type {other:?}")),
}
}
"dtcs:to_decimal" => {
let value = args
.first()
.ok_or("dtcs:to_decimal requires one argument")?;
match value {
RuntimeValue::Null => Ok(RuntimeValue::Null),
RuntimeValue::Decimal(d) => Ok(RuntimeValue::Decimal(*d)),
RuntimeValue::Integer(i) => Ok(RuntimeValue::Decimal(*i as f64)),
RuntimeValue::String(s) => s
.parse::<f64>()
.map(RuntimeValue::Decimal)
.map_err(|_| "dtcs:to_decimal parse failed".to_string()),
other => Err(format!("dtcs:to_decimal unsupported type {other:?}")),
}
}
other => Err(format!("unsupported function '{other}'")),
}
}