use crate::abi::{decode, encode, Value};
use crate::types::{Arena, Case, Field, Function, Param, Type, TypeDef, TypePath};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeHash([u8; 32]);
impl TypeHash {
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_u64s(a: u64, b: u64, c: u64, d: u64) -> Self {
let mut bytes = [0u8; 32];
bytes[0..8].copy_from_slice(&a.to_le_bytes());
bytes[8..16].copy_from_slice(&b.to_le_bytes());
bytes[16..24].copy_from_slice(&c.to_le_bytes());
bytes[24..32].copy_from_slice(&d.to_le_bytes());
Self(bytes)
}
pub fn to_u64s(&self) -> (u64, u64, u64, u64) {
let b = &self.0;
(
u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]),
u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]),
u64::from_le_bytes([b[16], b[17], b[18], b[19], b[20], b[21], b[22], b[23]]),
u64::from_le_bytes([b[24], b[25], b[26], b[27], b[28], b[29], b[30], b[31]]),
)
}
pub fn to_hex(&self) -> String {
use core::fmt::Write;
let mut s = String::with_capacity(self.0.len() * 2);
for b in &self.0 {
let _ = write!(s, "{:02x}", b);
}
s
}
pub fn to_short_hex(&self) -> String {
use core::fmt::Write;
let mut s = String::with_capacity(8);
for b in self.0.iter().take(4) {
let _ = write!(s, "{:02x}", b);
}
s
}
pub const fn from_bytes_const(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
pub const HASH_SELF_REF: TypeHash = TypeHash::from_bytes_const([
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
impl std::fmt::Display for TypeHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_short_hex())
}
}
pub const HASH_BOOL: TypeHash = TypeHash([
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U8: TypeHash = TypeHash([
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U16: TypeHash = TypeHash([
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U32: TypeHash = TypeHash([
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U64: TypeHash = TypeHash([
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S8: TypeHash = TypeHash([
0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S16: TypeHash = TypeHash([
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S32: TypeHash = TypeHash([
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S64: TypeHash = TypeHash([
0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_F32: TypeHash = TypeHash([
0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_F64: TypeHash = TypeHash([
0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_CHAR: TypeHash = TypeHash([
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_STRING: TypeHash = TypeHash([
0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_FLAGS: TypeHash = TypeHash([
0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
use sha2::{Digest, Sha256};
const HASH_TAG_LIST: u8 = 0x10;
const HASH_TAG_OPTION: u8 = 0x11;
const HASH_TAG_RESULT: u8 = 0x12;
const HASH_TAG_TUPLE: u8 = 0x13;
const HASH_TAG_RECORD: u8 = 0x14;
const HASH_TAG_VARIANT: u8 = 0x15;
const HASH_TAG_FUNCTION: u8 = 0x16;
const HASH_TAG_INTERFACE: u8 = 0x17;
struct TypeHasher {
hasher: Sha256,
}
impl TypeHasher {
fn new() -> Self {
Self {
hasher: Sha256::new(),
}
}
fn tag(mut self, tag: u8) -> Self {
self.hasher.update([tag]);
self
}
fn string(mut self, s: &str) -> Self {
self.hasher.update((s.len() as u32).to_le_bytes());
self.hasher.update(s.as_bytes());
self
}
fn child(mut self, hash: &TypeHash) -> Self {
self.hasher.update(hash.as_bytes());
self
}
fn count(mut self, n: usize) -> Self {
self.hasher.update((n as u32).to_le_bytes());
self
}
fn finish(self) -> TypeHash {
let result = self.hasher.finalize();
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&result);
TypeHash(bytes)
}
}
pub fn hash_list(element: &TypeHash) -> TypeHash {
TypeHasher::new().tag(HASH_TAG_LIST).child(element).finish()
}
pub fn hash_option(inner: &TypeHash) -> TypeHash {
TypeHasher::new().tag(HASH_TAG_OPTION).child(inner).finish()
}
pub fn hash_result(ok: &TypeHash, err: &TypeHash) -> TypeHash {
TypeHasher::new()
.tag(HASH_TAG_RESULT)
.child(ok)
.child(err)
.finish()
}
pub fn hash_tuple(elements: &[TypeHash]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(HASH_TAG_TUPLE).count(elements.len());
for elem in elements {
hasher = hasher.child(elem);
}
hasher.finish()
}
pub fn hash_record(fields: &[(&str, TypeHash)]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(HASH_TAG_RECORD).count(fields.len());
for (name, type_hash) in fields {
hasher = hasher.string(name).child(type_hash);
}
hasher.finish()
}
pub fn hash_variant(cases: &[(&str, Option<TypeHash>)]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(HASH_TAG_VARIANT).count(cases.len());
for (name, payload) in cases {
hasher = hasher.string(name);
if let Some(type_hash) = payload {
hasher = hasher.tag(1).child(type_hash);
} else {
hasher = hasher.tag(0);
}
}
hasher.finish()
}
pub fn hash_function(params: &[TypeHash], results: &[TypeHash]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(HASH_TAG_FUNCTION).count(params.len());
for param in params {
hasher = hasher.child(param);
}
hasher = hasher.count(results.len());
for result in results {
hasher = hasher.child(result);
}
hasher.finish()
}
pub struct Binding<'a> {
pub name: &'a str,
pub hash: TypeHash,
}
pub fn hash_interface(
name: &str,
type_bindings: &[Binding<'_>],
func_bindings: &[Binding<'_>],
) -> TypeHash {
let mut hasher = TypeHasher::new()
.tag(HASH_TAG_INTERFACE)
.string(name)
.count(type_bindings.len());
for binding in type_bindings {
hasher = hasher.string(binding.name).child(&binding.hash);
}
hasher = hasher.count(func_bindings.len());
for binding in func_bindings {
hasher = hasher.string(binding.name).child(&binding.hash);
}
hasher.finish()
}
#[derive(Debug, Clone)]
pub struct InterfaceHash {
pub name: String,
pub hash: TypeHash,
}
pub fn hash_type(ty: &Type) -> TypeHash {
hash_type_in(ty, &[])
}
pub fn hash_type_in(ty: &Type, types: &[TypeDef]) -> TypeHash {
hash_type_inner(ty, types, &mut Vec::new())
}
fn hash_type_inner(ty: &Type, types: &[TypeDef], stack: &mut Vec<String>) -> TypeHash {
match ty {
Type::Unit => hash_tuple(&[]), Type::Bool => HASH_BOOL,
Type::U8 => HASH_U8,
Type::U16 => HASH_U16,
Type::U32 => HASH_U32,
Type::U64 => HASH_U64,
Type::S8 => HASH_S8,
Type::S16 => HASH_S16,
Type::S32 => HASH_S32,
Type::S64 => HASH_S64,
Type::F32 => HASH_F32,
Type::F64 => HASH_F64,
Type::Char => HASH_CHAR,
Type::String => HASH_STRING,
Type::List(inner) => hash_list(&hash_type_inner(inner, types, stack)),
Type::Option(inner) => hash_option(&hash_type_inner(inner, types, stack)),
Type::Result { ok, err } => hash_result(
&hash_type_inner(ok, types, stack),
&hash_type_inner(err, types, stack),
),
Type::Tuple(elems) => {
let hashes: Vec<_> = elems
.iter()
.map(|t| hash_type_inner(t, types, stack))
.collect();
hash_tuple(&hashes)
}
Type::Ref(path) => hash_ref(path, types, stack),
Type::Value => HASH_SELF_REF,
}
}
fn hash_ref(path: &TypePath, types: &[TypeDef], stack: &mut Vec<String>) -> TypeHash {
if path.is_self_ref() {
return HASH_SELF_REF;
}
if let Some(name) = path.as_simple() {
if stack.iter().any(|s| s == name) {
return HASH_SELF_REF;
}
if let Some(td) = types.iter().find(|t| t.name() == name) {
stack.push(name.to_string());
let h = hash_typedef_inner(td, types, stack);
stack.pop();
return h;
}
}
let path_str = path.to_string();
let mut hasher = sha2::Sha256::new();
hasher.update(b"ref:");
hasher.update(path_str.as_bytes());
TypeHash::from_bytes(hasher.finalize().into())
}
fn hash_typedef_inner(td: &TypeDef, types: &[TypeDef], stack: &mut Vec<String>) -> TypeHash {
match td {
TypeDef::Alias { ty, .. } => hash_type_inner(ty, types, stack),
TypeDef::Record { fields, .. } => {
let pairs: Vec<(String, TypeHash)> = fields
.iter()
.map(|f| (f.name.clone(), hash_type_inner(&f.ty, types, stack)))
.collect();
let mut sorted: Vec<_> = pairs.iter().map(|(n, h)| (n.as_str(), *h)).collect();
sorted.sort_by(|a, b| a.0.cmp(b.0));
hash_record(&sorted)
}
TypeDef::Variant { cases, .. } => {
let pairs: Vec<(String, Option<TypeHash>)> = cases
.iter()
.map(|c| {
let payload = if c.payload.is_unit() {
None
} else {
Some(hash_type_inner(&c.payload, types, stack))
};
(c.name.clone(), payload)
})
.collect();
let mut sorted: Vec<_> = pairs.iter().map(|(n, h)| (n.as_str(), *h)).collect();
sorted.sort_by(|a, b| a.0.cmp(b.0));
hash_variant(&sorted)
}
TypeDef::Enum { cases, .. } => {
let mut sorted: Vec<(&str, Option<TypeHash>)> =
cases.iter().map(|c| (c.as_str(), None)).collect();
sorted.sort_by(|a, b| a.0.cmp(b.0));
hash_variant(&sorted)
}
TypeDef::Flags { .. } => HASH_FLAGS,
}
}
pub fn hash_function_from_sig(func: &Function) -> TypeHash {
hash_function_from_sig_in(func, &[])
}
pub fn hash_function_from_sig_in(func: &Function, types: &[TypeDef]) -> TypeHash {
let param_hashes: Vec<_> = func
.params
.iter()
.map(|p| hash_type_in(&p.ty, types))
.collect();
let result_hashes: Vec<_> = func
.results
.iter()
.map(|t| hash_type_in(t, types))
.collect();
hash_function(¶m_hashes, &result_hashes)
}
pub fn compute_interface_hash(interface_arena: &Arena) -> TypeHash {
let types: &[TypeDef] = &interface_arena.types;
let mut bindings: Vec<_> = interface_arena
.functions
.iter()
.map(|f| Binding {
name: &f.name,
hash: hash_function_from_sig_in(f, types),
})
.collect();
bindings.sort_by(|a, b| a.name.cmp(b.name));
hash_interface(
&interface_arena.name,
&[], &bindings,
)
}
pub fn compute_interface_hashes(arena: &Arena, section: &str) -> Vec<InterfaceHash> {
let mut result = Vec::new();
for child in &arena.children {
if child.name == section {
for interface_arena in &child.children {
result.push(InterfaceHash {
name: interface_arena.name.clone(),
hash: compute_interface_hash(interface_arena),
});
}
}
}
result
}
#[derive(Debug, Clone)]
pub struct MetadataWithHashes {
pub arena: Arena,
pub import_hashes: Vec<InterfaceHash>,
pub export_hashes: Vec<InterfaceHash>,
}
#[derive(Debug, thiserror::Error)]
pub enum MetadataError {
#[error("package does not export __pack_types")]
NotFound,
#[error("metadata call failed: {0}")]
CallFailed(String),
#[error("failed to decode metadata: {0}")]
DecodeFailed(String),
#[error("invalid metadata structure: {0}")]
InvalidStructure(String),
#[error("failed to encode metadata: {0}")]
EncodeFailed(String),
}
const TAG_BOOL: u32 = 0;
const TAG_U8: u32 = 1;
const TAG_U16: u32 = 2;
const TAG_U32: u32 = 3;
const TAG_U64: u32 = 4;
const TAG_S8: u32 = 5;
const TAG_S16: u32 = 6;
const TAG_S32: u32 = 7;
const TAG_S64: u32 = 8;
const TAG_F32: u32 = 9;
const TAG_F64: u32 = 10;
const TAG_CHAR: u32 = 11;
const TAG_STRING: u32 = 12;
const TAG_FLAGS: u32 = 13;
const TAG_LIST: u32 = 14;
const TAG_OPTION: u32 = 15;
const TAG_RESULT: u32 = 16;
const TAG_RECORD: u32 = 17;
const TAG_VARIANT: u32 = 18;
const TAG_TUPLE: u32 = 19;
const TAG_VALUE: u32 = 20;
const TAG_UNIT: u32 = 21;
pub fn decode_metadata(bytes: &[u8]) -> Result<Arena, MetadataError> {
let value = decode(bytes).map_err(|e| MetadataError::DecodeFailed(format!("{:?}", e)))?;
match value {
Value::Record { fields, .. } => {
let mut imports = Vec::new();
let mut exports = Vec::new();
let mut type_defs = Vec::new();
for (name, val) in fields {
match name.as_str() {
"imports" => imports = decode_func_sig_list(val, &mut type_defs)?,
"exports" => exports = decode_func_sig_list(val, &mut type_defs)?,
_ => {}
}
}
let mut arena = Arena::new("package");
if !imports.is_empty() {
let mut import_arena = Arena::new("imports");
let mut by_interface: std::collections::HashMap<String, Vec<Function>> =
std::collections::HashMap::new();
for (interface, func) in imports {
by_interface.entry(interface).or_default().push(func);
}
for (interface_name, funcs) in by_interface {
let mut interface_arena = Arena::new(interface_name);
for func in funcs {
interface_arena.add_function(func);
}
import_arena.add_child(interface_arena);
}
arena.add_child(import_arena);
}
if !exports.is_empty() {
let mut export_arena = Arena::new("exports");
let mut by_interface: std::collections::HashMap<String, Vec<Function>> =
std::collections::HashMap::new();
for (interface, func) in exports {
by_interface.entry(interface).or_default().push(func);
}
for (interface_name, funcs) in by_interface {
let mut interface_arena = Arena::new(interface_name);
for func in funcs {
interface_arena.add_function(func);
}
export_arena.add_child(interface_arena);
}
arena.add_child(export_arena);
}
Ok(arena)
}
_ => Err(MetadataError::InvalidStructure(
"expected record at top level".into(),
)),
}
}
pub fn decode_metadata_with_hashes(bytes: &[u8]) -> Result<MetadataWithHashes, MetadataError> {
let value = decode(bytes).map_err(|e| MetadataError::DecodeFailed(format!("{:?}", e)))?;
match value {
Value::Record { fields, .. } => {
let mut imports = Vec::new();
let mut exports = Vec::new();
let mut import_hashes = Vec::new();
let mut export_hashes = Vec::new();
let mut type_defs = Vec::new();
for (name, val) in fields {
match name.as_str() {
"imports" => imports = decode_func_sig_list(val, &mut type_defs)?,
"exports" => exports = decode_func_sig_list(val, &mut type_defs)?,
"import-hashes" => import_hashes = decode_interface_hash_list(val)?,
"export-hashes" => export_hashes = decode_interface_hash_list(val)?,
_ => {}
}
}
let mut arena = Arena::new("package");
if !imports.is_empty() {
let mut import_arena = Arena::new("imports");
let mut by_interface: std::collections::HashMap<String, Vec<Function>> =
std::collections::HashMap::new();
for (interface, func) in imports {
by_interface.entry(interface).or_default().push(func);
}
for (interface_name, funcs) in by_interface {
let mut interface_arena = Arena::new(interface_name);
for func in funcs {
interface_arena.add_function(func);
}
import_arena.add_child(interface_arena);
}
arena.add_child(import_arena);
}
if !exports.is_empty() {
let mut export_arena = Arena::new("exports");
let mut by_interface: std::collections::HashMap<String, Vec<Function>> =
std::collections::HashMap::new();
for (interface, func) in exports {
by_interface.entry(interface).or_default().push(func);
}
for (interface_name, funcs) in by_interface {
let mut interface_arena = Arena::new(interface_name);
for func in funcs {
interface_arena.add_function(func);
}
export_arena.add_child(interface_arena);
}
arena.add_child(export_arena);
}
Ok(MetadataWithHashes {
arena,
import_hashes,
export_hashes,
})
}
_ => Err(MetadataError::InvalidStructure(
"expected record at top level".into(),
)),
}
}
fn decode_interface_hash_list(value: Value) -> Result<Vec<InterfaceHash>, MetadataError> {
match value {
Value::List { items, .. } => items.into_iter().map(decode_interface_hash).collect(),
_ => Err(MetadataError::InvalidStructure(
"expected list of interface hashes".into(),
)),
}
}
fn decode_interface_hash(value: Value) -> Result<InterfaceHash, MetadataError> {
match value {
Value::Record { fields, .. } => {
let mut name = String::new();
let mut hash = TypeHash::from_bytes([0u8; 32]);
for (field_name, val) in fields {
match field_name.as_str() {
"name" => {
if let Value::String(s) = val {
name = s;
}
}
"hash" => {
match val {
Value::List { items, .. } => {
let bytes: Vec<u8> = items
.into_iter()
.filter_map(|v| match v {
Value::U8(b) => Some(b),
_ => None,
})
.collect();
if bytes.len() == 32 {
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
hash = TypeHash::from_bytes(arr);
}
}
Value::Tuple(parts) => {
if parts.len() == 4 {
let a = match &parts[0] {
Value::U64(v) => *v,
_ => 0,
};
let b = match &parts[1] {
Value::U64(v) => *v,
_ => 0,
};
let c = match &parts[2] {
Value::U64(v) => *v,
_ => 0,
};
let d = match &parts[3] {
Value::U64(v) => *v,
_ => 0,
};
hash = TypeHash::from_u64s(a, b, c, d);
}
}
_ => {}
}
}
_ => {}
}
}
Ok(InterfaceHash { name, hash })
}
_ => Err(MetadataError::InvalidStructure(
"expected record for interface hash".into(),
)),
}
}
fn decode_func_sig_list(
value: Value,
type_defs: &mut Vec<TypeDef>,
) -> Result<Vec<(String, Function)>, MetadataError> {
match value {
Value::List { items, .. } => items
.into_iter()
.map(|v| decode_func_sig(v, type_defs))
.collect(),
_ => Err(MetadataError::InvalidStructure(
"expected list of function signatures".into(),
)),
}
}
fn decode_func_sig(
value: Value,
type_defs: &mut Vec<TypeDef>,
) -> Result<(String, Function), MetadataError> {
match value {
Value::Record { fields, .. } => {
let mut interface = String::new();
let mut name = String::new();
let mut params = Vec::new();
let mut results = Vec::new();
for (field_name, val) in fields {
match field_name.as_str() {
"interface" => {
if let Value::String(s) = val {
interface = s;
}
}
"name" => {
if let Value::String(s) = val {
name = s;
}
}
"params" => {
params = decode_param_list(val, type_defs)?;
}
"results" => {
results = decode_type_list(val, type_defs)?;
}
_ => {}
}
}
let mut func = Function::with_signature(name, params, results);
func.types = type_defs.clone();
Ok((interface, func))
}
_ => Err(MetadataError::InvalidStructure(
"expected record for function signature".into(),
)),
}
}
fn decode_param_list(
value: Value,
type_defs: &mut Vec<TypeDef>,
) -> Result<Vec<Param>, MetadataError> {
match value {
Value::List { items, .. } => items
.into_iter()
.map(|v| decode_param(v, type_defs))
.collect(),
_ => Err(MetadataError::InvalidStructure(
"expected list of parameters".into(),
)),
}
}
fn decode_param(value: Value, type_defs: &mut Vec<TypeDef>) -> Result<Param, MetadataError> {
match value {
Value::Record { fields, .. } => {
let mut name = String::new();
let mut ty = Type::Value;
for (field_name, val) in fields {
match field_name.as_str() {
"name" => {
if let Value::String(s) = val {
name = s;
}
}
"type" => {
ty = decode_type_collecting(val, type_defs)?;
}
_ => {}
}
}
Ok(Param::new(name, ty))
}
_ => Err(MetadataError::InvalidStructure(
"expected record for parameter".into(),
)),
}
}
fn decode_type_list(
value: Value,
type_defs: &mut Vec<TypeDef>,
) -> Result<Vec<Type>, MetadataError> {
match value {
Value::List { items, .. } => items
.into_iter()
.map(|v| decode_type_collecting(v, type_defs))
.collect(),
_ => Err(MetadataError::InvalidStructure(
"expected list of types".into(),
)),
}
}
fn decode_type_collecting(
value: Value,
type_defs: &mut Vec<TypeDef>,
) -> Result<Type, MetadataError> {
match value {
Value::Variant { tag, payload, .. } => {
let tag = tag as u32;
match tag {
TAG_BOOL => Ok(Type::Bool),
TAG_U8 => Ok(Type::U8),
TAG_U16 => Ok(Type::U16),
TAG_U32 => Ok(Type::U32),
TAG_U64 => Ok(Type::U64),
TAG_S8 => Ok(Type::S8),
TAG_S16 => Ok(Type::S16),
TAG_S32 => Ok(Type::S32),
TAG_S64 => Ok(Type::S64),
TAG_F32 => Ok(Type::F32),
TAG_F64 => Ok(Type::F64),
TAG_CHAR => Ok(Type::Char),
TAG_STRING => Ok(Type::String),
TAG_FLAGS => Ok(Type::Ref(TypePath::simple("flags"))),
TAG_LIST => {
let inner = payload.into_iter().next().ok_or_else(|| {
MetadataError::InvalidStructure("list missing element type".into())
})?;
Ok(Type::list(decode_type_collecting(inner, type_defs)?))
}
TAG_OPTION => {
let inner = payload.into_iter().next().ok_or_else(|| {
MetadataError::InvalidStructure("option missing inner type".into())
})?;
Ok(Type::option(decode_type_collecting(inner, type_defs)?))
}
TAG_RESULT => {
let record = payload.into_iter().next().ok_or_else(|| {
MetadataError::InvalidStructure("result missing payload".into())
})?;
match record {
Value::Record { fields, .. } => {
let mut ok = Type::Unit;
let mut err = Type::Unit;
for (name, val) in fields {
match name.as_str() {
"ok" => ok = decode_type_collecting(val, type_defs)?,
"err" => err = decode_type_collecting(val, type_defs)?,
_ => {}
}
}
Ok(Type::result(ok, err))
}
_ => Err(MetadataError::InvalidStructure(
"result payload not a record".into(),
)),
}
}
TAG_RECORD => {
let record = payload.into_iter().next().ok_or_else(|| {
MetadataError::InvalidStructure("record missing payload".into())
})?;
decode_record_type(record, type_defs)
}
TAG_VARIANT => {
let record = payload.into_iter().next().ok_or_else(|| {
MetadataError::InvalidStructure("variant missing payload".into())
})?;
decode_variant_type(record, type_defs)
}
TAG_TUPLE => {
let list = payload.into_iter().next().ok_or_else(|| {
MetadataError::InvalidStructure("tuple missing payload".into())
})?;
match list {
Value::List { items, .. } => {
let types: Result<Vec<_>, _> = items
.into_iter()
.map(|v| decode_type_collecting(v, type_defs))
.collect();
Ok(Type::tuple(types?))
}
_ => Err(MetadataError::InvalidStructure(
"tuple payload not a list".into(),
)),
}
}
TAG_VALUE => Ok(Type::Value),
TAG_UNIT => Ok(Type::Unit),
_ => Err(MetadataError::InvalidStructure(format!(
"unknown type tag: {}",
tag
))),
}
}
_ => Err(MetadataError::InvalidStructure(
"expected variant for type".into(),
)),
}
}
fn decode_record_type(value: Value, type_defs: &mut Vec<TypeDef>) -> Result<Type, MetadataError> {
match value {
Value::Record {
fields: rec_fields, ..
} => {
let mut name = String::new();
let mut decoded_fields = Vec::new();
for (fname, val) in rec_fields {
match fname.as_str() {
"name" => {
if let Value::String(s) = val {
name = s;
}
}
"fields" => {
if let Value::List { items, .. } = val {
for item in items {
if let Value::Record {
fields: field_rec, ..
} = item
{
let mut field_name = String::new();
let mut field_type = Type::Value;
for (fn_name, fn_val) in field_rec {
match fn_name.as_str() {
"name" => {
if let Value::String(s) = fn_val {
field_name = s;
}
}
"type" => {
field_type =
decode_type_collecting(fn_val, type_defs)?;
}
_ => {}
}
}
decoded_fields.push(Field::new(field_name, field_type));
}
}
}
}
_ => {}
}
}
if !name.is_empty() && !decoded_fields.is_empty() {
if !type_defs.iter().any(|td| td.name() == name) {
type_defs.push(TypeDef::Record {
name: name.clone(),
fields: decoded_fields,
});
}
}
Ok(Type::Ref(TypePath::simple(name)))
}
_ => Err(MetadataError::InvalidStructure(
"record payload not a record".into(),
)),
}
}
fn decode_variant_type(value: Value, type_defs: &mut Vec<TypeDef>) -> Result<Type, MetadataError> {
match value {
Value::Record {
fields: rec_fields, ..
} => {
let mut name = String::new();
let mut decoded_cases = Vec::new();
for (fname, val) in rec_fields {
match fname.as_str() {
"name" => {
if let Value::String(s) = val {
name = s;
}
}
"cases" => {
if let Value::List { items, .. } = val {
for item in items {
if let Value::Record {
fields: case_rec, ..
} = item
{
let mut case_name = String::new();
let mut case_payload = Type::Unit;
for (cn, cv) in case_rec {
match cn.as_str() {
"name" => {
if let Value::String(s) = cv {
case_name = s;
}
}
"payload" => {
if let Value::Option {
value: Some(payload_val),
..
} = cv
{
case_payload = decode_type_collecting(
*payload_val,
type_defs,
)?;
}
}
_ => {}
}
}
decoded_cases.push(Case::new(case_name, case_payload));
}
}
}
}
_ => {}
}
}
if !name.is_empty()
&& !decoded_cases.is_empty()
&& !type_defs.iter().any(|td| td.name() == name)
{
type_defs.push(TypeDef::Variant {
name: name.clone(),
cases: decoded_cases,
});
}
Ok(Type::Ref(TypePath::simple(name)))
}
_ => Err(MetadataError::InvalidStructure(
"variant payload not a record".into(),
)),
}
}
pub fn encode_metadata(arena: &Arena) -> Result<Vec<u8>, MetadataError> {
let mut imports = Vec::new();
let mut exports = Vec::new();
for child in &arena.children {
match child.name.as_str() {
"imports" => {
for interface in &child.children {
for func in &interface.functions {
imports.push(encode_func_sig_value(&interface.name, func));
}
}
}
"exports" => {
for interface in &child.children {
for func in &interface.functions {
exports.push(encode_func_sig_value(&interface.name, func));
}
}
}
_ => {}
}
}
let record = Value::Record {
type_name: "PackageMetadata".to_string(),
fields: vec![
(
"imports".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Record("FunctionSignature".to_string()),
items: imports,
},
),
(
"exports".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Record("FunctionSignature".to_string()),
items: exports,
},
),
],
};
encode(&record).map_err(|e| MetadataError::EncodeFailed(format!("{:?}", e)))
}
fn encode_func_sig_value(interface: &str, func: &Function) -> Value {
Value::Record {
type_name: "FunctionSignature".to_string(),
fields: vec![
(
"interface".to_string(),
Value::String(interface.to_string()),
),
("name".to_string(), Value::String(func.name.clone())),
(
"params".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Record("ParamSignature".to_string()),
items: func.params.iter().map(encode_param_value).collect(),
},
),
(
"results".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Variant("Type".to_string()),
items: func.results.iter().map(encode_type_value).collect(),
},
),
],
}
}
fn encode_param_value(param: &Param) -> Value {
Value::Record {
type_name: "ParamSignature".to_string(),
fields: vec![
("name".to_string(), Value::String(param.name.clone())),
("type".to_string(), encode_type_value(¶m.ty)),
],
}
}
fn encode_type_value(ty: &Type) -> Value {
let (tag, payload) = match ty {
Type::Unit => (TAG_UNIT as usize, vec![]),
Type::Bool => (TAG_BOOL as usize, vec![]),
Type::U8 => (TAG_U8 as usize, vec![]),
Type::U16 => (TAG_U16 as usize, vec![]),
Type::U32 => (TAG_U32 as usize, vec![]),
Type::U64 => (TAG_U64 as usize, vec![]),
Type::S8 => (TAG_S8 as usize, vec![]),
Type::S16 => (TAG_S16 as usize, vec![]),
Type::S32 => (TAG_S32 as usize, vec![]),
Type::S64 => (TAG_S64 as usize, vec![]),
Type::F32 => (TAG_F32 as usize, vec![]),
Type::F64 => (TAG_F64 as usize, vec![]),
Type::Char => (TAG_CHAR as usize, vec![]),
Type::String => (TAG_STRING as usize, vec![]),
Type::List(inner) => (TAG_LIST as usize, vec![encode_type_value(inner)]),
Type::Option(inner) => (TAG_OPTION as usize, vec![encode_type_value(inner)]),
Type::Result { ok, err } => (
TAG_RESULT as usize,
vec![Value::Record {
type_name: "ResultPayload".to_string(),
fields: vec![
("ok".to_string(), encode_type_value(ok)),
("err".to_string(), encode_type_value(err)),
],
}],
),
Type::Tuple(types) => (
TAG_TUPLE as usize,
vec![Value::List {
elem_type: crate::abi::ValueType::Variant("Type".to_string()),
items: types.iter().map(encode_type_value).collect(),
}],
),
Type::Ref(path) => {
let name = path.segments.join("::");
(
TAG_VARIANT as usize,
vec![Value::Record {
type_name: "TypeRef".to_string(),
fields: vec![("name".to_string(), Value::String(name))],
}],
)
}
Type::Value => (TAG_VALUE as usize, vec![]),
};
Value::Variant {
type_name: "Type".to_string(),
case_name: format!("tag{}", tag),
tag,
payload,
}
}
pub type TypeDesc = Type;
pub type FieldDesc = Field;
pub type CaseDesc = Case;
pub type ParamSignature = Param;
pub type FunctionSignature = Function;
pub type PackageMetadata = Arena;
pub fn encode_metadata_with_hashes(arena: &Arena) -> Result<Vec<u8>, MetadataError> {
let mut imports = Vec::new();
let mut exports = Vec::new();
for child in &arena.children {
match child.name.as_str() {
"imports" => {
for interface in &child.children {
for func in &interface.functions {
imports.push(encode_func_sig_value(&interface.name, func));
}
}
}
"exports" => {
for interface in &child.children {
for func in &interface.functions {
exports.push(encode_func_sig_value(&interface.name, func));
}
}
}
_ => {}
}
}
let import_hashes = compute_interface_hashes(arena, "imports");
let export_hashes = compute_interface_hashes(arena, "exports");
let record = Value::Record {
type_name: "PackageMetadata".to_string(),
fields: vec![
(
"imports".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Record("FunctionSignature".to_string()),
items: imports,
},
),
(
"exports".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Record("FunctionSignature".to_string()),
items: exports,
},
),
(
"import-hashes".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Record("InterfaceHash".to_string()),
items: import_hashes
.iter()
.map(encode_interface_hash_value)
.collect(),
},
),
(
"export-hashes".to_string(),
Value::List {
elem_type: crate::abi::ValueType::Record("InterfaceHash".to_string()),
items: export_hashes
.iter()
.map(encode_interface_hash_value)
.collect(),
},
),
],
};
encode(&record).map_err(|e| MetadataError::EncodeFailed(format!("{:?}", e)))
}
fn encode_interface_hash_value(ih: &InterfaceHash) -> Value {
Value::Record {
type_name: "InterfaceHash".to_string(),
fields: vec![
("name".to_string(), Value::String(ih.name.clone())),
(
"hash".to_string(),
Value::List {
elem_type: crate::abi::ValueType::U8,
items: ih.hash.as_bytes().iter().map(|&b| Value::U8(b)).collect(),
},
),
],
}
}
#[derive(Debug, Clone)]
pub enum TypeValidationError {
TypeMismatch { expected: String, got: String },
MissingField { record: String, field: String },
ExtraField { record: String, field: String },
UnknownCase { variant: String, case: String },
UnresolvedRef { path: String },
WrongArity { expected: usize, got: usize },
Nested {
context: String,
inner: Box<TypeValidationError>,
},
}
impl std::fmt::Display for TypeValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TypeValidationError::TypeMismatch { expected, got } => {
write!(f, "expected {}, got {}", expected, got)
}
TypeValidationError::MissingField { record, field } => {
write!(f, "record '{}' missing field '{}'", record, field)
}
TypeValidationError::ExtraField { record, field } => {
write!(f, "record '{}' has unexpected field '{}'", record, field)
}
TypeValidationError::UnknownCase { variant, case } => {
write!(f, "variant '{}' has no case '{}'", variant, case)
}
TypeValidationError::UnresolvedRef { path } => {
write!(f, "unresolved type reference '{}'", path)
}
TypeValidationError::WrongArity { expected, got } => {
write!(f, "expected {} elements, got {}", expected, got)
}
TypeValidationError::Nested { context, inner } => {
write!(f, "in {}: {}", context, inner)
}
}
}
}
impl std::error::Error for TypeValidationError {}
pub fn validate_value_in_type_space(
value: &Value,
expected: &Type,
type_defs: &[TypeDef],
) -> Result<(), TypeValidationError> {
match expected {
Type::Value => Ok(()),
Type::Unit => match value {
Value::Tuple(items) if items.is_empty() => Ok(()),
_ => Err(TypeValidationError::TypeMismatch {
expected: "unit".into(),
got: value_type_name(value),
}),
},
Type::Bool => match value {
Value::Bool(_) => Ok(()),
_ => Err(mismatch("bool", value)),
},
Type::U8 => match value {
Value::U8(_) => Ok(()),
_ => Err(mismatch("u8", value)),
},
Type::U16 => match value {
Value::U16(_) => Ok(()),
_ => Err(mismatch("u16", value)),
},
Type::U32 => match value {
Value::U32(_) => Ok(()),
_ => Err(mismatch("u32", value)),
},
Type::U64 => match value {
Value::U64(_) => Ok(()),
_ => Err(mismatch("u64", value)),
},
Type::S8 => match value {
Value::S8(_) => Ok(()),
_ => Err(mismatch("s8", value)),
},
Type::S16 => match value {
Value::S16(_) => Ok(()),
_ => Err(mismatch("s16", value)),
},
Type::S32 => match value {
Value::S32(_) => Ok(()),
_ => Err(mismatch("s32", value)),
},
Type::S64 => match value {
Value::S64(_) => Ok(()),
_ => Err(mismatch("s64", value)),
},
Type::F32 => match value {
Value::F32(_) => Ok(()),
_ => Err(mismatch("f32", value)),
},
Type::F64 => match value {
Value::F64(_) => Ok(()),
_ => Err(mismatch("f64", value)),
},
Type::Char => match value {
Value::Char(_) => Ok(()),
_ => Err(mismatch("char", value)),
},
Type::String => match value {
Value::String(_) => Ok(()),
_ => Err(mismatch("string", value)),
},
Type::List(elem_type) => match value {
Value::List { items, .. } => {
for (i, item) in items.iter().enumerate() {
validate_value_in_type_space(item, elem_type, type_defs).map_err(|e| {
TypeValidationError::Nested {
context: format!("list[{}]", i),
inner: Box::new(e),
}
})?;
}
Ok(())
}
_ => Err(mismatch("list", value)),
},
Type::Option(inner_type) => match value {
Value::Option { value: inner, .. } => {
if let Some(v) = inner {
validate_value_in_type_space(v, inner_type, type_defs).map_err(|e| {
TypeValidationError::Nested {
context: "option::some".into(),
inner: Box::new(e),
}
})?;
}
Ok(())
}
_ => Err(mismatch("option", value)),
},
Type::Result { ok, err } => match value {
Value::Result { value: result, .. } => {
match result {
Ok(v) => validate_value_in_type_space(v, ok, type_defs).map_err(|e| {
TypeValidationError::Nested {
context: "result::ok".into(),
inner: Box::new(e),
}
})?,
Err(v) => validate_value_in_type_space(v, err, type_defs).map_err(|e| {
TypeValidationError::Nested {
context: "result::err".into(),
inner: Box::new(e),
}
})?,
}
Ok(())
}
_ => Err(mismatch("result", value)),
},
Type::Tuple(types) => match value {
Value::Tuple(items) => {
if types.len() != items.len() {
return Err(TypeValidationError::WrongArity {
expected: types.len(),
got: items.len(),
});
}
for (i, (ty, val)) in types.iter().zip(items.iter()).enumerate() {
validate_value_in_type_space(val, ty, type_defs).map_err(|e| {
TypeValidationError::Nested {
context: format!("tuple.{}", i),
inner: Box::new(e),
}
})?;
}
Ok(())
}
_ => Err(mismatch("tuple", value)),
},
Type::Ref(path) => {
let name = path.segments.last().map(|s| s.as_str()).unwrap_or("");
let type_def = type_defs.iter().find(|td| td.name() == name);
match type_def {
Some(TypeDef::Record { name, fields }) => {
validate_record(value, name, fields, type_defs)
}
Some(TypeDef::Variant { name, cases }) => {
validate_variant(value, name, cases, type_defs)
}
Some(TypeDef::Alias { ty, .. }) => {
validate_value_in_type_space(value, ty, type_defs)
}
Some(TypeDef::Enum { name, cases }) => validate_enum(value, name, cases),
Some(TypeDef::Flags { .. }) => match value {
Value::Flags(_) => Ok(()),
_ => Err(mismatch("flags", value)),
},
None => Err(TypeValidationError::UnresolvedRef {
path: path.to_string(),
}),
}
}
}
}
fn validate_record(
value: &Value,
record_name: &str,
expected_fields: &[Field],
type_defs: &[TypeDef],
) -> Result<(), TypeValidationError> {
match value {
Value::Record {
fields: val_fields, ..
} => {
for expected in expected_fields {
if !val_fields.iter().any(|(name, _)| name == &expected.name) {
return Err(TypeValidationError::MissingField {
record: record_name.into(),
field: expected.name.clone(),
});
}
}
for (name, _) in val_fields {
if !expected_fields.iter().any(|f| &f.name == name) {
return Err(TypeValidationError::ExtraField {
record: record_name.into(),
field: name.clone(),
});
}
}
for (name, val) in val_fields {
if let Some(field_def) = expected_fields.iter().find(|f| &f.name == name) {
validate_value_in_type_space(val, &field_def.ty, type_defs).map_err(|e| {
TypeValidationError::Nested {
context: format!("field '{}'", name),
inner: Box::new(e),
}
})?;
}
}
Ok(())
}
_ => Err(TypeValidationError::TypeMismatch {
expected: format!("record '{}'", record_name),
got: value_type_name(value),
}),
}
}
fn validate_variant(
value: &Value,
variant_name: &str,
expected_cases: &[Case],
type_defs: &[TypeDef],
) -> Result<(), TypeValidationError> {
match value {
Value::Variant {
case_name, payload, ..
} => {
let case_def = expected_cases.iter().find(|c| &c.name == case_name);
match case_def {
Some(case) => {
match (&case.payload, payload.len()) {
(Type::Unit, 0) => Ok(()),
(Type::Unit, n) => Err(TypeValidationError::Nested {
context: format!("case '{}'", case_name),
inner: Box::new(TypeValidationError::WrongArity {
expected: 0,
got: n,
}),
}),
(ty, 1) => validate_value_in_type_space(&payload[0], ty, type_defs)
.map_err(|e| TypeValidationError::Nested {
context: format!("case '{}'", case_name),
inner: Box::new(e),
}),
(_, n) => Err(TypeValidationError::Nested {
context: format!("case '{}'", case_name),
inner: Box::new(TypeValidationError::WrongArity {
expected: 1,
got: n,
}),
}),
}
}
None => Err(TypeValidationError::UnknownCase {
variant: variant_name.into(),
case: case_name.clone(),
}),
}
}
_ => Err(TypeValidationError::TypeMismatch {
expected: format!("variant '{}'", variant_name),
got: value_type_name(value),
}),
}
}
fn validate_enum(
value: &Value,
enum_name: &str,
expected_cases: &[String],
) -> Result<(), TypeValidationError> {
match value {
Value::Variant {
case_name, payload, ..
} => {
if !expected_cases.iter().any(|c| c == case_name) {
return Err(TypeValidationError::UnknownCase {
variant: enum_name.into(),
case: case_name.clone(),
});
}
if !payload.is_empty() {
return Err(TypeValidationError::Nested {
context: format!("enum case '{}'", case_name),
inner: Box::new(TypeValidationError::WrongArity {
expected: 0,
got: payload.len(),
}),
});
}
Ok(())
}
_ => Err(TypeValidationError::TypeMismatch {
expected: format!("enum '{}'", enum_name),
got: value_type_name(value),
}),
}
}
fn mismatch(expected: &str, value: &Value) -> TypeValidationError {
TypeValidationError::TypeMismatch {
expected: expected.into(),
got: value_type_name(value),
}
}
fn value_type_name(value: &Value) -> String {
match value {
Value::Bool(_) => "bool".into(),
Value::U8(_) => "u8".into(),
Value::U16(_) => "u16".into(),
Value::U32(_) => "u32".into(),
Value::U64(_) => "u64".into(),
Value::S8(_) => "s8".into(),
Value::S16(_) => "s16".into(),
Value::S32(_) => "s32".into(),
Value::S64(_) => "s64".into(),
Value::F32(_) => "f32".into(),
Value::F64(_) => "f64".into(),
Value::Char(_) => "char".into(),
Value::String(_) => "string".into(),
Value::List { .. } => "list".into(),
Value::Option { .. } => "option".into(),
Value::Result { .. } => "result".into(),
Value::Record { type_name, .. } => format!("record '{}'", type_name),
Value::Variant {
type_name,
case_name,
..
} => format!("variant '{}' (case '{}')", type_name, case_name),
Value::Tuple(items) => format!("tuple<{}>", items.len()),
Value::Flags(_) => "flags".into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_tags_preserved() {
assert_eq!(TAG_BOOL, 0);
assert_eq!(TAG_U8, 1);
assert_eq!(TAG_STRING, 12);
assert_eq!(TAG_VALUE, 20);
assert_eq!(TAG_UNIT, 21);
}
#[test]
fn test_hash_type_primitives() {
assert_eq!(hash_type(&Type::Bool), HASH_BOOL);
assert_eq!(hash_type(&Type::String), HASH_STRING);
assert_eq!(hash_type(&Type::S32), HASH_S32);
assert_eq!(hash_type(&Type::U8), HASH_U8);
}
#[test]
fn test_hash_type_compound() {
let list_hash = hash_type(&Type::List(Box::new(Type::U8)));
let expected = hash_list(&HASH_U8);
assert_eq!(list_hash, expected);
let option_hash = hash_type(&Type::Option(Box::new(Type::String)));
let expected = hash_option(&HASH_STRING);
assert_eq!(option_hash, expected);
let result_hash = hash_type(&Type::Result {
ok: Box::new(Type::String),
err: Box::new(Type::String),
});
let expected = hash_result(&HASH_STRING, &HASH_STRING);
assert_eq!(result_hash, expected);
}
#[test]
fn test_compute_interface_hash() {
let mut interface = Arena::new("test:example/api");
interface.add_function(Function::with_signature(
"greet",
vec![Param::new("name", Type::String)],
vec![Type::String],
));
interface.add_function(Function::with_signature(
"add",
vec![Param::new("a", Type::S32), Param::new("b", Type::S32)],
vec![Type::S32],
));
let hash1 = compute_interface_hash(&interface);
let hash2 = compute_interface_hash(&interface);
assert_eq!(hash1, hash2);
let mut interface_reordered = Arena::new("test:example/api");
interface_reordered.add_function(Function::with_signature(
"add",
vec![Param::new("a", Type::S32), Param::new("b", Type::S32)],
vec![Type::S32],
));
interface_reordered.add_function(Function::with_signature(
"greet",
vec![Param::new("name", Type::String)],
vec![Type::String],
));
let hash3 = compute_interface_hash(&interface_reordered);
assert_eq!(hash1, hash3);
}
#[test]
fn test_compute_interface_hash_differs_on_signature() {
let mut interface1 = Arena::new("test:api");
interface1.add_function(Function::with_signature(
"foo",
vec![Param::new("x", Type::S32)],
vec![Type::S32],
));
let mut interface2 = Arena::new("test:api");
interface2.add_function(Function::with_signature(
"foo",
vec![Param::new("x", Type::S64)], vec![Type::S64],
));
assert_ne!(
compute_interface_hash(&interface1),
compute_interface_hash(&interface2)
);
}
#[test]
fn test_encode_metadata_with_hashes_roundtrip() {
let mut arena = Arena::new("package");
let mut imports = Arena::new("imports");
let mut runtime_interface = Arena::new("theater:simple/runtime");
runtime_interface.add_function(Function::with_signature(
"log",
vec![Param::new("msg", Type::String)],
vec![],
));
imports.add_child(runtime_interface);
arena.add_child(imports);
let mut exports = Arena::new("exports");
let mut actor_interface = Arena::new("theater:simple/actor");
actor_interface.add_function(Function::with_signature(
"init",
vec![Param::new("data", Type::List(Box::new(Type::U8)))],
vec![Type::List(Box::new(Type::U8))],
));
exports.add_child(actor_interface);
arena.add_child(exports);
let bytes = encode_metadata_with_hashes(&arena).expect("encoding failed");
let decoded = decode_metadata_with_hashes(&bytes).expect("decoding failed");
assert_eq!(decoded.import_hashes.len(), 1);
assert_eq!(decoded.import_hashes[0].name, "theater:simple/runtime");
assert_eq!(decoded.export_hashes.len(), 1);
assert_eq!(decoded.export_hashes[0].name, "theater:simple/actor");
assert!(!decoded.import_hashes[0]
.hash
.as_bytes()
.iter()
.all(|&b| b == 0));
assert!(!decoded.export_hashes[0]
.hash
.as_bytes()
.iter()
.all(|&b| b == 0));
}
#[test]
fn test_validate_primitives() {
let defs = vec![];
assert!(validate_value_in_type_space(&Value::Bool(true), &Type::Bool, &defs).is_ok());
assert!(validate_value_in_type_space(&Value::U32(42), &Type::U32, &defs).is_ok());
assert!(
validate_value_in_type_space(&Value::String("hi".into()), &Type::String, &defs).is_ok()
);
assert!(validate_value_in_type_space(&Value::Bool(true), &Type::U32, &defs).is_err());
assert!(validate_value_in_type_space(&Value::U32(42), &Type::String, &defs).is_err());
}
#[test]
fn test_validate_unit() {
let defs = vec![];
assert!(validate_value_in_type_space(&Value::Tuple(vec![]), &Type::Unit, &defs).is_ok());
assert!(validate_value_in_type_space(&Value::Bool(true), &Type::Unit, &defs).is_err());
}
#[test]
fn test_validate_type_value_escape_hatch() {
let defs = vec![];
assert!(validate_value_in_type_space(&Value::Bool(true), &Type::Value, &defs).is_ok());
assert!(
validate_value_in_type_space(&Value::String("x".into()), &Type::Value, &defs).is_ok()
);
assert!(validate_value_in_type_space(
&Value::Tuple(vec![Value::U8(1)]),
&Type::Value,
&defs
)
.is_ok());
}
#[test]
fn test_validate_tuple() {
let defs = vec![];
let ty = Type::Tuple(vec![Type::String, Type::U32]);
let val = Value::Tuple(vec![Value::String("a".into()), Value::U32(1)]);
assert!(validate_value_in_type_space(&val, &ty, &defs).is_ok());
let val_short = Value::Tuple(vec![Value::String("a".into())]);
assert!(validate_value_in_type_space(&val_short, &ty, &defs).is_err());
let val_bad = Value::Tuple(vec![Value::String("a".into()), Value::Bool(true)]);
assert!(validate_value_in_type_space(&val_bad, &ty, &defs).is_err());
}
#[test]
fn test_validate_record() {
let defs = vec![TypeDef::Record {
name: "my-state".into(),
fields: vec![
Field::new("count", Type::S32),
Field::new("name", Type::String),
],
}];
let ty = Type::Ref(TypePath::simple("my-state"));
let val = Value::Record {
type_name: "my-state".into(),
fields: vec![
("count".into(), Value::S32(5)),
("name".into(), Value::String("test".into())),
],
};
assert!(validate_value_in_type_space(&val, &ty, &defs).is_ok());
let val_missing = Value::Record {
type_name: "my-state".into(),
fields: vec![("count".into(), Value::S32(5))],
};
assert!(validate_value_in_type_space(&val_missing, &ty, &defs).is_err());
let val_extra = Value::Record {
type_name: "my-state".into(),
fields: vec![
("count".into(), Value::S32(5)),
("name".into(), Value::String("test".into())),
("bonus".into(), Value::Bool(true)),
],
};
assert!(validate_value_in_type_space(&val_extra, &ty, &defs).is_err());
let val_wrong_type = Value::Record {
type_name: "my-state".into(),
fields: vec![
("count".into(), Value::String("not a number".into())),
("name".into(), Value::String("test".into())),
],
};
assert!(validate_value_in_type_space(&val_wrong_type, &ty, &defs).is_err());
}
#[test]
fn test_validate_variant() {
let defs = vec![TypeDef::Variant {
name: "result".into(),
cases: vec![
Case::new("ok", Type::String),
Case::new("err", Type::String),
],
}];
let ty = Type::Ref(TypePath::simple("result"));
let val = Value::Variant {
type_name: "result".into(),
case_name: "ok".into(),
tag: 0,
payload: vec![Value::String("success".into())],
};
assert!(validate_value_in_type_space(&val, &ty, &defs).is_ok());
let val_bad = Value::Variant {
type_name: "result".into(),
case_name: "maybe".into(),
tag: 2,
payload: vec![],
};
assert!(validate_value_in_type_space(&val_bad, &ty, &defs).is_err());
let val_wrong = Value::Variant {
type_name: "result".into(),
case_name: "ok".into(),
tag: 0,
payload: vec![Value::U32(42)],
};
assert!(validate_value_in_type_space(&val_wrong, &ty, &defs).is_err());
}
#[test]
fn test_validate_enum() {
let defs = vec![TypeDef::Enum {
name: "color".into(),
cases: vec!["red".into(), "green".into(), "blue".into()],
}];
let ty = Type::Ref(TypePath::simple("color"));
let val = Value::Variant {
type_name: "color".into(),
case_name: "red".into(),
tag: 0,
payload: vec![],
};
assert!(validate_value_in_type_space(&val, &ty, &defs).is_ok());
let val_bad = Value::Variant {
type_name: "color".into(),
case_name: "purple".into(),
tag: 3,
payload: vec![],
};
assert!(validate_value_in_type_space(&val_bad, &ty, &defs).is_err());
}
#[test]
fn test_validate_nested_record_with_variant() {
let defs = vec![
TypeDef::Record {
name: "state".into(),
fields: vec![
Field::new("status", Type::Ref(TypePath::simple("status"))),
Field::new("count", Type::U32),
],
},
TypeDef::Variant {
name: "status".into(),
cases: vec![Case::unit("pending"), Case::new("active", Type::String)],
},
];
let ty = Type::Ref(TypePath::simple("state"));
let val = Value::Record {
type_name: "state".into(),
fields: vec![
(
"status".into(),
Value::Variant {
type_name: "status".into(),
case_name: "active".into(),
tag: 1,
payload: vec![Value::String("running".into())],
},
),
("count".into(), Value::U32(10)),
],
};
assert!(validate_value_in_type_space(&val, &ty, &defs).is_ok());
let val_bad = Value::Record {
type_name: "state".into(),
fields: vec![
(
"status".into(),
Value::Variant {
type_name: "status".into(),
case_name: "active".into(),
tag: 1,
payload: vec![Value::U32(42)], },
),
("count".into(), Value::U32(10)),
],
};
assert!(validate_value_in_type_space(&val_bad, &ty, &defs).is_err());
}
#[test]
fn test_validate_unresolved_ref() {
let defs = vec![];
let ty = Type::Ref(TypePath::simple("nonexistent"));
let val = Value::Bool(true);
let err = validate_value_in_type_space(&val, &ty, &defs).unwrap_err();
assert!(matches!(err, TypeValidationError::UnresolvedRef { .. }));
}
#[test]
fn test_validate_option_and_list() {
use crate::abi::ValueType;
let defs = vec![];
let ty = Type::Option(Box::new(Type::String));
let val = Value::Option {
inner_type: ValueType::String,
value: Some(Box::new(Value::String("hi".into()))),
};
assert!(validate_value_in_type_space(&val, &ty, &defs).is_ok());
let val_none = Value::Option {
inner_type: ValueType::String,
value: None,
};
assert!(validate_value_in_type_space(&val_none, &ty, &defs).is_ok());
let ty_list = Type::List(Box::new(Type::U32));
let val_list = Value::List {
elem_type: ValueType::U32,
items: vec![Value::U32(1), Value::U32(2)],
};
assert!(validate_value_in_type_space(&val_list, &ty_list, &defs).is_ok());
let val_bad_list = Value::List {
elem_type: ValueType::U32,
items: vec![Value::U32(1), Value::String("oops".into())],
};
assert!(validate_value_in_type_space(&val_bad_list, &ty_list, &defs).is_err());
}
#[test]
fn test_hash_type_ref_resolves_structurally() {
let types = vec![TypeDef::record(
"point",
vec![Field::new("x", Type::S32), Field::new("y", Type::S32)],
)];
let ref_hash = hash_type_in(&Type::named("point"), &types);
let structural_hash =
hash_record(&[("x", hash_type(&Type::S32)), ("y", hash_type(&Type::S32))]);
assert_eq!(ref_hash, structural_hash);
}
#[test]
fn test_hash_type_ref_unresolved_falls_back_to_path() {
let h1 = hash_type_in(&Type::named("unknown-type"), &[]);
let h2 = hash_type(&Type::named("unknown-type"));
assert_eq!(h1, h2);
assert_ne!(h1, HASH_STRING);
}
#[test]
fn test_hash_type_recursive_ref_terminates() {
let types = vec![TypeDef::record(
"tree",
vec![Field::new("children", Type::list(Type::named("tree")))],
)];
let h = hash_type_in(&Type::named("tree"), &types);
let again = hash_type_in(&Type::named("tree"), &types);
assert_eq!(h, again);
let expected = hash_record(&[("children", hash_list(&HASH_SELF_REF))]);
assert_eq!(h, expected);
}
#[test]
fn test_compute_interface_hash_resolves_record_refs() {
let mut by_ref = Arena::new("test:api/podman");
by_ref.add_type(TypeDef::record(
"container-spec",
vec![
Field::new("image", Type::String),
Field::new("name", Type::String),
],
));
by_ref.add_function(Function::with_signature(
"run",
vec![Param::new("spec", Type::named("container-spec"))],
vec![Type::result(Type::String, Type::String)],
));
let record_hash = hash_record(&[("image", HASH_STRING), ("name", HASH_STRING)]);
let func_hash = hash_function(&[record_hash], &[hash_result(&HASH_STRING, &HASH_STRING)]);
let expected = hash_interface(
"test:api/podman",
&[],
&[Binding {
name: "run",
hash: func_hash,
}],
);
assert_eq!(compute_interface_hash(&by_ref), expected);
}
}