solverforge_core/
entity_context.rs1use std::cell::RefCell;
14use std::collections::HashMap;
15
16use crate::Value;
17
18thread_local! {
19 static REGISTRY: RefCell<Option<HashMap<(String, String), Value>>> = const { RefCell::new(None) };
20}
21
22pub struct EntityContextGuard;
27
28impl EntityContextGuard {
29 pub fn new() -> Self {
31 REGISTRY.with(|r| *r.borrow_mut() = Some(HashMap::new()));
32 Self
33 }
34}
35
36impl Default for EntityContextGuard {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl Drop for EntityContextGuard {
43 fn drop(&mut self) {
44 REGISTRY.with(|r| *r.borrow_mut() = None);
45 }
46}
47
48pub fn register_canonical(type_name: &str, planning_id: &Value, entity: Value) {
58 REGISTRY.with(|r| {
59 if let Some(ref mut map) = *r.borrow_mut() {
60 map.insert((type_name.to_string(), id_to_string(planning_id)), entity);
61 }
62 });
63}
64
65pub fn lookup_canonical(type_name: &str, planning_id: &Value) -> Option<Value> {
77 REGISTRY.with(|r| {
78 r.borrow().as_ref().and_then(|map| {
79 map.get(&(type_name.to_string(), id_to_string(planning_id)))
80 .cloned()
81 })
82 })
83}
84
85fn id_to_string(id: &Value) -> String {
87 match id {
88 Value::String(s) => s.clone(),
89 Value::Int(i) => i.to_string(),
90 Value::Float(f) => f.to_string(),
91 Value::Decimal(d) => d.to_string(),
92 Value::Bool(b) => b.to_string(),
93 _ => format!("{:?}", id),
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_register_and_lookup() {
103 let _guard = EntityContextGuard::new();
104
105 let entity = Value::Object(
106 [("id".to_string(), Value::String("e1".to_string()))]
107 .into_iter()
108 .collect(),
109 );
110 let id = Value::String("e1".to_string());
111
112 register_canonical("TestEntity", &id, entity.clone());
113
114 let found = lookup_canonical("TestEntity", &id);
115 assert!(found.is_some());
116 assert_eq!(found.unwrap(), entity);
117 }
118
119 #[test]
120 fn test_lookup_without_context_returns_none() {
121 let id = Value::String("e1".to_string());
122 let found = lookup_canonical("TestEntity", &id);
123 assert!(found.is_none());
124 }
125
126 #[test]
127 fn test_lookup_wrong_type_returns_none() {
128 let _guard = EntityContextGuard::new();
129
130 let entity = Value::Object(
131 [("id".to_string(), Value::String("e1".to_string()))]
132 .into_iter()
133 .collect(),
134 );
135 let id = Value::String("e1".to_string());
136
137 register_canonical("TypeA", &id, entity);
138
139 let found = lookup_canonical("TypeB", &id);
140 assert!(found.is_none());
141 }
142
143 #[test]
144 fn test_context_cleanup_on_drop() {
145 let id = Value::String("e1".to_string());
146 let entity = Value::Object(
147 [("id".to_string(), Value::String("e1".to_string()))]
148 .into_iter()
149 .collect(),
150 );
151
152 {
153 let _guard = EntityContextGuard::new();
154 register_canonical("TestEntity", &id, entity);
155 assert!(lookup_canonical("TestEntity", &id).is_some());
156 }
157
158 assert!(lookup_canonical("TestEntity", &id).is_none());
160 }
161
162 #[test]
163 fn test_int_id() {
164 let _guard = EntityContextGuard::new();
165
166 let entity = Value::Object([("id".to_string(), Value::Int(42))].into_iter().collect());
167 let id = Value::Int(42);
168
169 register_canonical("TestEntity", &id, entity.clone());
170
171 let found = lookup_canonical("TestEntity", &id);
172 assert!(found.is_some());
173 assert_eq!(found.unwrap(), entity);
174 }
175
176 #[test]
177 fn test_large_int_id() {
178 let _guard = EntityContextGuard::new();
179
180 let entity = Value::Object(
181 [("id".to_string(), Value::Int(123456789))]
182 .into_iter()
183 .collect(),
184 );
185 let id = Value::Int(123456789);
186
187 register_canonical("TestEntity", &id, entity.clone());
188
189 let found = lookup_canonical("TestEntity", &id);
190 assert!(found.is_some());
191 assert_eq!(found.unwrap(), entity);
192 }
193}