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() -> Result<()> {
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 manifest = load_and_validate_manifest(&manifest_path, true, true)?;
203 assert!(!manifest.sources.is_empty());
204 assert!(!manifest.agents.is_empty());
205 Ok(())
206 }
207
208 #[test]
209 fn test_load_and_validate_manifest_no_sources() -> Result<()> {
210 let temp_dir = tempdir().unwrap();
211 let manifest_path = temp_dir.path().join("agpm.toml");
212
213 let content = r#"
215[agents]
216test-agent = { path = "../local/agent.md" }
217"#;
218 fs::write(&manifest_path, content).unwrap();
219
220 let result = load_and_validate_manifest(&manifest_path, true, false);
222 assert!(result.is_err());
223 assert!(result.unwrap_err().to_string().contains("No sources"));
224
225 load_and_validate_manifest(&manifest_path, false, false)?;
227 Ok(())
228 }
229
230 #[test]
231 fn test_load_and_validate_manifest_no_dependencies() -> Result<()> {
232 let temp_dir = tempdir().unwrap();
233 let manifest_path = temp_dir.path().join("agpm.toml");
234
235 let content = r#"
237[sources]
238test = "https://github.com/test/repo.git"
239"#;
240 fs::write(&manifest_path, content).unwrap();
241
242 let result = load_and_validate_manifest(&manifest_path, false, true);
244 assert!(result.is_err());
245 assert!(result.unwrap_err().to_string().contains("No dependencies"));
246
247 load_and_validate_manifest(&manifest_path, false, false)?;
249 Ok(())
250 }
251
252 #[test]
253 fn test_load_and_validate_manifest_nonexistent() {
254 let temp_dir = tempdir().unwrap();
255 let manifest_path = temp_dir.path().join("nonexistent.toml");
256
257 let result = load_and_validate_manifest(&manifest_path, false, false);
258 assert!(result.is_err());
259 assert!(result.unwrap_err().to_string().contains("not found"));
260 }
261
262 #[test]
263 fn test_load_and_validate_manifest_with_snippets() -> Result<()> {
264 let temp_dir = tempdir().unwrap();
265 let manifest_path = temp_dir.path().join("agpm.toml");
266
267 let content = r#"
269[sources]
270test = "https://github.com/test/repo.git"
271
272[snippets]
273test-snippet = { source = "test", path = "snippet.md", version = "v1.0.0" }
274"#;
275 fs::write(&manifest_path, content).unwrap();
276
277 load_and_validate_manifest(&manifest_path, true, true)?;
279 Ok(())
280 }
281
282 #[test]
283 fn test_load_and_validate_manifest_with_commands() -> Result<()> {
284 let temp_dir = tempdir().unwrap();
285 let manifest_path = temp_dir.path().join("agpm.toml");
286
287 let content = r#"
289[sources]
290test = "https://github.com/test/repo.git"
291
292[commands]
293test-command = { source = "test", path = "command.md", version = "v1.0.0" }
294"#;
295 fs::write(&manifest_path, content).unwrap();
296
297 load_and_validate_manifest(&manifest_path, true, true)?;
299 Ok(())
300 }
301
302 #[test]
303 fn test_load_and_validate_manifest_with_mcp_servers() -> Result<()> {
304 let temp_dir = tempdir().unwrap();
305 let manifest_path = temp_dir.path().join("agpm.toml");
306
307 let content = r#"
309[sources]
310test = "https://github.com/test/repo.git"
311
312[mcp-servers]
313test-server = "../local/mcp-servers/test-server.json"
314"#;
315 fs::write(&manifest_path, content).unwrap();
316
317 load_and_validate_manifest(&manifest_path, true, true)?;
319 Ok(())
320 }
321
322 #[test]
323 fn test_load_project_manifest_valid() -> Result<()> {
324 let temp_dir = tempdir().unwrap();
325 let manifest_path = temp_dir.path().join("agpm.toml");
326
327 let content = r#"
329[sources]
330test = "https://github.com/test/repo.git"
331
332[agents]
333test-agent = { source = "test", path = "agent.md", version = "v1.0.0" }
334"#;
335 fs::write(&manifest_path, content).unwrap();
336
337 let manifest = load_project_manifest(temp_dir.path())?;
338 assert_eq!(manifest.sources.len(), 1);
339 assert_eq!(manifest.agents.len(), 1);
340 Ok(())
341 }
342
343 #[test]
344 fn test_load_and_validate_empty_manifest() -> Result<()> {
345 let temp_dir = tempdir().unwrap();
346 let manifest_path = temp_dir.path().join("agpm.toml");
347
348 let content = "";
350 fs::write(&manifest_path, content).unwrap();
351
352 load_and_validate_manifest(&manifest_path, false, false)?;
354
355 let result = load_and_validate_manifest(&manifest_path, true, false);
357 assert!(result.is_err());
358
359 let result = load_and_validate_manifest(&manifest_path, false, true);
361 assert!(result.is_err());
362 Ok(())
363 }
364
365 #[test]
366 fn test_manifest_validation_mixed_dependencies() -> Result<()> {
367 let temp_dir = tempdir().unwrap();
368 let manifest_path = temp_dir.path().join("agpm.toml");
369
370 let content = r#"
372[sources]
373source1 = "https://github.com/test/repo1.git"
374source2 = "https://github.com/test/repo2.git"
375
376[agents]
377agent1 = { source = "source1", path = "agent1.md", version = "v1.0.0" }
378
379[snippets]
380snippet1 = { source = "source2", path = "snippet1.md", version = "v2.0.0" }
381
382[commands]
383cmd1 = { source = "source1", path = "cmd1.md", version = "v1.0.0" }
384"#;
385 fs::write(&manifest_path, content).unwrap();
386
387 let manifest = load_and_validate_manifest(&manifest_path, true, true)?;
388 assert_eq!(manifest.sources.len(), 2);
389 assert_eq!(manifest.agents.len(), 1);
390 assert_eq!(manifest.snippets.len(), 1);
391 assert_eq!(manifest.commands.len(), 1);
392 Ok(())
393 }
394
395 #[test]
396 fn test_error_context_in_load_project_manifest() {
397 let temp_dir = tempdir().unwrap();
398
399 let result = load_project_manifest(temp_dir.path());
401 assert!(result.is_err());
402
403 let err_chain = result.unwrap_err();
404 let err_str = format!("{:?}", err_chain);
405
406 assert!(err_str.contains("agpm.toml") || err_str.contains("init"));
408 }
409
410 #[test]
411 fn test_error_context_in_validation() {
412 let temp_dir = tempdir().unwrap();
413 let manifest_path = temp_dir.path().join("agpm.toml");
414
415 fs::write(&manifest_path, "").unwrap();
417
418 let result = load_and_validate_manifest(&manifest_path, true, false);
420 assert!(result.is_err());
421
422 let err_chain = result.unwrap_err();
423 let err_str = format!("{:?}", err_chain);
424 assert!(err_str.contains("source") || err_str.contains("No sources"));
425
426 let result = load_and_validate_manifest(&manifest_path, false, true);
428 assert!(result.is_err());
429
430 let err_chain = result.unwrap_err();
431 let err_str = format!("{:?}", err_chain);
432 assert!(err_str.contains("dependencies") || err_str.contains("No dependencies"));
433 }
434}