systemprompt_generator/error/
mod.rs1use std::path::PathBuf;
16
17mod suggestions;
18use suggestions::suggest_fix_for_field;
19
20#[derive(Debug, thiserror::Error)]
21pub enum PublishError {
22 #[error("Missing field '{field}' for content '{slug}'")]
23 MissingField {
24 field: String,
25 slug: String,
26 source_path: Option<PathBuf>,
27 suggestion: Option<String>,
28 },
29
30 #[error("No template for content type '{content_type}'")]
31 TemplateNotFound {
32 content_type: String,
33 slug: String,
34 available_templates: Vec<String>,
35 },
36
37 #[error("Page data provider '{provider_id}' failed: {cause}")]
38 ProviderFailed {
39 provider_id: String,
40 cause: String,
41 suggestion: Option<String>,
42 },
43
44 #[error("Template render failed for '{template_name}'")]
45 RenderFailed {
46 template_name: String,
47 slug: Option<String>,
48 cause: String,
49 },
50
51 #[error("Content fetch failed for source '{source_name}'")]
52 FetchFailed { source_name: String, cause: String },
53
54 #[error("Configuration error: {message}")]
55 Config {
56 message: String,
57 path: Option<String>,
58 },
59
60 #[error("Page prerenderer '{page_type}' failed: {cause}")]
61 PagePrerendererFailed { page_type: String, cause: String },
62
63 #[error("I/O error: {0}")]
64 Io(#[from] std::io::Error),
65
66 #[error("{context}: {source}")]
67 IoContext {
68 context: String,
69 #[source]
70 source: std::io::Error,
71 },
72
73 #[error("YAML error: {0}")]
74 Yaml(#[from] serde_yaml::Error),
75
76 #[error("JSON error: {0}")]
77 Json(#[from] serde_json::Error),
78
79 #[error("Failed to read content config {}: {source}", path.display())]
80 ContentConfigRead {
81 path: PathBuf,
82 #[source]
83 source: std::io::Error,
84 },
85
86 #[error("Failed to parse content config {}: {source}", path.display())]
87 ContentConfigParse {
88 path: PathBuf,
89 #[source]
90 source: serde_yaml::Error,
91 },
92
93 #[error("Failed to load web config: {0}")]
94 WebConfig(#[from] systemprompt_models::WebConfigError),
95
96 #[error("Failed to load global config: {0}")]
97 GlobalConfig(#[from] systemprompt_models::errors::ConfigError),
98
99 #[error("{context}: {source}")]
100 Content {
101 context: String,
102 #[source]
103 source: systemprompt_content::ContentError,
104 },
105
106 #[error("Template registry error: {0}")]
107 Template(#[from] systemprompt_templates::TemplateError),
108
109 #[error("Extension discovery failed: {0}")]
110 ExtensionDiscovery(#[from] systemprompt_extension::LoaderError),
111}
112
113pub type GeneratorResult<T> = Result<T, PublishError>;
114
115impl PublishError {
116 pub fn missing_field(field: impl Into<String>, slug: impl Into<String>) -> Self {
117 let field_str = field.into();
118 Self::MissingField {
119 suggestion: suggest_fix_for_field(&field_str),
120 field: field_str,
121 slug: slug.into(),
122 source_path: None,
123 }
124 }
125
126 pub fn missing_field_with_path(
127 field: impl Into<String>,
128 slug: impl Into<String>,
129 path: PathBuf,
130 ) -> Self {
131 let field_str = field.into();
132 Self::MissingField {
133 suggestion: suggest_fix_for_field(&field_str),
134 field: field_str,
135 slug: slug.into(),
136 source_path: Some(path),
137 }
138 }
139
140 pub fn template_not_found(
141 content_type: impl Into<String>,
142 slug: impl Into<String>,
143 available: Vec<String>,
144 ) -> Self {
145 Self::TemplateNotFound {
146 content_type: content_type.into(),
147 slug: slug.into(),
148 available_templates: available,
149 }
150 }
151
152 pub fn provider_failed(provider_id: impl Into<String>, cause: impl Into<String>) -> Self {
153 Self::ProviderFailed {
154 provider_id: provider_id.into(),
155 cause: cause.into(),
156 suggestion: None,
157 }
158 }
159
160 pub fn render_failed(
161 template_name: impl Into<String>,
162 slug: Option<String>,
163 cause: impl Into<String>,
164 ) -> Self {
165 Self::RenderFailed {
166 template_name: template_name.into(),
167 slug,
168 cause: cause.into(),
169 }
170 }
171
172 pub fn fetch_failed(source_name: impl Into<String>, cause: impl Into<String>) -> Self {
173 Self::FetchFailed {
174 source_name: source_name.into(),
175 cause: cause.into(),
176 }
177 }
178
179 pub fn config(message: impl Into<String>) -> Self {
180 Self::Config {
181 message: message.into(),
182 path: None,
183 }
184 }
185
186 pub fn page_prerenderer_failed(page_type: impl Into<String>, cause: impl Into<String>) -> Self {
187 Self::PagePrerendererFailed {
188 page_type: page_type.into(),
189 cause: cause.into(),
190 }
191 }
192
193 pub fn io_context(context: impl Into<String>, source: std::io::Error) -> Self {
194 Self::IoContext {
195 context: context.into(),
196 source,
197 }
198 }
199
200 pub fn content(
201 context: impl Into<String>,
202 source: impl Into<systemprompt_content::ContentError>,
203 ) -> Self {
204 Self::Content {
205 context: context.into(),
206 source: source.into(),
207 }
208 }
209
210 pub fn location(&self) -> Option<String> {
211 match self {
212 Self::MissingField { source_path, .. } => {
213 source_path.as_ref().map(|p| p.display().to_string())
214 },
215 Self::Config { path, .. } => path.clone(),
216 _ => None,
217 }
218 }
219
220 pub fn suggestion_string(&self) -> Option<String> {
221 match self {
222 Self::MissingField { suggestion, .. } | Self::ProviderFailed { suggestion, .. } => {
223 suggestion.clone()
224 },
225 Self::TemplateNotFound {
226 available_templates,
227 content_type,
228 ..
229 } => {
230 if available_templates.is_empty() {
231 Some("Add templates to the templates directory".to_owned())
232 } else {
233 Some(format!(
234 "Change content type from '{}' to one of: {}",
235 content_type,
236 available_templates.join(", ")
237 ))
238 }
239 },
240 _ => None,
241 }
242 }
243
244 pub fn cause_string(&self) -> Option<String> {
245 match self {
246 Self::ProviderFailed { cause, .. }
247 | Self::RenderFailed { cause, .. }
248 | Self::FetchFailed { cause, .. }
249 | Self::PagePrerendererFailed { cause, .. } => Some(cause.clone()),
250 _ => None,
251 }
252 }
253}