use std::collections::BTreeMap;
use std::sync::Arc;
use harn_vm::VmValue;
use crate::error::HostlibError;
use crate::value_args;
pub fn dict_arg(
builtin: &'static str,
args: &[VmValue],
) -> Result<Arc<BTreeMap<String, VmValue>>, HostlibError> {
match args.first() {
Some(VmValue::Dict(dict)) => Ok(dict.clone()),
Some(VmValue::Nil) | None => Ok(Arc::new(BTreeMap::new())),
Some(other) => Err(HostlibError::InvalidParameter {
builtin,
param: "params",
message: format!(
"expected a dict argument, got {} ({:?})",
other.type_name(),
other
),
}),
}
}
pub fn require_string(
builtin: &'static str,
dict: &BTreeMap<String, VmValue>,
key: &'static str,
) -> Result<String, HostlibError> {
value_args::require_string(builtin, dict, key)
}
pub fn optional_string(
builtin: &'static str,
dict: &BTreeMap<String, VmValue>,
key: &'static str,
) -> Result<Option<String>, HostlibError> {
value_args::optional_string(builtin, dict, key)
}
pub fn optional_bool(
builtin: &'static str,
dict: &BTreeMap<String, VmValue>,
key: &'static str,
default: bool,
) -> Result<bool, HostlibError> {
value_args::optional_bool(builtin, dict, key).map(|value| value.unwrap_or(default))
}
pub fn optional_int(
builtin: &'static str,
dict: &BTreeMap<String, VmValue>,
key: &'static str,
default: i64,
) -> Result<i64, HostlibError> {
value_args::optional_i64(builtin, dict, key, default)
}
pub fn optional_string_list(
builtin: &'static str,
dict: &BTreeMap<String, VmValue>,
key: &'static str,
) -> Result<Vec<String>, HostlibError> {
value_args::optional_string_list(builtin, dict, key).map(|value| value.unwrap_or_default())
}
#[cfg(feature = "ast")]
pub fn optional_int_list(
builtin: &'static str,
dict: &BTreeMap<String, VmValue>,
key: &'static str,
) -> Result<Vec<i64>, HostlibError> {
match dict.get(key) {
None | Some(VmValue::Nil) => Ok(Vec::new()),
Some(VmValue::List(items)) => {
let mut out = Vec::with_capacity(items.len());
for item in items.iter() {
out.push(value_args::coerce_i64(builtin, key, item)?);
}
Ok(out)
}
Some(other) => Err(HostlibError::InvalidParameter {
builtin,
param: key,
message: format!(
"expected list of integers, got {}",
value_args::describe(other)
),
}),
}
}
pub fn build_dict<I, K>(entries: I) -> VmValue
where
I: IntoIterator<Item = (K, VmValue)>,
K: Into<String>,
{
let mut map: BTreeMap<String, VmValue> = BTreeMap::new();
for (k, v) in entries {
map.insert(k.into(), v);
}
VmValue::Dict(Arc::new(map))
}
pub fn str_value(s: impl AsRef<str>) -> VmValue {
VmValue::String(Arc::from(s.as_ref()))
}
#[cfg(test)]
mod tests {
use super::*;
fn dict(entries: [(&'static str, VmValue); 1]) -> BTreeMap<String, VmValue> {
entries
.into_iter()
.map(|(key, value)| (key.to_string(), value))
.collect()
}
#[test]
fn optional_int_rejects_non_finite_float() {
let payload = dict([("limit", VmValue::Float(f64::INFINITY))]);
assert!(matches!(
optional_int("test", &payload, "limit", 0),
Err(HostlibError::InvalidParameter { param: "limit", .. })
));
}
#[test]
fn optional_int_rejects_out_of_range_float() {
let payload = dict([("limit", VmValue::Float(1.0e100))]);
assert!(matches!(
optional_int("test", &payload, "limit", 0),
Err(HostlibError::InvalidParameter { param: "limit", .. })
));
}
}