1use std::collections::HashMap;
2
3pub struct Ctx {
17 store: HashMap<String, String>,
18 log: Vec<String>,
19}
20
21impl Ctx {
22 pub fn new() -> Self {
24 Self {
25 store: HashMap::new(),
26 log: vec![],
27 }
28 }
29
30 pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
32 self.store.insert(key.into(), value.into());
33 }
34
35 pub fn get(&self, key: &str) -> Option<&str> {
37 self.store.get(key).map(|s| s.as_str())
38 }
39
40 pub fn remove(&mut self, key: &str) -> Option<String> {
42 self.store.remove(key)
43 }
44
45 pub fn log(&mut self, msg: impl Into<String>) {
47 self.log.push(msg.into());
48 }
49
50 pub fn logs(&self) -> &[String] {
52 &self.log
53 }
54
55 pub fn clear_logs(&mut self) {
57 self.log.clear();
58 }
59
60 pub fn clear(&mut self) {
62 self.store.clear();
63 self.log.clear();
64 }
65}
66
67impl Default for Ctx {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
80 fn set_then_get() {
81 let mut ctx = Ctx::new();
82 ctx.set("key", "value");
83 assert_eq!(ctx.get("key"), Some("value"));
84 }
85
86 #[test]
87 fn get_missing_key() {
88 let ctx = Ctx::new();
89 assert_eq!(ctx.get("nope"), None);
90 }
91
92 #[test]
93 fn set_overwrites() {
94 let mut ctx = Ctx::new();
95 ctx.set("key", "first");
96 ctx.set("key", "second");
97 assert_eq!(ctx.get("key"), Some("second"));
98 }
99
100 #[test]
101 fn remove_returns_value() {
102 let mut ctx = Ctx::new();
103 ctx.set("key", "value");
104 assert_eq!(ctx.remove("key"), Some("value".to_string()));
105 assert_eq!(ctx.get("key"), None);
106 }
107
108 #[test]
109 fn remove_missing_key() {
110 let mut ctx = Ctx::new();
111 assert_eq!(ctx.remove("nope"), None);
112 }
113
114 #[test]
117 fn log_appends_and_logs_returns_in_order() {
118 let mut ctx = Ctx::new();
119 ctx.log("first");
120 ctx.log("second");
121 ctx.log("third");
122 assert_eq!(ctx.logs(), &["first", "second", "third"]);
123 }
124
125 #[test]
126 fn clear_logs_preserves_store() {
127 let mut ctx = Ctx::new();
128 ctx.set("key", "value");
129 ctx.log("msg");
130 ctx.clear_logs();
131 assert!(ctx.logs().is_empty());
132 assert_eq!(ctx.get("key"), Some("value"));
133 }
134
135 #[test]
136 fn clear_empties_both() {
137 let mut ctx = Ctx::new();
138 ctx.set("key", "value");
139 ctx.log("msg");
140 ctx.clear();
141 assert!(ctx.logs().is_empty());
142 assert_eq!(ctx.get("key"), None);
143 }
144
145 #[test]
146 fn separate_contexts_have_independent_state() {
147 let mut a = Ctx::new();
148 let b = Ctx::new();
149
150 a.set("topic", "rust");
151 a.log("phase 1 done");
152
153 assert_eq!(a.get("topic"), Some("rust"));
154 assert_eq!(b.get("topic"), None);
155 assert_eq!(a.logs(), &["phase 1 done"]);
156 assert!(b.logs().is_empty());
157 }
158}