1use std::path::PathBuf;
9
10use thiserror::Error;
11
12#[derive(Debug, Error)]
14pub enum ManifestError {
15 #[error("agents.yml io error{path}: {source}", path = fmt_path(.path.as_ref()))]
17 Io {
18 #[source]
19 source: std::io::Error,
20 path: Option<PathBuf>,
21 },
22 #[error("agents.yml is not valid YAML{path}: {source}", path = fmt_path(.path.as_ref()))]
24 ParseYaml {
25 #[source]
26 source: serde_yaml_ng::Error,
27 path: Option<PathBuf>,
28 },
29 #[error("agents.yml failed schema validation{path}: {message}", path = fmt_path(.path.as_ref()))]
31 Schema {
32 message: String,
33 path: Option<PathBuf>,
34 },
35}
36
37impl ManifestError {
38 #[must_use]
39 pub fn with_path(mut self, p: impl Into<PathBuf>) -> Self {
40 let new_path = p.into();
41 match &mut self {
42 Self::Io { path, .. } | Self::ParseYaml { path, .. } | Self::Schema { path, .. } => {
43 *path = Some(new_path);
44 }
45 }
46 self
47 }
48
49 pub const fn path(&self) -> Option<&PathBuf> {
50 match self {
51 Self::Io { path, .. } | Self::ParseYaml { path, .. } | Self::Schema { path, .. } => {
52 path.as_ref()
53 }
54 }
55 }
56}
57
58#[derive(Debug, Error)]
60pub enum LockfileError {
61 #[error("agents.lock io error{path}: {source}", path = fmt_path(.path.as_ref()))]
67 Io {
68 #[source]
69 source: std::io::Error,
70 path: Option<PathBuf>,
71 },
72 #[error("agents.lock is not valid JSON{path}: {source}", path = fmt_path(.path.as_ref()))]
74 ParseJson {
75 #[source]
76 source: serde_json::Error,
77 path: Option<PathBuf>,
78 },
79 #[error("agents.lock failed schema validation{path}: {message}", path = fmt_path(.path.as_ref()))]
81 Schema {
82 message: String,
83 path: Option<PathBuf>,
84 },
85}
86
87impl LockfileError {
88 #[must_use]
89 pub fn with_path(mut self, p: impl Into<PathBuf>) -> Self {
90 let new_path = p.into();
91 match &mut self {
92 Self::Io { path, .. } | Self::ParseJson { path, .. } | Self::Schema { path, .. } => {
93 *path = Some(new_path);
94 }
95 }
96 self
97 }
98
99 pub const fn path(&self) -> Option<&PathBuf> {
100 match self {
101 Self::Io { path, .. } | Self::ParseJson { path, .. } | Self::Schema { path, .. } => {
102 path.as_ref()
103 }
104 }
105 }
106}
107
108fn fmt_path(p: Option<&PathBuf>) -> String {
116 p.map_or_else(String::new, |path| format!(" at {}", redact(path)))
117}
118
119fn redact(path: &std::path::Path) -> String {
120 if let Ok(cwd) = std::env::current_dir() {
121 if let Ok(rel) = path.strip_prefix(&cwd) {
122 return rel.to_string_lossy().replace('\\', "/");
123 }
124 }
125 path.file_name().map_or_else(
126 || path.display().to_string(),
127 |n| n.to_string_lossy().into_owned(),
128 )
129}