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<harn_vm::value::DictMap>, HostlibError> {
match args.first() {
Some(VmValue::Dict(dict)) => Ok(dict.clone()),
Some(VmValue::Nil) | None => Ok(Arc::new(harn_vm::value::DictMap::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: &harn_vm::value::DictMap,
key: &'static str,
) -> Result<String, HostlibError> {
value_args::require_string(builtin, dict, key)
}
pub fn optional_string(
builtin: &'static str,
dict: &harn_vm::value::DictMap,
key: &'static str,
) -> Result<Option<String>, HostlibError> {
value_args::optional_string(builtin, dict, key)
}
pub fn optional_bool(
builtin: &'static str,
dict: &harn_vm::value::DictMap,
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: &harn_vm::value::DictMap,
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: &harn_vm::value::DictMap,
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: &harn_vm::value::DictMap,
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: harn_vm::value::DictMap = harn_vm::value::DictMap::new();
for (k, v) in entries {
map.insert(harn_vm::value::intern_key(&k.into()), v);
}
VmValue::dict(map)
}
pub fn str_value(s: impl AsRef<str>) -> VmValue {
VmValue::string(s)
}
pub fn to_agent_path(path: impl AsRef<std::path::Path>) -> String {
to_agent_path_str(path.as_ref().to_string_lossy())
}
pub fn to_agent_path_str(path: impl AsRef<str>) -> String {
path.as_ref().replace('\\', "/")
}
#[cfg(test)]
mod tests {
use super::*;
fn dict(entries: [(&'static str, VmValue); 1]) -> harn_vm::value::DictMap {
entries
.into_iter()
.map(|(key, value)| (harn_vm::value::intern_key(key), 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", .. })
));
}
#[test]
fn to_agent_path_str_rewrites_backslashes() {
assert_eq!(
to_agent_path_str("crates\\burin-tui\\src\\lib.rs"),
"crates/burin-tui/src/lib.rs"
);
}
#[test]
fn to_agent_path_str_leaves_forward_slashes_untouched() {
assert_eq!(
to_agent_path_str("crates/burin-tui/src/lib.rs"),
"crates/burin-tui/src/lib.rs"
);
}
#[test]
fn to_agent_path_never_emits_backslashes() {
let joined: std::path::PathBuf = ["crates", "burin-tui", "src", "lib.rs"].iter().collect();
let emitted = to_agent_path(&joined);
assert!(
!emitted.contains('\\'),
"agent path must not contain backslashes, got {emitted:?}"
);
assert!(emitted.ends_with("crates/burin-tui/src/lib.rs"));
}
}