1use crate::OutputFormat;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
5pub enum Severity {
6 Error,
7 Warning,
8 Note,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
13pub struct Location {
14 pub file: String, pub line: u32,
16 pub col: u32,
17}
18
19#[derive(Debug, Clone, serde::Serialize)]
21pub struct Diagnostic {
22 pub severity: Severity,
23 pub code: Option<String>,
24 pub message: String,
25 pub primary: Option<Location>,
26 pub related: Vec<Location>,
27 pub hint: Option<String>,
28}
29
30impl Diagnostic {
31 pub fn new(severity: Severity, message: String) -> Self {
33 Self {
34 severity,
35 code: None,
36 message,
37 primary: None,
38 related: Vec::new(),
39 hint: None,
40 }
41 }
42
43 pub fn with_code(mut self, code: String) -> Self {
45 self.code = Some(code);
46 self
47 }
48
49 pub fn with_location(mut self, location: Location) -> Self {
51 self.primary = Some(location);
52 self
53 }
54
55 pub fn with_related(mut self, location: Location) -> Self {
57 self.related.push(location);
58 self
59 }
60
61 pub fn with_hint(mut self, hint: String) -> Self {
63 self.hint = Some(hint);
64 self
65 }
66
67 pub fn fmt_pretty(&self) -> String {
69 let mut result = format!(
70 "[{}] {}",
71 match self.severity {
72 Severity::Error => "ERROR",
73 Severity::Warning => "WARN",
74 Severity::Note => "NOTE",
75 },
76 self.message
77 );
78
79 if let Some(ref code) = self.code {
80 result.push_str(&format!(" ({})", code));
81 }
82
83 if let Some(ref loc) = self.primary {
84 result.push_str(&format!(" at {}:{}:{}", loc.file, loc.line, loc.col));
85 }
86
87 if let Some(ref hint) = self.hint {
88 result.push_str(&format!("\n hint: {}", hint));
89 }
90
91 result
92 }
93}
94
95#[derive(thiserror::Error, Debug)]
97pub enum RenderError {
98 #[error("Engine creation failed")]
99 EngineCreation {
100 diag: Diagnostic,
101 #[source]
102 source: Option<anyhow::Error>,
103 },
104
105 #[error("Invalid YAML frontmatter")]
106 InvalidFrontmatter {
107 diag: Diagnostic,
108 #[source]
109 source: Option<anyhow::Error>,
110 },
111
112 #[error("Template rendering failed")]
113 TemplateFailed {
114 #[source]
115 source: minijinja::Error,
116 diag: Diagnostic,
117 },
118
119 #[error("Backend compilation failed with {0} error(s)")]
120 CompilationFailed(usize, Vec<Diagnostic>),
121
122 #[error("{format:?} not supported by {backend}")]
123 FormatNotSupported {
124 backend: String,
125 format: OutputFormat,
126 },
127
128 #[error("Unsupported backend: {0}")]
129 UnsupportedBackend(String),
130
131 #[error("Dynamic asset collision: {filename}")]
132 DynamicAssetCollision { filename: String, message: String },
133
134 #[error(transparent)]
135 Internal(#[from] anyhow::Error),
136
137 #[error("{0}")]
138 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
139
140 #[error("Template error: {0}")]
141 Template(#[from] crate::templating::TemplateError),
142}
143
144#[derive(Debug)]
146pub struct RenderResult {
147 pub artifacts: Vec<crate::Artifact>,
148 pub warnings: Vec<Diagnostic>,
149}
150
151impl RenderResult {
152 pub fn new(artifacts: Vec<crate::Artifact>) -> Self {
154 Self {
155 artifacts,
156 warnings: Vec::new(),
157 }
158 }
159
160 pub fn with_warning(mut self, warning: Diagnostic) -> Self {
162 self.warnings.push(warning);
163 self
164 }
165}
166
167impl From<minijinja::Error> for RenderError {
169 fn from(e: minijinja::Error) -> Self {
170 let loc = e.line().map(|line| Location {
171 file: e.name().unwrap_or("template").to_string(),
172 line: line as u32,
173 col: 0, });
175
176 let diag = Diagnostic {
177 severity: Severity::Error,
178 code: Some(format!("minijinja::{:?}", e.kind())),
179 message: e.to_string(),
180 primary: loc,
181 related: vec![],
182 hint: None,
183 };
184
185 RenderError::TemplateFailed { source: e, diag }
186 }
187}
188
189pub fn print_errors(err: &RenderError) {
191 match err {
192 RenderError::CompilationFailed(_, diags) => {
193 for d in diags {
194 eprintln!("{}", d.fmt_pretty());
195 }
196 }
197 RenderError::TemplateFailed { diag, .. } => eprintln!("{}", diag.fmt_pretty()),
198 RenderError::InvalidFrontmatter { diag, .. } => eprintln!("{}", diag.fmt_pretty()),
199 RenderError::EngineCreation { diag, .. } => eprintln!("{}", diag.fmt_pretty()),
200 _ => eprintln!("{}", err),
201 }
202}