use crate::datum::{self, Datum, DatumError};
use crate::{
node::{self, Node},
visit,
};
use serde::de::{self, Deserialize, DeserializeOwned, Deserializer, Visitor};
use serde::ser::{Serialize, SerializeMap, Serializer};
use std::collections::BTreeMap;
use std::fmt;
pub trait AsRefNode {
fn as_ref_node(&self) -> Option<&Ref>;
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Ref {
addr: gantz_ca::ContentAddr,
ext: BTreeMap<String, Datum>,
}
impl Ref {
pub fn new(addr: gantz_ca::ContentAddr) -> Self {
Self {
addr,
ext: BTreeMap::new(),
}
}
pub fn content_addr(&self) -> gantz_ca::ContentAddr {
self.addr
}
pub fn retarget(&self, addr: gantz_ca::ContentAddr) -> Self {
Self {
addr,
ext: self.ext.clone(),
}
}
pub fn ext(&self, key: &str) -> Option<&Datum> {
self.ext.get(key)
}
pub fn ext_as<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
self.ext
.get(key)
.and_then(|d| datum::from_datum(d.clone()).ok())
}
pub fn set_ext(
&mut self,
key: impl Into<String>,
value: &impl Serialize,
) -> Result<(), DatumError> {
let mut d = datum::to_datum(value)?;
d.canonicalize();
self.ext.insert(key.into(), d);
Ok(())
}
pub fn remove_ext(&mut self, key: &str) -> Option<Datum> {
self.ext.remove(key)
}
}
struct ExtMap<'a>(&'a Ref);
impl Serialize for ExtMap<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut map = s.serialize_map(Some(2))?;
map.serialize_entry("addr", &self.0.addr)?;
map.serialize_entry("ext", &self.0.ext)?;
map.end()
}
}
impl Serialize for Ref {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if self.ext.is_empty() {
s.serialize_newtype_struct("Ref", &self.addr)
} else {
s.serialize_newtype_struct("Ref", &ExtMap(self))
}
}
}
impl<'de> Deserialize<'de> for Ref {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct RefVisitor;
impl<'de> Visitor<'de> for RefVisitor {
type Value = Ref;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a content address or a map of `addr` + `ext`")
}
fn visit_newtype_struct<D: Deserializer<'de>>(self, d: D) -> Result<Ref, D::Error> {
d.deserialize_any(RefVisitor)
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Ref, E> {
let addr = v.parse::<gantz_ca::ContentAddr>().map_err(|_| {
de::Error::invalid_value(de::Unexpected::Str(v), &"a hex content address")
})?;
Ok(Ref::new(addr))
}
fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Ref, A::Error> {
let hex: String = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
self.visit_str(&hex)
}
fn visit_map<A: de::MapAccess<'de>>(self, mut map: A) -> Result<Ref, A::Error> {
let mut addr = None;
let mut ext = BTreeMap::new();
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"addr" => addr = Some(map.next_value::<gantz_ca::ContentAddr>()?),
"ext" => ext = map.next_value::<BTreeMap<String, Datum>>()?,
_ => {
map.next_value::<de::IgnoredAny>()?;
}
}
}
let addr = addr.ok_or_else(|| de::Error::missing_field("addr"))?;
ext.values_mut().for_each(Datum::canonicalize);
Ok(Ref { addr, ext })
}
}
d.deserialize_newtype_struct("Ref", RefVisitor)
}
}
impl AsRefNode for Ref {
fn as_ref_node(&self) -> Option<&Ref> {
Some(self)
}
}
impl gantz_ca::CaHash for Ref {
fn hash(&self, hasher: &mut gantz_ca::Hasher) {
hasher.update("gantz.ref".as_bytes());
gantz_ca::CaHash::hash(&self.addr, hasher);
if !self.ext.is_empty() {
hasher.update(b"ext");
for (k, v) in &self.ext {
hasher.update(&(k.len() as u64).to_be_bytes());
hasher.update(k.as_bytes());
gantz_ca::CaHash::hash(v, hasher);
}
}
}
}
impl Node for Ref {
fn n_inputs(&self, ctx: node::MetaCtx) -> usize {
ctx.node(&self.addr).map(|n| n.n_inputs(ctx)).unwrap_or(0)
}
fn n_outputs(&self, ctx: node::MetaCtx) -> usize {
ctx.node(&self.addr).map(|n| n.n_outputs(ctx)).unwrap_or(0)
}
fn branches(&self, ctx: node::MetaCtx) -> Vec<node::EvalConf> {
ctx.node(&self.addr)
.map(|n| n.branches(ctx))
.unwrap_or_default()
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
match ctx.node(&self.addr) {
Some(n) => n.expr(ctx),
None => Err(node::ExprError::custom(format!(
"node not found for address {:?}",
self.addr
))),
}
}
fn push_eval(&self, ctx: node::MetaCtx) -> Vec<node::EvalConf> {
ctx.node(&self.addr)
.map(|n| n.push_eval(ctx))
.unwrap_or_default()
}
fn pull_eval(&self, ctx: node::MetaCtx) -> Vec<node::EvalConf> {
ctx.node(&self.addr)
.map(|n| n.pull_eval(ctx))
.unwrap_or_default()
}
fn stateful(&self, ctx: node::MetaCtx) -> bool {
ctx.node(&self.addr)
.map(|n| n.stateful(ctx))
.unwrap_or(false)
}
fn register(&self, ctx: node::RegCtx<'_, '_>) {
if ctx.node(&self.addr).is_some() {
let (get_node, path, vm) = ctx.into_parts();
let n = (get_node)(&self.addr).unwrap();
n.register(node::RegCtx::new(get_node, path, vm));
}
}
fn inlet(&self, ctx: node::MetaCtx) -> bool {
ctx.node(&self.addr).map(|n| n.inlet(ctx)).unwrap_or(false)
}
fn outlet(&self, ctx: node::MetaCtx) -> bool {
ctx.node(&self.addr).map(|n| n.outlet(ctx)).unwrap_or(false)
}
fn delay(&self, ctx: node::MetaCtx) -> bool {
ctx.node(&self.addr).map(|n| n.delay(ctx)).unwrap_or(false)
}
fn required_addrs(&self) -> Vec<gantz_ca::ContentAddr> {
vec![self.addr]
}
fn visit(&self, ctx: visit::Ctx<'_, '_>, visitor: &mut dyn node::Visitor) {
if let Some(n) = ctx.node(&self.addr) {
n.visit(ctx, visitor);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
fn test_ref() -> Ref {
Ref::new(gantz_ca::ContentAddr::from([0u8; 32]))
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct TestExt {
zeta: bool,
alpha: u32,
}
#[test]
fn ext_free_content_addr_is_pinned() {
assert_eq!(
gantz_ca::content_addr(&test_ref()).to_string(),
"0f0d9b48ea1b758612ee71930395098005e9ae7b37560c48d4763dbfa950f421",
"ext-free Ref CA changed - this breaks every existing graph address",
);
}
#[test]
fn ext_affects_content_addr() {
let plain = test_ref();
let mut extended = test_ref();
extended
.set_ext(
"test.ext",
&TestExt {
zeta: true,
alpha: 3,
},
)
.unwrap();
assert_ne!(
gantz_ca::content_addr(&plain),
gantz_ca::content_addr(&extended)
);
extended.remove_ext("test.ext");
assert_eq!(
gantz_ca::content_addr(&plain),
gantz_ca::content_addr(&extended)
);
}
#[test]
fn ref_roundtrips_through_datum_codec() {
let plain = test_ref();
let d = datum::to_datum(&plain).unwrap();
assert!(
matches!(d, Datum::Str(_)),
"ext-free Ref must stay a bare address, got {d:?}"
);
assert_eq!(datum::from_datum::<Ref>(d).unwrap(), plain);
let mut extended = test_ref();
extended
.set_ext(
"test.ext",
&TestExt {
zeta: true,
alpha: 3,
},
)
.unwrap();
let d = datum::to_datum(&extended).unwrap();
assert!(
matches!(d, Datum::Map(_)),
"ext-carrying Ref must take the map form, got {d:?}"
);
let rt: Ref = datum::from_datum(d).unwrap();
assert_eq!(rt, extended);
assert_eq!(
gantz_ca::content_addr(&rt),
gantz_ca::content_addr(&extended)
);
assert_eq!(
extended.ext("test.ext"),
Some(&Datum::Map(vec![
("alpha".to_string(), Datum::U64(3)),
("zeta".to_string(), Datum::Bool(true)),
])),
);
assert_eq!(
extended.ext_as::<TestExt>("test.ext"),
Some(TestExt {
zeta: true,
alpha: 3
})
);
}
#[test]
fn ref_roundtrips_through_json() {
let mut extended = test_ref();
extended
.set_ext(
"test.ext",
&TestExt {
zeta: false,
alpha: 1,
},
)
.unwrap();
for r in [test_ref(), extended] {
let json = serde_json::to_string(&r).unwrap();
let rt: Ref = serde_json::from_str(&json).unwrap();
assert_eq!(rt, r);
assert_eq!(gantz_ca::content_addr(&rt), gantz_ca::content_addr(&r));
}
}
}