use anyhow::{Context, Result};
use std::sync::Arc;
pub use packr::abi::{ConversionError, FromValue, Value, ValueType};
pub use packr::{
AsyncCtx, AsyncInstance, AsyncRuntime, CallInterceptor, Ctx, HostFunctionProvider,
HostLinkerBuilder, InterfaceBuilder, LinkerError,
};
pub use packr::{
compute_interface_hash, compute_interface_hashes, decode_metadata_with_hashes,
encode_metadata_with_hashes, hash_type, validate_value_in_type_space, FunctionSignature,
InterfaceHash, MetadataError, MetadataWithHashes, PackageMetadata, ParamSignature, TypeDesc,
TypeValidationError,
};
pub use packr::types::{Arena, Function, Param, Type, TypeDef};
pub use packr::{FuncSignature, InterfaceImpl, PackParams, PackType, TypeHash};
pub use packr::{parse_pact, PactInterface};
use std::collections::HashMap;
use crate::actor::store::ActorStore;
use crate::id::TheaterId;
#[derive(Debug, Clone)]
pub struct FunctionTypeInfo {
pub param_types: Vec<Type>,
pub result_types: Vec<Type>,
pub type_defs: Vec<TypeDef>,
}
fn extract_functions_from_arena(
arena: &PackageMetadata,
section: &str,
) -> Vec<(String, FunctionSignature)> {
let mut result = Vec::new();
for child in &arena.children {
if child.name == section {
for interface_arena in &child.children {
let interface_name = &interface_arena.name;
for func in &interface_arena.functions {
result.push((interface_name.clone(), func.clone()));
}
}
}
}
result
}
pub struct PackInstance {
pub name: String,
pub instance: AsyncInstance<ActorStore>,
pub actor_store: ActorStore,
function_types: HashMap<String, FunctionTypeInfo>,
}
impl PackInstance {
pub async fn new<F>(
name: impl Into<String>,
wasm_bytes: &[u8],
runtime: &AsyncRuntime,
actor_store: ActorStore,
configure: F,
) -> Result<Self>
where
F: FnOnce(&mut HostLinkerBuilder<'_, ActorStore>) -> Result<(), LinkerError>,
{
Self::new_with_interceptor(name, wasm_bytes, runtime, actor_store, None, configure).await
}
pub async fn new_with_interceptor<F>(
name: impl Into<String>,
wasm_bytes: &[u8],
runtime: &AsyncRuntime,
actor_store: ActorStore,
interceptor: Option<Arc<dyn CallInterceptor>>,
configure: F,
) -> Result<Self>
where
F: FnOnce(&mut HostLinkerBuilder<'_, ActorStore>) -> Result<(), LinkerError>,
{
let module = runtime
.load_module(wasm_bytes)
.context("Failed to load WASM module with Pack runtime")?;
let instance = module
.instantiate_with_host_and_interceptor_async(
actor_store.clone(),
interceptor,
configure,
)
.await
.context("Failed to instantiate Pack module")?;
Ok(Self {
name: name.into(),
instance,
actor_store,
function_types: HashMap::new(),
})
}
pub fn id(&self) -> TheaterId {
self.actor_store.id
}
pub async fn get_metadata(&mut self) -> Result<PackageMetadata, MetadataError> {
self.instance.types().await
}
pub async fn has_export(
&mut self,
interface: &str,
function: &str,
) -> Result<bool, MetadataError> {
let exports = self.get_exports().await?;
Ok(exports
.iter()
.any(|(iface, func)| iface == interface && func.name == function))
}
pub async fn get_exports(&mut self) -> Result<Vec<(String, FunctionSignature)>, MetadataError> {
let metadata = self.get_metadata().await?;
Ok(extract_functions_from_arena(&metadata, "exports"))
}
pub async fn get_imports(&mut self) -> Result<Vec<(String, FunctionSignature)>, MetadataError> {
let metadata = self.get_metadata().await?;
Ok(extract_functions_from_arena(&metadata, "imports"))
}
pub async fn get_metadata_with_hashes(&mut self) -> Result<MetadataWithHashes, MetadataError> {
self.instance.types_with_hashes().await
}
pub async fn get_import_hashes(&mut self) -> Result<Vec<InterfaceHash>, MetadataError> {
let metadata = self.get_metadata_with_hashes().await?;
Ok(metadata.import_hashes)
}
pub async fn get_export_hashes(&mut self) -> Result<Vec<InterfaceHash>, MetadataError> {
let metadata = self.get_metadata_with_hashes().await?;
Ok(metadata.export_hashes)
}
pub async fn cache_function_types(&mut self) -> Result<(), MetadataError> {
let metadata = self.get_metadata().await?;
let mut function_types = HashMap::new();
for child in &metadata.children {
if child.name == "exports" {
for interface_arena in &child.children {
let interface_types = &interface_arena.types;
for func in &interface_arena.functions {
let full_name = format!("{}.{}", interface_arena.name, func.name);
let mut all_types = interface_types.clone();
all_types.extend(func.types.clone());
function_types.insert(
full_name,
FunctionTypeInfo {
param_types: func.params.iter().map(|p| p.ty.clone()).collect(),
result_types: func.results.clone(),
type_defs: all_types,
},
);
}
}
}
}
self.function_types = function_types;
Ok(())
}
pub async fn call_function(
&mut self,
function_name: &str,
state: Value,
params: Vec<u8>,
) -> Result<(Value, Vec<u8>)> {
let params_value = bytes_to_value(¶ms);
self.call_function_with_value(function_name, state, params_value)
.await
}
pub async fn call_function_with_value(
&mut self,
function_name: &str,
state: Value,
params: Value,
) -> Result<(Value, Vec<u8>)> {
if !self.function_types.is_empty() {
let info = self.function_types.get(function_name).ok_or_else(|| {
anyhow::anyhow!(
"Function '{}' not found in cached type metadata",
function_name
)
})?;
if let Some(first_param) = info.param_types.first() {
validate_value_in_type_space(&state, first_param, &info.type_defs).map_err(
|e| anyhow::anyhow!("State type mismatch for '{}': {}", function_name, e),
)?;
}
}
let input = match params {
Value::Tuple(items) => {
let mut all = Vec::with_capacity(1 + items.len());
all.push(state);
all.extend(items);
Value::Tuple(all)
}
other => Value::Tuple(vec![state, other]),
};
let output = self
.instance
.call_with_value_async(function_name, &input)
.await
.with_context(|| {
format!(
"Failed to call function '{}' with input: {}",
function_name, input
)
})?;
if !self.function_types.is_empty() {
if let Some(info) = self.function_types.get(function_name) {
if let Some(result_type) = info.result_types.first() {
validate_value_in_type_space(&output, result_type, &info.type_defs).map_err(
|e| {
anyhow::anyhow!("Return type violation from '{}': {}", function_name, e)
},
)?;
}
}
}
decode_function_result(output)
}
pub async fn call_value(&mut self, function_name: &str, input: &Value) -> Result<Value> {
self.instance
.call_with_value_async(function_name, input)
.await
.context(format!("Failed to call function '{}'", function_name))
}
pub async fn call_typed<T: FromValue>(
&mut self,
function_name: &str,
state: Value,
params: Value,
) -> Result<ActorResult<T>> {
let (new_state, result_bytes) = self
.call_function_with_value(function_name, state, params)
.await?;
let result_value = if result_bytes.is_empty() {
Value::Tuple(vec![]) } else {
decode_value(&result_bytes)?
};
let value = T::from_value(result_value)
.map_err(|e| anyhow::anyhow!("Failed to decode result: {:?}", e))?;
Ok(ActorResult {
state: new_state,
value,
})
}
}
fn bytes_to_value(bytes: &[u8]) -> Value {
use packr::abi::ValueType;
Value::List {
elem_type: ValueType::U8,
items: bytes.iter().copied().map(Value::U8).collect(),
}
}
pub fn encode_value(value: &Value) -> Result<Vec<u8>> {
packr::encode(value).map_err(|e| anyhow::anyhow!("Failed to encode value: {:?}", e))
}
pub fn decode_value(bytes: &[u8]) -> Result<Value> {
packr::decode(bytes).map_err(|e| anyhow::anyhow!("Failed to decode value: {:?}", e))
}
fn decode_function_result(value: Value) -> Result<(Value, Vec<u8>)> {
match value {
Value::Result {
value: Ok(inner), ..
} => decode_ok_payload(*inner),
Value::Result {
value: Err(err), ..
} => {
let error_msg = match *err {
Value::String(s) => s,
other => format!("{:?}", other),
};
Err(anyhow::anyhow!("Function returned error: {}", error_msg))
}
Value::Variant {
tag: 0, payload, ..
} if !payload.is_empty() => decode_ok_payload(payload.into_iter().next().unwrap()),
Value::Variant {
tag: 1, payload, ..
} if !payload.is_empty() => {
let error_msg = match payload.into_iter().next().unwrap() {
Value::String(s) => s,
other => format!("{:?}", other),
};
Err(anyhow::anyhow!("Function returned error: {}", error_msg))
}
Value::Variant { tag: 1, .. } => {
Err(anyhow::anyhow!("Function returned error (no message)"))
}
Value::Variant { tag, .. } => {
Err(anyhow::anyhow!("Unexpected result variant tag: {}", tag))
}
other => Ok((other, vec![])),
}
}
fn decode_ok_payload(value: Value) -> Result<(Value, Vec<u8>)> {
match value {
Value::Tuple(mut items) if !items.is_empty() => {
let new_state = items.remove(0);
let result_value = if items.len() == 1 {
items.remove(0)
} else if items.is_empty() {
Value::Tuple(vec![])
} else {
Value::Tuple(items)
};
let result_bytes = encode_value(&result_value)?;
Ok((new_state, result_bytes))
}
other => Ok((other, vec![])),
}
}
#[derive(Debug, Clone)]
pub struct ActorResult<T> {
pub state: Value,
pub value: T,
}
impl<T: FromValue> FromValue for ActorResult<T> {
fn from_value(value: Value) -> std::result::Result<Self, ConversionError> {
match value {
Value::Result {
value: Ok(inner), ..
} => decode_actor_ok_payload(*inner),
Value::Result {
value: Err(err), ..
} => {
let error_msg = match *err {
Value::String(s) => s,
other => format!("{:?}", other),
};
Err(ConversionError::TypeMismatch {
expected: String::from("Ok result"),
got: format!("Actor error: {}", error_msg),
})
}
Value::Variant {
tag: 0, payload, ..
} if !payload.is_empty() => {
decode_actor_ok_payload(payload.into_iter().next().unwrap())
}
Value::Variant { tag: 0, .. } => Err(ConversionError::MissingPayload),
Value::Variant {
tag: 1, payload, ..
} => {
let error_msg = payload
.into_iter()
.next()
.map(|v| match v {
Value::String(s) => s,
other => format!("{:?}", other),
})
.unwrap_or_else(|| "Unknown error".to_string());
Err(ConversionError::TypeMismatch {
expected: String::from("Ok result"),
got: format!("Actor error: {}", error_msg),
})
}
other => Err(ConversionError::TypeMismatch {
expected: String::from("Result or Variant"),
got: format!("{:?}", other),
}),
}
}
}
fn decode_actor_ok_payload<T: FromValue>(
value: Value,
) -> std::result::Result<ActorResult<T>, ConversionError> {
match value {
Value::Tuple(mut items) if !items.is_empty() => {
let state = items.remove(0);
let return_value = if items.len() == 1 {
items.remove(0)
} else if items.is_empty() {
Value::Tuple(vec![])
} else {
Value::Tuple(items)
};
let value = T::from_value(return_value)?;
Ok(ActorResult { state, value })
}
other => Ok(ActorResult {
state: other,
value: T::from_value(Value::Tuple(vec![]))?,
}),
}
}
pub trait IntoValue {
fn into_value(self) -> Value;
}
impl IntoValue for () {
fn into_value(self) -> Value {
Value::Tuple(vec![])
}
}
impl<T: IntoValue, E: IntoValue> IntoValue for Result<T, E> {
fn into_value(self) -> Value {
match self {
Ok(v) => Value::Variant {
type_name: String::from("result"),
case_name: String::from("ok"),
tag: 0,
payload: vec![v.into_value()],
},
Err(e) => Value::Variant {
type_name: String::from("result"),
case_name: String::from("err"),
tag: 1,
payload: vec![e.into_value()],
},
}
}
}
impl IntoValue for String {
fn into_value(self) -> Value {
Value::String(self)
}
}
impl IntoValue for &str {
fn into_value(self) -> Value {
Value::String(self.to_string())
}
}
impl IntoValue for bool {
fn into_value(self) -> Value {
Value::Bool(self)
}
}
impl IntoValue for i64 {
fn into_value(self) -> Value {
Value::S64(self)
}
}
impl IntoValue for u64 {
fn into_value(self) -> Value {
Value::U64(self)
}
}
impl IntoValue for Vec<u8> {
fn into_value(self) -> Value {
use packr::abi::ValueType;
Value::List {
elem_type: ValueType::U8,
items: self.into_iter().map(Value::U8).collect(),
}
}
}
impl<T: IntoValue> IntoValue for Option<T> {
fn into_value(self) -> Value {
let (inner_type, value) = match self {
Some(v) => {
let val = v.into_value();
let ty = val.infer_type();
(ty, Some(Box::new(val)))
}
None => {
use packr::abi::ValueType;
(ValueType::Bool, None)
}
};
Value::Option { inner_type, value }
}
}
impl IntoValue for Value {
fn into_value(self) -> Value {
self
}
}
impl<T: IntoValue> IntoValue for Vec<T> {
fn into_value(self) -> Value {
let items: Vec<Value> = self.into_iter().map(|v| v.into_value()).collect();
let elem_type = items.first().map(|v| v.infer_type()).unwrap_or_else(|| {
use packr::abi::ValueType;
ValueType::Bool });
Value::List { elem_type, items }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_into_value_result() {
let ok: Result<String, String> = Ok("success".to_string());
let value = ok.into_value();
match value {
Value::Variant {
tag: 0,
ref payload,
..
} if !payload.is_empty() => match &payload[0] {
Value::String(s) => assert_eq!(s, "success"),
_ => panic!("Expected String"),
},
_ => panic!("Expected Ok variant"),
}
}
}