use crate::metadata::TypeHash;
use crate::parser::{MetadataValue, PactExport, PactInterface};
use crate::types::{Type, TypeDef};
pub trait PackType {
fn pack_type() -> Type;
}
impl PackType for () {
fn pack_type() -> Type {
Type::Unit
}
}
impl PackType for bool {
fn pack_type() -> Type {
Type::Bool
}
}
impl PackType for u8 {
fn pack_type() -> Type {
Type::U8
}
}
impl PackType for u16 {
fn pack_type() -> Type {
Type::U16
}
}
impl PackType for u32 {
fn pack_type() -> Type {
Type::U32
}
}
impl PackType for u64 {
fn pack_type() -> Type {
Type::U64
}
}
impl PackType for i8 {
fn pack_type() -> Type {
Type::S8
}
}
impl PackType for i16 {
fn pack_type() -> Type {
Type::S16
}
}
impl PackType for i32 {
fn pack_type() -> Type {
Type::S32
}
}
impl PackType for i64 {
fn pack_type() -> Type {
Type::S64
}
}
impl PackType for f32 {
fn pack_type() -> Type {
Type::F32
}
}
impl PackType for f64 {
fn pack_type() -> Type {
Type::F64
}
}
impl PackType for char {
fn pack_type() -> Type {
Type::Char
}
}
impl PackType for String {
fn pack_type() -> Type {
Type::String
}
}
impl PackType for crate::abi::Value {
fn pack_type() -> Type {
Type::Value
}
}
impl<T: PackType> PackType for Vec<T> {
fn pack_type() -> Type {
Type::List(Box::new(T::pack_type()))
}
}
impl<T: PackType> PackType for Option<T> {
fn pack_type() -> Type {
Type::Option(Box::new(T::pack_type()))
}
}
impl<T: PackType, E: PackType> PackType for Result<T, E> {
fn pack_type() -> Type {
Type::Result {
ok: Box::new(T::pack_type()),
err: Box::new(E::pack_type()),
}
}
}
impl<A: PackType> PackType for (A,) {
fn pack_type() -> Type {
Type::Tuple(vec![A::pack_type()])
}
}
impl<A: PackType, B: PackType> PackType for (A, B) {
fn pack_type() -> Type {
Type::Tuple(vec![A::pack_type(), B::pack_type()])
}
}
impl<A: PackType, B: PackType, C: PackType> PackType for (A, B, C) {
fn pack_type() -> Type {
Type::Tuple(vec![A::pack_type(), B::pack_type(), C::pack_type()])
}
}
impl<A: PackType, B: PackType, C: PackType, D: PackType> PackType for (A, B, C, D) {
fn pack_type() -> Type {
Type::Tuple(vec![
A::pack_type(),
B::pack_type(),
C::pack_type(),
D::pack_type(),
])
}
}
#[derive(Debug, Clone)]
pub struct FuncSignature {
pub name: String,
pub params: Vec<Type>,
pub results: Vec<Type>,
}
impl FuncSignature {
pub fn hash(&self) -> TypeHash {
self.hash_in(&[])
}
pub fn hash_in(&self, types: &[TypeDef]) -> TypeHash {
let param_hashes: Vec<_> = self
.params
.iter()
.map(|t| crate::metadata::hash_type_in(t, types))
.collect();
let result_hashes: Vec<_> = self
.results
.iter()
.map(|t| crate::metadata::hash_type_in(t, types))
.collect();
crate::metadata::hash_function(¶m_hashes, &result_hashes)
}
}
#[derive(Debug)]
pub struct InterfaceImpl {
pub name: String,
pub types: Vec<TypeDef>,
pub functions: Vec<FuncSignature>,
}
impl InterfaceImpl {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
types: Vec::new(),
functions: Vec::new(),
}
}
pub fn func<F, Args, Ret>(mut self, name: &str, _f: F) -> Self
where
F: HostFunc<Args, Ret>,
Args: PackParams,
Ret: PackType + 'static,
{
let params = Args::pack_types();
let results = if std::any::TypeId::of::<Ret>() == std::any::TypeId::of::<()>() {
vec![]
} else {
vec![Ret::pack_type()]
};
self.functions.push(FuncSignature {
name: name.to_string(),
params,
results,
});
self
}
pub fn hash(&self) -> TypeHash {
use crate::metadata::Binding;
let mut bindings: Vec<_> = self
.functions
.iter()
.map(|f| Binding {
name: &f.name,
hash: f.hash_in(&self.types),
})
.collect();
bindings.sort_by(|a, b| a.name.cmp(b.name));
crate::metadata::hash_interface(
&self.name,
&[], &bindings,
)
}
pub fn hash_subset(&self, function_names: &[&str]) -> Option<TypeHash> {
use crate::metadata::Binding;
let mut bindings = Vec::with_capacity(function_names.len());
for name in function_names {
let func = self.functions.iter().find(|f| f.name == *name)?;
bindings.push(Binding {
name: &func.name,
hash: func.hash_in(&self.types),
});
}
bindings.sort_by(|a, b| a.name.cmp(b.name));
Some(crate::metadata::hash_interface(
&self.name,
&[], &bindings,
))
}
pub fn function_hash(&self, name: &str) -> Option<TypeHash> {
self.functions
.iter()
.find(|f| f.name == name)
.map(|f| f.hash_in(&self.types))
}
pub fn name(&self) -> &str {
&self.name
}
pub fn signatures(&self) -> &[FuncSignature] {
&self.functions
}
pub fn from_pact(pact: &PactInterface) -> Self {
let package = pact
.metadata
.iter()
.find(|m| m.name == "package")
.and_then(|m| match &m.value {
MetadataValue::String(s) => Some(s.as_str()),
_ => None,
})
.unwrap_or("");
let full_name = if package.is_empty() {
pact.name.clone()
} else {
format!("{}/{}", package, pact.name)
};
let mut interface = InterfaceImpl::new(full_name);
interface.types = pact.types.clone();
for export in &pact.exports {
if let PactExport::Type(td) = export {
interface.types.push(td.clone());
}
}
for export in &pact.exports {
if let PactExport::Function(func) = export {
let params: Vec<Type> = func.params.iter().map(|p| p.ty.clone()).collect();
let results: Vec<Type> = func.results.clone();
interface.functions.push(FuncSignature {
name: func.name.clone(),
params,
results,
});
}
}
interface
}
}
pub trait PackParams {
fn pack_types() -> Vec<Type>;
}
impl PackParams for () {
fn pack_types() -> Vec<Type> {
vec![]
}
}
impl<A: PackType> PackParams for (A,) {
fn pack_types() -> Vec<Type> {
vec![A::pack_type()]
}
}
impl<A: PackType, B: PackType> PackParams for (A, B) {
fn pack_types() -> Vec<Type> {
vec![A::pack_type(), B::pack_type()]
}
}
impl<A: PackType, B: PackType, C: PackType> PackParams for (A, B, C) {
fn pack_types() -> Vec<Type> {
vec![A::pack_type(), B::pack_type(), C::pack_type()]
}
}
impl<A: PackType, B: PackType, C: PackType, D: PackType> PackParams for (A, B, C, D) {
fn pack_types() -> Vec<Type> {
vec![
A::pack_type(),
B::pack_type(),
C::pack_type(),
D::pack_type(),
]
}
}
pub trait HostFunc<Args, Ret> {}
impl<F, Ret> HostFunc<(), Ret> for F
where
F: Fn() -> Ret,
Ret: PackType,
{
}
impl<F, A, Ret> HostFunc<(A,), Ret> for F
where
F: Fn(A) -> Ret,
A: PackType,
Ret: PackType,
{
}
impl<F, A, B, Ret> HostFunc<(A, B), Ret> for F
where
F: Fn(A, B) -> Ret,
A: PackType,
B: PackType,
Ret: PackType,
{
}
impl<F, A, B, C, Ret> HostFunc<(A, B, C), Ret> for F
where
F: Fn(A, B, C) -> Ret,
A: PackType,
B: PackType,
C: PackType,
Ret: PackType,
{
}
impl<F, A, B, C, D, Ret> HostFunc<(A, B, C, D), Ret> for F
where
F: Fn(A, B, C, D) -> Ret,
A: PackType,
B: PackType,
C: PackType,
D: PackType,
Ret: PackType,
{
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pack_type_primitives() {
assert!(matches!(String::pack_type(), Type::String));
assert!(matches!(i32::pack_type(), Type::S32));
assert!(matches!(bool::pack_type(), Type::Bool));
}
#[test]
fn test_pack_type_compound() {
let list_type = Vec::<u8>::pack_type();
assert!(matches!(list_type, Type::List(_)));
let option_type = Option::<String>::pack_type();
assert!(matches!(option_type, Type::Option(_)));
let result_type = Result::<String, String>::pack_type();
assert!(matches!(result_type, Type::Result { .. }));
}
#[test]
fn test_interface_impl_basic() {
let interface = InterfaceImpl::new("test:example/api")
.func("greet", |name: String| -> String {
format!("Hello, {}!", name)
})
.func("add", |a: i32, b: i32| -> i32 { a + b });
assert_eq!(interface.name(), "test:example/api");
assert_eq!(interface.functions.len(), 2);
let greet = &interface.functions[0];
assert_eq!(greet.name, "greet");
assert_eq!(greet.params.len(), 1);
assert!(matches!(greet.params[0], Type::String));
assert_eq!(greet.results.len(), 1);
assert!(matches!(greet.results[0], Type::String));
let add = &interface.functions[1];
assert_eq!(add.name, "add");
assert_eq!(add.params.len(), 2);
}
#[test]
fn test_interface_hash_deterministic() {
let interface1 = InterfaceImpl::new("test:api")
.func("foo", |x: i32| -> i32 { x })
.func("bar", |s: String| -> String { s });
let interface2 = InterfaceImpl::new("test:api")
.func("bar", |s: String| -> String { s }) .func("foo", |x: i32| -> i32 { x });
assert_eq!(interface1.hash(), interface2.hash());
}
#[test]
fn test_interface_hash_differs_on_signature() {
let interface1 = InterfaceImpl::new("test:api").func("foo", |x: i32| -> i32 { x });
let interface2 = InterfaceImpl::new("test:api").func("foo", |x: i64| -> i64 { x });
assert_ne!(interface1.hash(), interface2.hash());
}
#[test]
fn test_from_pact_basic() {
use crate::parser::parse_pact;
let src = r#"
interface runtime {
@package: string = "theater:simple"
exports {
log: func(msg: string)
get-chain: func() -> list<u8>
shutdown: func(data: option<list<u8>>) -> result<_, string>
}
}
"#;
let pact = parse_pact(src).expect("should parse");
let interface = InterfaceImpl::from_pact(&pact);
assert_eq!(interface.name(), "theater:simple/runtime");
assert_eq!(interface.functions.len(), 3);
let names: Vec<&str> = interface
.functions
.iter()
.map(|f| f.name.as_str())
.collect();
assert!(names.contains(&"log"));
assert!(names.contains(&"get-chain"));
assert!(names.contains(&"shutdown"));
let log_fn = interface
.functions
.iter()
.find(|f| f.name == "log")
.unwrap();
assert_eq!(log_fn.params.len(), 1);
assert!(matches!(log_fn.params[0], Type::String));
assert!(log_fn.results.is_empty());
let get_chain_fn = interface
.functions
.iter()
.find(|f| f.name == "get-chain")
.unwrap();
assert!(get_chain_fn.params.is_empty());
assert_eq!(get_chain_fn.results.len(), 1);
assert!(matches!(get_chain_fn.results[0], Type::List(_)));
}
#[test]
fn test_from_pact_hash_determinism() {
use crate::parser::parse_pact;
let src = r#"
interface test {
@package: string = "my:package"
exports {
add: func(a: s32, b: s32) -> s32
subtract: func(a: s32, b: s32) -> s32
}
}
"#;
let pact1 = parse_pact(src).expect("should parse");
let pact2 = parse_pact(src).expect("should parse");
let interface1 = InterfaceImpl::from_pact(&pact1);
let interface2 = InterfaceImpl::from_pact(&pact2);
assert_eq!(interface1.hash(), interface2.hash());
}
#[test]
fn test_from_pact_matches_hand_coded() {
use crate::parser::parse_pact;
let hand_coded = InterfaceImpl::new("test:example/calculator")
.func("add", |_a: i32, _b: i32| -> i32 { 0 })
.func("subtract", |_a: i32, _b: i32| -> i32 { 0 });
let src = r#"
interface calculator {
@package: string = "test:example"
exports {
add: func(a: s32, b: s32) -> s32
subtract: func(a: s32, b: s32) -> s32
}
}
"#;
let pact = parse_pact(src).expect("should parse");
let from_pact = InterfaceImpl::from_pact(&pact);
assert_eq!(hand_coded.name(), from_pact.name());
assert_eq!(
hand_coded.hash(),
from_pact.hash(),
"Hand-coded interface hash should match pact-based interface hash"
);
}
}