use crate::address::ContentAddress;
use crate::archive::Archive;
use crate::codec::CodecError;
use crate::definition::Definition;
pub trait RebindTarget {
fn address_of(&self, name: &str) -> Option<ContentAddress>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Rebound {
Bound(String),
Free(String),
}
impl Rebound {
pub fn name(&self) -> &str {
match self {
Rebound::Bound(n) | Rebound::Free(n) => n,
}
}
pub fn is_bound(&self) -> bool {
matches!(self, Rebound::Bound(_))
}
}
pub fn rebind_node(node: &Definition, target: &impl RebindTarget) -> Result<Rebound, CodecError> {
let node_addr = node.address()?;
Ok(match target.address_of(&node.name) {
Some(target_addr) if target_addr == node_addr => Rebound::Bound(node.name.clone()),
_ => Rebound::Free(node.name.clone()),
})
}
pub fn rebind_nodes(
archive: &Archive,
target: &impl RebindTarget,
) -> Result<Vec<Rebound>, CodecError> {
archive
.nodes
.iter()
.map(|n| rebind_node(n, target))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct Known(HashMap<String, ContentAddress>);
impl RebindTarget for Known {
fn address_of(&self, name: &str) -> Option<ContentAddress> {
self.0.get(name).copied()
}
}
fn node(name: &str, edge_target: &str) -> Definition {
Definition {
kind: "Concept".into(),
name: name.into(),
edges: vec![("Subsumption".into(), edge_target.into())],
axioms: vec![],
lexical: None,
}
}
#[test]
fn binds_when_name_and_address_agree() {
let n = node("Employer", "Agent");
let mut known = HashMap::new();
known.insert("Employer".to_string(), n.address().unwrap());
assert_eq!(
rebind_node(&n, &Known(known)).unwrap(),
Rebound::Bound("Employer".into())
);
}
#[test]
fn stays_free_when_unknown() {
let n = node("Employer", "Agent");
assert!(!rebind_node(&n, &Known(HashMap::new())).unwrap().is_bound());
}
#[test]
fn stays_free_on_address_disagreement_even_with_same_name() {
let loaded = node("Employer", "Agent");
let different = node("Employer", "Person"); let mut known = HashMap::new();
known.insert("Employer".to_string(), different.address().unwrap());
assert!(!rebind_node(&loaded, &Known(known)).unwrap().is_bound());
}
#[test]
fn partial_rebind_keeps_unknowns_free() {
let a = node("Employer", "Agent");
let b = node("Stranger", "Thing");
let mut known = HashMap::new();
known.insert("Employer".to_string(), a.address().unwrap());
let archive = Archive {
nodes: vec![a, b],
connections: vec![],
};
let rebound = rebind_nodes(&archive, &Known(known)).unwrap();
let bound: Vec<&str> = rebound
.iter()
.filter(|r| r.is_bound())
.map(|r| r.name())
.collect();
let free: Vec<&str> = rebound
.iter()
.filter(|r| !r.is_bound())
.map(|r| r.name())
.collect();
assert_eq!(bound, vec!["Employer"]);
assert_eq!(free, vec!["Stranger"]);
}
}