1use crate::address::ContentAddress;
19use crate::archive::Archive;
20use crate::codec::CodecError;
21use crate::definition::Definition;
22
23pub trait RebindTarget {
26 fn address_of(&self, name: &str) -> Option<ContentAddress>;
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum Rebound {
34 Bound(String),
37 Free(String),
40}
41
42impl Rebound {
43 pub fn name(&self) -> &str {
45 match self {
46 Rebound::Bound(n) | Rebound::Free(n) => n,
47 }
48 }
49
50 pub fn is_bound(&self) -> bool {
52 matches!(self, Rebound::Bound(_))
53 }
54}
55
56pub fn rebind_node(node: &Definition, target: &impl RebindTarget) -> Result<Rebound, CodecError> {
58 let node_addr = node.address()?;
59 Ok(match target.address_of(&node.name) {
60 Some(target_addr) if target_addr == node_addr => Rebound::Bound(node.name.clone()),
61 _ => Rebound::Free(node.name.clone()),
62 })
63}
64
65pub fn rebind_nodes(
68 archive: &Archive,
69 target: &impl RebindTarget,
70) -> Result<Vec<Rebound>, CodecError> {
71 archive
72 .nodes
73 .iter()
74 .map(|n| rebind_node(n, target))
75 .collect()
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use std::collections::HashMap;
82
83 struct Known(HashMap<String, ContentAddress>);
85
86 impl RebindTarget for Known {
87 fn address_of(&self, name: &str) -> Option<ContentAddress> {
88 self.0.get(name).copied()
89 }
90 }
91
92 fn node(name: &str, edge_target: &str) -> Definition {
93 Definition {
94 kind: "Concept".into(),
95 name: name.into(),
96 edges: vec![("Subsumption".into(), edge_target.into())],
97 axioms: vec![],
98 lexical: None,
99 }
100 }
101
102 #[test]
103 fn binds_when_name_and_address_agree() {
104 let n = node("Employer", "Agent");
105 let mut known = HashMap::new();
106 known.insert("Employer".to_string(), n.address().unwrap());
107 assert_eq!(
108 rebind_node(&n, &Known(known)).unwrap(),
109 Rebound::Bound("Employer".into())
110 );
111 }
112
113 #[test]
114 fn stays_free_when_unknown() {
115 let n = node("Employer", "Agent");
116 assert!(!rebind_node(&n, &Known(HashMap::new())).unwrap().is_bound());
117 }
118
119 #[test]
120 fn stays_free_on_address_disagreement_even_with_same_name() {
121 let loaded = node("Employer", "Agent");
124 let different = node("Employer", "Person"); let mut known = HashMap::new();
126 known.insert("Employer".to_string(), different.address().unwrap());
127 assert!(!rebind_node(&loaded, &Known(known)).unwrap().is_bound());
128 }
129
130 #[test]
131 fn partial_rebind_keeps_unknowns_free() {
132 let a = node("Employer", "Agent");
133 let b = node("Stranger", "Thing");
134 let mut known = HashMap::new();
135 known.insert("Employer".to_string(), a.address().unwrap());
136 let archive = Archive {
137 nodes: vec![a, b],
138 connections: vec![],
139 };
140 let rebound = rebind_nodes(&archive, &Known(known)).unwrap();
141 let bound: Vec<&str> = rebound
142 .iter()
143 .filter(|r| r.is_bound())
144 .map(|r| r.name())
145 .collect();
146 let free: Vec<&str> = rebound
147 .iter()
148 .filter(|r| !r.is_bound())
149 .map(|r| r.name())
150 .collect();
151 assert_eq!(bound, vec!["Employer"]);
152 assert_eq!(free, vec!["Stranger"]);
153 }
154}