1use crate::core::error::ErrorContext;
7use crate::manifest::Manifest;
8use anyhow::{Context, Result, anyhow};
9use std::path::Path;
10
11pub fn load_project_manifest(project_dir: &Path) -> Result<Manifest> {
39 let manifest_path = project_dir.join("agpm.toml");
40
41 if !manifest_path.exists() {
42 return Err(anyhow!("No agpm.toml found in {}", project_dir.display()).context(
43 ErrorContext {
44 error: crate::core::AgpmError::ManifestNotFound,
45 suggestion: Some("Run 'agpm init' to create a new project".to_string()),
46 details: Some(format!("Expected manifest at: {}", manifest_path.display())),
47 },
48 ));
49 }
50
51 Manifest::load(&manifest_path).with_context(|| ErrorContext {
52 error: crate::core::AgpmError::ManifestParseError {
53 file: manifest_path.display().to_string(),
54 reason: "Failed to parse manifest".to_string(),
55 },
56 suggestion: Some("Check that agpm.toml is valid TOML syntax".to_string()),
57 details: Some(format!("Manifest path: {}", manifest_path.display())),
58 })
59}
60
61pub fn load_and_validate_manifest(
77 manifest_path: &Path,
78 require_sources: bool,
79 require_dependencies: bool,
80) -> Result<Manifest> {
81 if !manifest_path.exists() {
82 return Err(anyhow!("Manifest file not found: {}", manifest_path.display()));
83 }
84
85 let manifest = Manifest::load(manifest_path)?;
86
87 if require_sources && manifest.sources.is_empty() {
88 return Err(anyhow!("No sources defined in manifest").context(ErrorContext {
89 error: crate::core::AgpmError::ManifestValidationError {
90 reason: "No sources defined in manifest".to_string(),
91 },
92 suggestion: Some("Add at least one source using 'agpm add source'".to_string()),
93 details: None,
94 }));
95 }
96
97 if require_dependencies
98 && (manifest.agents.is_empty()
99 && manifest.snippets.is_empty()
100 && manifest.commands.is_empty()
101 && manifest.mcp_servers.is_empty())
102 {
103 return Err(anyhow!("No dependencies defined in manifest").context(ErrorContext {
104 error: crate::core::AgpmError::ManifestValidationError {
105 reason: "No dependencies defined in manifest".to_string(),
106 },
107 suggestion: Some("Add dependencies using 'agpm add dep'".to_string()),
108 details: None,
109 }));
110 }
111
112 Ok(manifest)
113}
114
115pub fn manifest_exists(project_dir: &Path) -> bool {
125 project_dir.join("agpm.toml").exists()
126}
127
128pub fn manifest_path(project_dir: &Path) -> std::path::PathBuf {
138 project_dir.join("agpm.toml")
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::fs;
145 use tempfile::tempdir;
146
147 #[test]
148 fn test_load_project_manifest_missing() {
149 let temp_dir = tempdir().unwrap();
150 let result = load_project_manifest(temp_dir.path());
151 assert!(result.is_err());
152 let err = result.unwrap_err();
154 let err_str = err.to_string();
155 assert!(err_str.contains("agpm.toml") || err_str.contains("Manifest"));
156 }
157
158 #[test]
159 fn test_load_project_manifest_invalid() {
160 let temp_dir = tempdir().unwrap();
161 let manifest_path = temp_dir.path().join("agpm.toml");
162 fs::write(&manifest_path, "invalid toml {").unwrap();
163
164 let result = load_project_manifest(temp_dir.path());
165 assert!(result.is_err());
166 }
168
169 #[test]
170 fn test_manifest_exists() {
171 let temp_dir = tempdir().unwrap();
172 assert!(!manifest_exists(temp_dir.path()));
173
174 let manifest_path = temp_dir.path().join("agpm.toml");
175 fs::write(&manifest_path, "[sources]").unwrap();
176 assert!(manifest_exists(temp_dir.path()));
177 }
178
179 #[test]
180 fn test_manifest_path() {
181 let temp_dir = tempdir().unwrap();
182 let path = manifest_path(temp_dir.path());
183 assert_eq!(path, temp_dir.path().join("agpm.toml"));
184 }
185
186 #[test]
187 fn test_load_and_validate_manifest_success() {
188 let temp_dir = tempdir().unwrap();
189 let manifest_path = temp_dir.path().join("agpm.toml");
190
191 let content = r#"
193[sources]
194test = "https://github.com/test/repo.git"
195
196[agents]
197test-agent = { source = "test", path = "agent.md", version = "v1.0.0" }
198"#;
199 fs::write(&manifest_path, content).unwrap();
200
201 let result = load_and_validate_manifest(&manifest_path, true, true);
203 assert!(result.is_ok());
204
205 let manifest = result.unwrap();
206 assert!(!manifest.sources.is_empty());
207 assert!(!manifest.agents.is_empty());
208 }
209
210 #[test]
211 fn test_load_and_validate_manifest_no_sources() {
212 let temp_dir = tempdir().unwrap();
213 let manifest_path = temp_dir.path().join("agpm.toml");
214
215 let content = r#"
217[agents]
218test-agent = { path = "../local/agent.md" }
219"#;
220 fs::write(&manifest_path, content).unwrap();
221
222 let result = load_and_validate_manifest(&manifest_path, true, false);
224 assert!(result.is_err());
225 assert!(result.unwrap_err().to_string().contains("No sources"));
226
227 let result = load_and_validate_manifest(&manifest_path, false, false);
229 assert!(result.is_ok());
230 }
231
232 #[test]
233 fn test_load_and_validate_manifest_no_dependencies() {
234 let temp_dir = tempdir().unwrap();
235 let manifest_path = temp_dir.path().join("agpm.toml");
236
237 let content = r#"
239[sources]
240test = "https://github.com/test/repo.git"
241"#;
242 fs::write(&manifest_path, content).unwrap();
243
244 let result = load_and_validate_manifest(&manifest_path, false, true);
246 assert!(result.is_err());
247 assert!(result.unwrap_err().to_string().contains("No dependencies"));
248
249 let result = load_and_validate_manifest(&manifest_path, false, false);
251 assert!(result.is_ok());
252 }
253
254 #[test]
255 fn test_load_and_validate_manifest_nonexistent() {
256 let temp_dir = tempdir().unwrap();
257 let manifest_path = temp_dir.path().join("nonexistent.toml");
258
259 let result = load_and_validate_manifest(&manifest_path, false, false);
260 assert!(result.is_err());
261 assert!(result.unwrap_err().to_string().contains("not found"));
262 }
263
264 #[test]
265 fn test_load_and_validate_manifest_with_snippets() {
266 let temp_dir = tempdir().unwrap();
267 let manifest_path = temp_dir.path().join("agpm.toml");
268
269 let content = r#"
271[sources]
272test = "https://github.com/test/repo.git"
273
274[snippets]
275test-snippet = { source = "test", path = "snippet.md", version = "v1.0.0" }
276"#;
277 fs::write(&manifest_path, content).unwrap();
278
279 let result = load_and_validate_manifest(&manifest_path, true, true);
281 assert!(result.is_ok());
282 }
283
284 #[test]
285 fn test_load_and_validate_manifest_with_commands() {
286 let temp_dir = tempdir().unwrap();
287 let manifest_path = temp_dir.path().join("agpm.toml");
288
289 let content = r#"
291[sources]
292test = "https://github.com/test/repo.git"
293
294[commands]
295test-command = { source = "test", path = "command.md", version = "v1.0.0" }
296"#;
297 fs::write(&manifest_path, content).unwrap();
298
299 let result = load_and_validate_manifest(&manifest_path, true, true);
301 assert!(result.is_ok());
302 }
303
304 #[test]
305 fn test_load_and_validate_manifest_with_mcp_servers() {
306 let temp_dir = tempdir().unwrap();
307 let manifest_path = temp_dir.path().join("agpm.toml");
308
309 let content = r#"
311[sources]
312test = "https://github.com/test/repo.git"
313
314[mcp-servers]
315test-server = "../local/mcp-servers/test-server.json"
316"#;
317 fs::write(&manifest_path, content).unwrap();
318
319 let result = load_and_validate_manifest(&manifest_path, true, true);
321 assert!(result.is_ok());
322 }
323
324 #[test]
325 fn test_load_project_manifest_valid() {
326 let temp_dir = tempdir().unwrap();
327 let manifest_path = temp_dir.path().join("agpm.toml");
328
329 let content = r#"
331[sources]
332test = "https://github.com/test/repo.git"
333
334[agents]
335test-agent = { source = "test", path = "agent.md", version = "v1.0.0" }
336"#;
337 fs::write(&manifest_path, content).unwrap();
338
339 let result = load_project_manifest(temp_dir.path());
340 assert!(result.is_ok());
341
342 let manifest = result.unwrap();
343 assert_eq!(manifest.sources.len(), 1);
344 assert_eq!(manifest.agents.len(), 1);
345 }
346
347 #[test]
348 fn test_load_and_validate_empty_manifest() {
349 let temp_dir = tempdir().unwrap();
350 let manifest_path = temp_dir.path().join("agpm.toml");
351
352 let content = "";
354 fs::write(&manifest_path, content).unwrap();
355
356 let result = load_and_validate_manifest(&manifest_path, false, false);
358 assert!(result.is_ok());
359
360 let result = load_and_validate_manifest(&manifest_path, true, false);
362 assert!(result.is_err());
363
364 let result = load_and_validate_manifest(&manifest_path, false, true);
366 assert!(result.is_err());
367 }
368
369 #[test]
370 fn test_manifest_validation_mixed_dependencies() {
371 let temp_dir = tempdir().unwrap();
372 let manifest_path = temp_dir.path().join("agpm.toml");
373
374 let content = r#"
376[sources]
377source1 = "https://github.com/test/repo1.git"
378source2 = "https://github.com/test/repo2.git"
379
380[agents]
381agent1 = { source = "source1", path = "agent1.md", version = "v1.0.0" }
382
383[snippets]
384snippet1 = { source = "source2", path = "snippet1.md", version = "v2.0.0" }
385
386[commands]
387cmd1 = { source = "source1", path = "cmd1.md", version = "v1.0.0" }
388"#;
389 fs::write(&manifest_path, content).unwrap();
390
391 let result = load_and_validate_manifest(&manifest_path, true, true);
392 assert!(result.is_ok());
393
394 let manifest = result.unwrap();
395 assert_eq!(manifest.sources.len(), 2);
396 assert_eq!(manifest.agents.len(), 1);
397 assert_eq!(manifest.snippets.len(), 1);
398 assert_eq!(manifest.commands.len(), 1);
399 }
400
401 #[test]
402 fn test_error_context_in_load_project_manifest() {
403 let temp_dir = tempdir().unwrap();
404
405 let result = load_project_manifest(temp_dir.path());
407 assert!(result.is_err());
408
409 let err_chain = result.unwrap_err();
410 let err_str = format!("{:?}", err_chain);
411
412 assert!(err_str.contains("agpm.toml") || err_str.contains("init"));
414 }
415
416 #[test]
417 fn test_error_context_in_validation() {
418 let temp_dir = tempdir().unwrap();
419 let manifest_path = temp_dir.path().join("agpm.toml");
420
421 fs::write(&manifest_path, "").unwrap();
423
424 let result = load_and_validate_manifest(&manifest_path, true, false);
426 assert!(result.is_err());
427
428 let err_chain = result.unwrap_err();
429 let err_str = format!("{:?}", err_chain);
430 assert!(err_str.contains("source") || err_str.contains("No sources"));
431
432 let result = load_and_validate_manifest(&manifest_path, false, true);
434 assert!(result.is_err());
435
436 let err_chain = result.unwrap_err();
437 let err_str = format!("{:?}", err_chain);
438 assert!(err_str.contains("dependencies") || err_str.contains("No dependencies"));
439 }
440}