runa/crates/runa-rune/src/value.rs

184 lines
6.7 KiB
Rust

use runa_core::{ActorRef, MAX_VALUE_BYTES, MAX_VALUE_DEPTH, MAX_VALUE_NODES, Value};
use rune::runtime::{Object, Vec as RuneVec};
use rune::{Any, TypeHash, Value as RuneValue};
use crate::ExperimentResult;
#[derive(Any, Clone)]
#[rune(item = ::runa)]
pub(crate) struct ActorReference(pub ActorRef);
/// Allocate a fresh Rune object graph for each invocation input.
pub fn value_to_rune(value: &Value) -> ExperimentResult<RuneValue> {
value.validate()?;
fn convert(value: &Value) -> ExperimentResult<RuneValue> {
Ok(match value {
Value::Null => RuneValue::from(()),
Value::Bool(value) => rune::to_value(*value)?,
Value::Integer(value) => rune::to_value(*value)?,
Value::String(value) => rune::to_value(value.as_str())?,
Value::ActorRef(value) => RuneValue::new(ActorReference(*value))?,
Value::List(values) => {
let mut result = RuneVec::with_capacity(values.len())?;
for value in values {
result.push(convert(value)?)?;
}
RuneValue::new(result)?
}
Value::Record(values) => {
let mut result = Object::with_capacity(values.len())?;
for (key, value) in values {
result.insert(
rune::alloc::String::try_from(key.as_str())?,
convert(value)?,
)?;
}
RuneValue::new(result)?
}
})
}
convert(value)
}
/// Copy only portable data out of Rune; cycles and unsupported objects fail.
pub fn value_from_rune(value: &RuneValue) -> ExperimentResult<Value> {
fn convert(
value: &RuneValue,
depth: usize,
nodes: &mut usize,
bytes: &mut usize,
) -> ExperimentResult<Value> {
*nodes += 1;
if depth > MAX_VALUE_DEPTH || *nodes > MAX_VALUE_NODES {
return Err("Rune actor value exceeds depth or node limit (possibly cyclic)".into());
}
let hash = value.type_hash();
let result = if value.into_unit().is_ok() {
Value::Null
} else if hash == bool::HASH {
Value::Bool(value.as_bool()?)
} else if hash == i64::HASH {
Value::Integer(value.as_integer()?)
} else if hash == rune::alloc::String::HASH {
let value = value.borrow_string_ref()?;
*bytes += value.len();
if *bytes > MAX_VALUE_BYTES {
return Err("Rune actor value exceeds string byte limit".into());
}
Value::String(value.to_string())
} else if hash == ActorReference::HASH {
Value::ActorRef(value.borrow_ref::<ActorReference>()?.0)
} else if hash == RuneVec::HASH {
let values = value.borrow_ref::<RuneVec>()?;
let mut result = Vec::new();
for value in values.iter() {
result.push(convert(value, depth + 1, nodes, bytes)?);
}
Value::List(result)
} else if hash == Object::HASH {
let values = value.borrow_ref::<Object>()?;
let mut result = std::collections::BTreeMap::new();
for (key, value) in values.iter() {
*bytes += key.len();
if *bytes > MAX_VALUE_BYTES {
return Err("Rune actor value exceeds string byte limit".into());
}
result.insert(key.to_string(), convert(value, depth + 1, nodes, bytes)?);
}
Value::Record(result)
} else {
return Err(format!("unsupported Rune actor value: {}", value.type_info()).into());
};
Ok(result)
}
convert(value, 0, &mut 0, &mut 0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CompiledProgram;
use std::collections::BTreeMap;
#[test]
fn portable_values_round_trip_without_turning_strings_into_references() -> ExperimentResult<()>
{
let actor = ActorRef::new();
let value = Value::Record(BTreeMap::from([(
"values".to_owned(),
Value::List(vec![
Value::Null,
Value::Bool(true),
Value::Integer(i64::MIN),
Value::Integer(i64::MAX),
Value::String("hello world".to_owned()),
Value::ActorRef(actor),
Value::String(actor.to_string()),
Value::List(vec![]),
Value::Record(BTreeMap::new()),
]),
)]));
assert_eq!(value_from_rune(&value_to_rune(&value)?)?, value);
Ok(())
}
#[test]
fn rune_mutations_do_not_alias_host_values_or_other_invocation_inputs() -> ExperimentResult<()>
{
let original = Value::Record(BTreeMap::from([(
"items".to_owned(),
Value::List(vec![Value::Integer(1)]),
)]));
let program =
CompiledProgram::compile("pub fn change(state) { state.items.push(2); state }")?;
let first = value_to_rune(&original)?;
let second = value_to_rune(&original)?;
let result = program.vm().call(["change"], (first,))?;
assert_eq!(value_from_rune(&second)?, original);
assert_ne!(value_from_rune(&result)?, original);
Ok(())
}
#[test]
fn references_can_be_compared_cloned_and_retained_in_rune_records() -> ExperimentResult<()> {
let actor = ActorRef::new();
let other = ActorRef::new();
let program = CompiledProgram::compile(
"pub fn check(left, right) { #{same: left == right, reference: left.clone()} }",
)?;
for (right, expected) in [(actor, true), (other, false)] {
let result = program.vm().call(
["check"],
(
value_to_rune(&Value::ActorRef(actor))?,
value_to_rune(&Value::ActorRef(right))?,
),
)?;
assert_eq!(
value_from_rune(&result)?,
Value::Record(BTreeMap::from([
("same".to_owned(), Value::Bool(expected)),
("reference".to_owned(), Value::ActorRef(actor)),
]))
);
}
Ok(())
}
#[test]
fn cyclic_and_unsupported_rune_values_are_explicit_errors() -> ExperimentResult<()> {
for source in [
"pub fn make() { 1.5 }",
"pub fn make() { (1, 2) }",
"pub fn make() { Some(1) }",
"pub fn make() { let x = []; x.push(x); x }",
] {
let program = CompiledProgram::compile(source)?;
let value = program.vm().call(["make"], ())?;
assert!(value_from_rune(&value).is_err(), "{source}");
}
Ok(())
}
}