agm_core/validator/
memory.rs1use crate::error::diagnostic::{AgmError, ErrorLocation};
6use crate::model::node::Node;
7
8#[must_use]
15pub fn validate_memory(node: &Node, file_name: &str) -> Vec<AgmError> {
16 let entries = match &node.memory {
17 Some(e) => e,
18 None => return Vec::new(),
19 };
20
21 let mut errors = Vec::new();
22 let line = node.span.start_line;
23 let id = node.id.as_str();
24 let loc = ErrorLocation::full(file_name, line, id);
25
26 for entry in entries {
27 errors.extend(crate::memory::schema::validate_memory_entry(
28 entry,
29 loc.clone(),
30 ));
31 }
32
33 errors
34}
35
36#[cfg(test)]
37mod tests {
38 use std::collections::BTreeMap;
39
40 use super::*;
41 use crate::error::codes::ErrorCode;
42 use crate::model::fields::{NodeType, Span};
43 use crate::model::memory::{MemoryAction, MemoryEntry};
44 use crate::model::node::Node;
45
46 fn minimal_node() -> Node {
47 Node {
48 id: "test.node".to_owned(),
49 node_type: NodeType::Facts,
50 summary: "a test node".to_owned(),
51 priority: None,
52 stability: None,
53 confidence: None,
54 status: None,
55 depends: None,
56 related_to: None,
57 replaces: None,
58 conflicts: None,
59 see_also: None,
60 items: None,
61 steps: None,
62 fields: None,
63 input: None,
64 output: None,
65 detail: None,
66 rationale: None,
67 tradeoffs: None,
68 resolution: None,
69 examples: None,
70 notes: None,
71 code: None,
72 code_blocks: None,
73 verify: None,
74 agent_context: None,
75 target: None,
76 execution_status: None,
77 executed_by: None,
78 executed_at: None,
79 execution_log: None,
80 retry_count: None,
81 parallel_groups: None,
82 memory: None,
83 scope: None,
84 applies_when: None,
85 valid_from: None,
86 valid_until: None,
87 tags: None,
88 aliases: None,
89 keywords: None,
90 extra_fields: BTreeMap::new(),
91 span: Span::new(5, 7),
92 }
93 }
94
95 fn valid_entry() -> MemoryEntry {
96 MemoryEntry {
97 key: "repo.pattern".to_owned(),
98 topic: "rust.repository".to_owned(),
99 action: MemoryAction::Get,
100 value: None,
101 scope: None,
102 ttl: None,
103 query: None,
104 max_results: None,
105 }
106 }
107
108 #[test]
109 fn test_validate_memory_none_returns_empty() {
110 let node = minimal_node();
111 let errors = validate_memory(&node, "test.agm");
112 assert!(errors.is_empty());
113 }
114
115 #[test]
116 fn test_validate_memory_valid_entry_returns_empty() {
117 let mut node = minimal_node();
118 node.memory = Some(vec![valid_entry()]);
119 let errors = validate_memory(&node, "test.agm");
120 assert!(errors.is_empty());
121 }
122
123 #[test]
124 fn test_validate_memory_invalid_key_uppercase_returns_v022() {
125 let mut node = minimal_node();
126 let mut entry = valid_entry();
127 entry.key = "Repo.Pattern".to_owned();
128 node.memory = Some(vec![entry]);
129 let errors = validate_memory(&node, "test.agm");
130 assert!(errors.iter().any(|e| e.code == ErrorCode::V022));
131 }
132
133 #[test]
134 fn test_validate_memory_invalid_key_special_chars_returns_v022() {
135 let mut node = minimal_node();
136 let mut entry = valid_entry();
137 entry.key = "repo-pattern".to_owned(); node.memory = Some(vec![entry]);
139 let errors = validate_memory(&node, "test.agm");
140 assert!(errors.iter().any(|e| e.code == ErrorCode::V022));
141 }
142
143 #[test]
144 fn test_validate_memory_invalid_key_leading_dot_returns_v022() {
145 let mut node = minimal_node();
146 let mut entry = valid_entry();
147 entry.key = ".repo.pattern".to_owned();
148 node.memory = Some(vec![entry]);
149 let errors = validate_memory(&node, "test.agm");
150 assert!(errors.iter().any(|e| e.code == ErrorCode::V022));
151 }
152
153 #[test]
154 fn test_validate_memory_upsert_no_value_returns_v023() {
155 let mut node = minimal_node();
156 let mut entry = valid_entry();
157 entry.action = MemoryAction::Upsert;
158 entry.value = None;
159 node.memory = Some(vec![entry]);
160 let errors = validate_memory(&node, "test.agm");
161 assert!(errors.iter().any(|e| e.code == ErrorCode::V023));
162 }
163
164 #[test]
165 fn test_validate_memory_upsert_with_value_returns_empty() {
166 let mut node = minimal_node();
167 let mut entry = valid_entry();
168 entry.action = MemoryAction::Upsert;
169 entry.value = Some("the value".to_owned());
170 node.memory = Some(vec![entry]);
171 let errors = validate_memory(&node, "test.agm");
172 assert!(!errors.iter().any(|e| e.code == ErrorCode::V023));
173 }
174
175 #[test]
176 fn test_validate_memory_search_no_query_returns_v023() {
177 let mut node = minimal_node();
178 let mut entry = valid_entry();
179 entry.action = MemoryAction::Search;
180 entry.query = None;
181 node.memory = Some(vec![entry]);
182 let errors = validate_memory(&node, "test.agm");
183 assert!(errors.iter().any(|e| e.code == ErrorCode::V023));
184 }
185
186 #[test]
187 fn test_validate_memory_search_with_query_returns_empty() {
188 let mut node = minimal_node();
189 let mut entry = valid_entry();
190 entry.action = MemoryAction::Search;
191 entry.query = Some("find auth patterns".to_owned());
192 node.memory = Some(vec![entry]);
193 let errors = validate_memory(&node, "test.agm");
194 assert!(!errors.iter().any(|e| e.code == ErrorCode::V023));
195 }
196
197 #[test]
198 fn test_validate_memory_get_no_query_returns_empty() {
199 let mut node = minimal_node();
200 let entry = valid_entry(); node.memory = Some(vec![entry]);
202 let errors = validate_memory(&node, "test.agm");
203 assert!(errors.is_empty());
204 }
205
206 #[test]
207 fn test_validate_memory_invalid_topic_returns_v025() {
208 let mut node = minimal_node();
209 let mut entry = valid_entry();
210 entry.topic = "Rust.Models".to_owned();
211 node.memory = Some(vec![entry]);
212 let errors = validate_memory(&node, "test.agm");
213 assert!(errors.iter().any(|e| e.code == ErrorCode::V025));
214 }
215
216 #[test]
217 fn test_validate_memory_valid_underscore_key_returns_empty() {
218 let mut node = minimal_node();
219 let mut entry = valid_entry();
220 entry.key = "repo.kanban_column.row_mapping_pattern".to_owned();
221 node.memory = Some(vec![entry]);
222 let errors = validate_memory(&node, "test.agm");
223 assert!(errors.is_empty());
224 }
225}