1use crate::{SourceSpan, detect::DetectTypeError};
2
3pub type Result<T> = std::result::Result<T, Error>;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ParseDiagnosticSpanKind {
7 Exact,
8 InsertionPoint,
9 Fallback,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ParseDiagnostic {
19 message: String,
20 span: Option<SourceSpan>,
21 span_kind: ParseDiagnosticSpanKind,
22 code: Option<String>,
23}
24
25#[cfg(any(
26 feature = "diagram-class",
27 feature = "diagram-er",
28 feature = "diagram-flowchart",
29 feature = "diagram-swimlane",
30 feature = "diagram-sequence",
31 feature = "diagram-state"
32))]
33pub(crate) trait ParseErrorSourceSpan {
34 fn source_span(&self) -> Option<SourceSpan>;
35}
36
37#[cfg(any(
38 feature = "diagram-class",
39 feature = "diagram-er",
40 feature = "diagram-flowchart",
41 feature = "diagram-swimlane",
42 feature = "diagram-sequence",
43 feature = "diagram-state"
44))]
45impl ParseErrorSourceSpan for String {
46 fn source_span(&self) -> Option<SourceSpan> {
47 None
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::{Error, ParseDiagnostic, ParseDiagnosticSpanKind};
54 use crate::SourceSpan;
55
56 #[test]
57 fn diagram_parse_fallback_builds_structured_fallback_diagnostic() {
58 let error = Error::diagram_parse_fallback("flowchart", "bad syntax");
59 let display = error.to_string();
60
61 let Error::DiagramParse {
62 diagram_type,
63 diagnostic,
64 } = error
65 else {
66 panic!("expected diagram parse error");
67 };
68
69 assert_eq!(diagram_type, "flowchart");
70 assert_eq!(diagnostic.message(), "bad syntax");
71 assert_eq!(diagnostic.span(), None);
72 assert_eq!(diagnostic.span_kind(), ParseDiagnosticSpanKind::Fallback);
73 assert_eq!(diagnostic.code(), None);
74 assert_eq!(display, "Diagram parse error (flowchart): bad syntax");
75 }
76
77 #[test]
78 fn diagram_parse_diagnostic_preserves_structured_metadata() {
79 let span = SourceSpan::new(2, 5);
80 let error = Error::diagram_parse_diagnostic(
81 "state",
82 ParseDiagnostic::new("unexpected token")
83 .with_span(span, ParseDiagnosticSpanKind::Exact)
84 .with_code("merman.test"),
85 );
86
87 let Error::DiagramParse {
88 diagram_type,
89 diagnostic,
90 } = error
91 else {
92 panic!("expected diagram parse error");
93 };
94
95 assert_eq!(diagram_type, "state");
96 assert_eq!(diagnostic.message(), "unexpected token");
97 assert_eq!(diagnostic.span(), Some(span));
98 assert_eq!(diagnostic.span_kind(), ParseDiagnosticSpanKind::Exact);
99 assert_eq!(diagnostic.code(), Some("merman.test"));
100 }
101}
102
103impl ParseDiagnostic {
104 pub fn new(message: impl Into<String>) -> Self {
105 Self {
106 message: message.into(),
107 span: None,
108 span_kind: ParseDiagnosticSpanKind::Fallback,
109 code: None,
110 }
111 }
112
113 pub fn with_span(mut self, span: SourceSpan, span_kind: ParseDiagnosticSpanKind) -> Self {
114 self.span = Some(span);
115 self.span_kind = span_kind;
116 self
117 }
118
119 pub fn with_code(mut self, code: impl Into<String>) -> Self {
120 self.code = Some(code.into());
121 self
122 }
123
124 pub fn map_span(mut self, map: impl FnOnce(SourceSpan) -> SourceSpan) -> Self {
125 self.span = self.span.map(map);
126 self
127 }
128
129 pub(crate) fn without_span(mut self) -> Self {
130 self.span = None;
131 self.span_kind = ParseDiagnosticSpanKind::Fallback;
132 self
133 }
134
135 pub fn message(&self) -> &str {
136 &self.message
137 }
138
139 pub fn span(&self) -> Option<SourceSpan> {
140 self.span
141 }
142
143 pub fn span_kind(&self) -> ParseDiagnosticSpanKind {
144 self.span_kind
145 }
146
147 pub fn code(&self) -> Option<&str> {
148 self.code.as_deref()
149 }
150}
151
152#[derive(Debug, thiserror::Error)]
153pub enum Error {
154 #[error(transparent)]
155 OperationCancelled(#[from] crate::OperationCancelled),
156
157 #[error(transparent)]
158 ThemeColor(#[from] crate::theme_color::ColorError),
159
160 #[error(transparent)]
161 RuntimePolicy(#[from] crate::runtime::RuntimePolicyError),
162
163 #[error(transparent)]
164 DetectType(#[from] DetectTypeError),
165
166 #[error("Unsupported diagram type: {diagram_type}")]
167 UnsupportedDiagram { diagram_type: String },
168
169 #[error("Diagram parse error ({diagram_type}): {}", diagnostic.message())]
175 DiagramParse {
176 diagram_type: String,
177 diagnostic: ParseDiagnostic,
178 },
179
180 #[error(
181 "Malformed YAML front-matter. If you were trying to use a YAML front-matter, please ensure that you've correctly opened and closed the YAML front-matter with un-indented `---` blocks"
182 )]
183 MalformedFrontMatter,
184
185 #[error("Invalid directive JSON: {message}")]
186 InvalidDirectiveJson { message: String },
187
188 #[error("Invalid YAML front-matter: {message}")]
189 InvalidFrontMatterYaml { message: String },
190}
191
192impl Error {
193 #[cfg(any(
194 feature = "diagram-git-graph",
195 feature = "diagram-radar",
196 feature = "diagram-mindmap"
197 ))]
198 pub(crate) fn with_exact_span_if_missing(self, span: SourceSpan) -> Self {
199 match self {
200 Self::DiagramParse {
201 diagram_type,
202 diagnostic,
203 } if diagnostic.span().is_none() => Self::DiagramParse {
204 diagram_type,
205 diagnostic: diagnostic.with_span(span, ParseDiagnosticSpanKind::Exact),
206 },
207 other => other,
208 }
209 }
210
211 pub fn diagram_parse_fallback(
212 diagram_type: impl Into<String>,
213 message: impl Into<String>,
214 ) -> Self {
215 Self::diagram_parse_diagnostic(diagram_type, ParseDiagnostic::new(message))
216 }
217
218 pub fn diagram_parse_exact(
219 diagram_type: impl Into<String>,
220 message: impl Into<String>,
221 span: SourceSpan,
222 ) -> Self {
223 Self::diagram_parse_diagnostic(
224 diagram_type,
225 ParseDiagnostic::new(message).with_span(span, ParseDiagnosticSpanKind::Exact),
226 )
227 }
228
229 pub fn diagram_parse_insertion_point(
230 diagram_type: impl Into<String>,
231 message: impl Into<String>,
232 offset: usize,
233 ) -> Self {
234 Self::diagram_parse_diagnostic(
235 diagram_type,
236 ParseDiagnostic::new(message).with_span(
237 SourceSpan::new(offset, offset),
238 ParseDiagnosticSpanKind::InsertionPoint,
239 ),
240 )
241 }
242
243 pub fn diagram_parse_fallback_at(
244 diagram_type: impl Into<String>,
245 message: impl Into<String>,
246 span: SourceSpan,
247 ) -> Self {
248 Self::diagram_parse_diagnostic(
249 diagram_type,
250 ParseDiagnostic::new(message).with_span(span, ParseDiagnosticSpanKind::Fallback),
251 )
252 }
253
254 pub fn diagram_parse_diagnostic(
255 diagram_type: impl Into<String>,
256 diagnostic: ParseDiagnostic,
257 ) -> Self {
258 Self::DiagramParse {
259 diagram_type: diagram_type.into(),
260 diagnostic,
261 }
262 }
263}