1pub mod filter;
7pub mod mode;
8pub mod profile;
9
10pub use filter::filter_node;
11pub use mode::LoadMode;
12pub use profile::LoadError;
13
14use crate::model::file::AgmFile;
15
16#[must_use]
24pub fn load(file: &AgmFile, mode: LoadMode) -> AgmFile {
25 let nodes = file.nodes.iter().map(|n| filter_node(n, mode)).collect();
26
27 AgmFile {
28 header: file.header.clone(),
29 nodes,
30 }
31}
32
33#[must_use]
42pub fn load_nodes(file: &AgmFile, mode: LoadMode, node_ids: &[&str]) -> AgmFile {
43 let id_set: std::collections::HashSet<&str> = node_ids.iter().copied().collect();
44
45 let nodes = file
46 .nodes
47 .iter()
48 .filter(|n| id_set.contains(n.id.as_str()))
49 .map(|n| filter_node(n, mode))
50 .collect();
51
52 AgmFile {
53 header: file.header.clone(),
54 nodes,
55 }
56}
57
58pub fn load_profile(file: &AgmFile, profile_name: Option<&str>) -> Result<AgmFile, LoadError> {
66 profile::resolve_and_apply(file, profile_name)
67}
68
69#[cfg(test)]
74mod tests {
75 use std::collections::BTreeMap;
76
77 use crate::model::fields::{NodeType, Priority, Span};
78 use crate::model::file::{AgmFile, Header};
79 use crate::model::node::Node;
80
81 use super::*;
82
83 fn minimal_header() -> Header {
84 Header {
85 agm: "1.0".to_owned(),
86 package: "test.pkg".to_owned(),
87 version: "0.1.0".to_owned(),
88 title: None,
89 owner: None,
90 imports: None,
91 default_load: None,
92 description: None,
93 tags: None,
94 status: None,
95 load_profiles: None,
96 target_runtime: None,
97 }
98 }
99
100 fn make_node(id: &str) -> Node {
101 Node {
102 id: id.to_owned(),
103 node_type: NodeType::Facts,
104 summary: format!("node {id}"),
105 priority: Some(Priority::High),
106 stability: None,
107 confidence: None,
108 status: None,
109 depends: None,
110 related_to: None,
111 replaces: None,
112 conflicts: None,
113 see_also: None,
114 items: Some(vec!["item1".to_owned()]),
115 steps: None,
116 fields: None,
117 input: None,
118 output: None,
119 detail: Some("full detail".to_owned()),
120 rationale: None,
121 tradeoffs: None,
122 resolution: None,
123 examples: None,
124 notes: None,
125 code: None,
126 code_blocks: None,
127 verify: None,
128 agent_context: None,
129 target: None,
130 execution_status: None,
131 executed_by: None,
132 executed_at: None,
133 execution_log: None,
134 retry_count: None,
135 parallel_groups: None,
136 memory: None,
137 scope: None,
138 applies_when: None,
139 valid_from: None,
140 valid_until: None,
141 tags: None,
142 aliases: None,
143 keywords: None,
144 extra_fields: BTreeMap::new(),
145 span: Span::new(1, 5),
146 }
147 }
148
149 fn make_file(nodes: Vec<Node>) -> AgmFile {
150 AgmFile {
151 header: minimal_header(),
152 nodes,
153 }
154 }
155
156 #[test]
157 fn test_load_returns_all_nodes_filtered() {
158 let file = make_file(vec![make_node("a"), make_node("b")]);
159 let result = load(&file, LoadMode::Summary);
160 assert_eq!(result.nodes.len(), 2);
161 assert!(result.nodes[0].detail.is_none());
163 assert!(result.nodes[1].detail.is_none());
164 assert!(result.nodes[0].priority.is_some());
166 }
167
168 #[test]
169 fn test_load_nodes_returns_only_requested_ids() {
170 let file = make_file(vec![make_node("a"), make_node("b"), make_node("c")]);
171 let result = load_nodes(&file, LoadMode::Summary, &["a", "c"]);
172 assert_eq!(result.nodes.len(), 2);
173 let ids: Vec<&str> = result.nodes.iter().map(|n| n.id.as_str()).collect();
174 assert!(ids.contains(&"a"));
175 assert!(ids.contains(&"c"));
176 assert!(!ids.contains(&"b"));
177 }
178
179 #[test]
180 fn test_load_nodes_preserves_file_order() {
181 let file = make_file(vec![make_node("x"), make_node("y"), make_node("z")]);
182 let result = load_nodes(&file, LoadMode::Summary, &["z", "x"]);
183 assert_eq!(result.nodes[0].id, "x");
185 assert_eq!(result.nodes[1].id, "z");
186 }
187
188 #[test]
189 fn test_load_nodes_ignores_unknown_ids() {
190 let file = make_file(vec![make_node("a")]);
191 let result = load_nodes(&file, LoadMode::Summary, &["a", "nonexistent"]);
192 assert_eq!(result.nodes.len(), 1);
193 assert_eq!(result.nodes[0].id, "a");
194 }
195}