1use std::fmt;
4
5use miette::{Diagnostic, Severity, SourceSpan};
6use thiserror::Error;
7
8use crate::code::MalformedCodeReason;
9use crate::description::DescriptionOwner;
10use crate::model::{EdgeId, MAX_MAGNITUDE, PlacementHint, Position, Shape};
11use crate::path::ElementPath;
12use crate::style::{AnyStyleKey, StyleValueKind};
13use crate::workflow::{StepId, StepItem, WorkflowId};
14
15#[derive(Debug, Error, Diagnostic)]
17pub enum Problem {
18 #[error(transparent)]
19 #[diagnostic(transparent)]
20 MissingAncestor(MissingAncestor),
21 #[error(transparent)]
22 #[diagnostic(transparent)]
23 DuplicateElement(DuplicateElement),
24 #[error(transparent)]
25 #[diagnostic(transparent)]
26 NoWorkflow(NoWorkflow),
27 #[error(transparent)]
28 #[diagnostic(transparent)]
29 DuplicateWorkflow(DuplicateWorkflow),
30 #[error(transparent)]
31 #[diagnostic(transparent)]
32 MalformedWorkflowId(MalformedWorkflowId),
33 #[error(transparent)]
34 #[diagnostic(transparent)]
35 WorkflowWithoutSteps(WorkflowWithoutSteps),
36 #[error(transparent)]
37 #[diagnostic(transparent)]
38 DuplicateStep(DuplicateStep),
39 #[error(transparent)]
40 #[diagnostic(transparent)]
41 MalformedStepId(MalformedStepId),
42 #[error(transparent)]
43 #[diagnostic(transparent)]
44 StepWithoutItems(StepWithoutItems),
45 #[error(transparent)]
46 #[diagnostic(transparent)]
47 DuplicateStepItem(DuplicateStepItem),
48 #[error(transparent)]
49 #[diagnostic(transparent)]
50 UnknownStepItem(UnknownStepItem),
51 #[error(transparent)]
52 #[diagnostic(transparent)]
53 UnlistedEdge(UnlistedEdge),
54 #[error(transparent)]
55 #[diagnostic(transparent)]
56 DanglingEdgeEndpoint(DanglingEdgeEndpoint),
57 #[error(transparent)]
58 #[diagnostic(transparent)]
59 LineageEdge(LineageEdge),
60 #[error(transparent)]
61 #[diagnostic(transparent)]
62 DuplicateOverride(DuplicateOverride),
63 #[error(transparent)]
64 #[diagnostic(transparent)]
65 OverrideOfUnshownElement(OverrideOfUnshownElement),
66 #[error(transparent)]
67 #[diagnostic(transparent)]
68 UnshownElement(UnshownElement),
69 #[error(transparent)]
70 #[diagnostic(transparent)]
71 UnknownStyleKey(UnknownStyleKey),
72 #[error(transparent)]
73 #[diagnostic(transparent)]
74 MismatchedStyleValue(MismatchedStyleValue),
75 #[error(transparent)]
76 #[diagnostic(transparent)]
77 EmptyGridHint(EmptyGridHint),
78 #[error(transparent)]
79 #[diagnostic(transparent)]
80 UncountablePackedTracks(UncountablePackedTracks),
81 #[error(transparent)]
82 #[diagnostic(transparent)]
83 ChildPlacementWithoutChildren(ChildPlacementWithoutChildren),
84 #[error(transparent)]
85 #[diagnostic(transparent)]
86 UnboundedPosition(UnboundedPosition),
87 #[error(transparent)]
88 #[diagnostic(transparent)]
89 UnresolvedLink(UnresolvedLink),
90 #[error(transparent)]
91 #[diagnostic(transparent)]
92 UnshownLinkTarget(UnshownLinkTarget),
93 #[error(transparent)]
94 #[diagnostic(transparent)]
95 MalformedCodeReference(MalformedCodeReference),
96 #[error(transparent)]
97 #[diagnostic(transparent)]
98 UnresolvedDescriptionTarget(UnresolvedDescriptionTarget),
99 #[error(transparent)]
100 #[diagnostic(transparent)]
101 IgnoredContainerShape(IgnoredContainerShape),
102 #[error(transparent)]
103 #[diagnostic(transparent)]
104 ImpossibleHeight(ImpossibleHeight),
105 #[error(transparent)]
106 #[diagnostic(transparent)]
107 IgnoredContainerHeight(IgnoredContainerHeight),
108 #[error(transparent)]
109 #[diagnostic(transparent)]
110 BadgeEdgeOnContainer(BadgeEdgeOnContainer),
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum ElementSite {
116 Declaration(ElementPath),
117 Override {
118 workflow: WorkflowId,
119 element: ElementPath,
120 },
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum StyleOwner {
126 Element(ElementSite),
127 Edge { workflow: WorkflowId, edge: EdgeId },
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
132pub enum UnresolvedTargetReason {
133 NamesNothing,
134 NamesNothingIn(WorkflowId),
135 NamesSeveralEdgesIn(WorkflowId),
136 IsNotShownBy(WorkflowId),
137}
138
139#[derive(Debug, Error, Diagnostic)]
141#[error("element `{element}` is inside `{ancestor}`, which is not an element")]
142#[diagnostic(code(svugy::dangling_reference))]
143pub struct MissingAncestor {
144 pub element: ElementPath,
145 pub ancestor: ElementPath,
146 #[help]
147 pub suggestion: Option<Suggestion<ElementPath>>,
148 #[label("declared here")]
149 pub span: Option<SourceSpan>,
150}
151
152#[derive(Debug, Error, Diagnostic)]
154#[error("element `{element}` is declared more than once")]
155#[diagnostic(code(svugy::duplicate_id))]
156pub struct DuplicateElement {
157 pub element: ElementPath,
158 #[label("declared again here")]
159 pub span: Option<SourceSpan>,
160}
161
162#[derive(Debug, Error, Diagnostic)]
164#[error("the model has no workflow, so nothing is drawn")]
165#[diagnostic(code(svugy::no_workflow))]
166pub struct NoWorkflow;
167
168#[derive(Debug, Error, Diagnostic)]
170#[error("workflow `{workflow}` is declared more than once")]
171#[diagnostic(code(svugy::duplicate_id))]
172pub struct DuplicateWorkflow {
173 pub workflow: WorkflowId,
174 #[label("declared again here")]
175 pub span: Option<SourceSpan>,
176}
177
178#[derive(Debug, Error, Diagnostic)]
180#[error(
181 "workflow id `{workflow}` is not an id: ASCII letters, digits, `_` and single inner `-`, starting with a letter or `_`"
182)]
183#[diagnostic(code(svugy::malformed_id))]
184pub struct MalformedWorkflowId {
185 pub workflow: WorkflowId,
186 #[label("declared here")]
187 pub span: Option<SourceSpan>,
188}
189
190#[derive(Debug, Error, Diagnostic)]
192#[error("workflow `{workflow}` has no step, so it shows nothing")]
193#[diagnostic(code(svugy::empty_workflow))]
194pub struct WorkflowWithoutSteps {
195 pub workflow: WorkflowId,
196 #[label("declared here")]
197 pub span: Option<SourceSpan>,
198}
199
200#[derive(Debug, Error, Diagnostic)]
202#[error("step `{workflow}/{step}` is declared more than once")]
203#[diagnostic(code(svugy::duplicate_id))]
204pub struct DuplicateStep {
205 pub workflow: WorkflowId,
206 pub step: StepId,
207 #[label("declared again here")]
208 pub span: Option<SourceSpan>,
209}
210
211#[derive(Debug, Error, Diagnostic)]
213#[error(
214 "step id `{step}` of workflow `{workflow}` is not an id: ASCII letters, digits, `_` and single inner `-`, starting with a letter or `_`"
215)]
216#[diagnostic(code(svugy::malformed_id))]
217pub struct MalformedStepId {
218 pub workflow: WorkflowId,
219 pub step: StepId,
220 #[label("declared here")]
221 pub span: Option<SourceSpan>,
222}
223
224#[derive(Debug, Error, Diagnostic)]
226#[error("step `{workflow}/{step}` lists no edge or component")]
227#[diagnostic(code(svugy::empty_step))]
228pub struct StepWithoutItems {
229 pub workflow: WorkflowId,
230 pub step: StepId,
231 #[label("declared here")]
232 pub span: Option<SourceSpan>,
233}
234
235#[derive(Debug, Error, Diagnostic)]
237#[error("step `{workflow}/{step}` lists {item} more than once")]
238#[diagnostic(code(svugy::duplicate_step_item))]
239pub struct DuplicateStepItem {
240 pub workflow: WorkflowId,
241 pub step: StepId,
242 pub item: StepItem,
243 #[label("in this step")]
244 pub span: Option<SourceSpan>,
245}
246
247#[derive(Debug, Error, Diagnostic)]
249#[error(
250 "step `{workflow}/{step}` lists {item}, which names nothing in {}",
251 Self::searched_scope(.item, .workflow)
252)]
253#[diagnostic(code(svugy::dangling_reference))]
254pub struct UnknownStepItem {
255 pub workflow: WorkflowId,
256 pub step: StepId,
257 pub item: StepItem,
258 #[help]
259 pub suggestion: Option<Suggestion<String>>,
260 #[label("in this step")]
261 pub span: Option<SourceSpan>,
262}
263
264#[derive(Debug, Error, Diagnostic)]
266#[error("edge `{edge}` of workflow `{workflow}` is listed by no step, so it is never drawn")]
267#[diagnostic(code(svugy::unlisted_edge))]
268pub struct UnlistedEdge {
269 pub workflow: WorkflowId,
270 pub edge: EdgeId,
271 #[label("declared here")]
272 pub span: Option<SourceSpan>,
273}
274
275#[derive(Debug, Error, Diagnostic)]
277#[error("edge `{edge}` of workflow `{workflow}` points to `{endpoint}`, which is not an element")]
278#[diagnostic(code(svugy::dangling_reference))]
279pub struct DanglingEdgeEndpoint {
280 pub workflow: WorkflowId,
281 pub edge: EdgeId,
282 pub endpoint: ElementPath,
283 #[help]
284 pub suggestion: Option<Suggestion<ElementPath>>,
285 #[label("referenced here")]
286 pub span: Option<SourceSpan>,
287}
288
289#[derive(Debug, Error, Diagnostic)]
291#[error(
292 "edge `{edge}` of workflow `{workflow}` joins an element to itself or to its own ancestor, which no layer can draw"
293)]
294#[diagnostic(code(svugy::lineage_edge))]
295pub struct LineageEdge {
296 pub workflow: WorkflowId,
297 pub edge: EdgeId,
298 #[label("declared here")]
299 pub span: Option<SourceSpan>,
300}
301
302#[derive(Debug, Error, Diagnostic)]
304#[error("workflow `{workflow}` overrides `{element}` more than once")]
305#[diagnostic(code(svugy::duplicate_override))]
306pub struct DuplicateOverride {
307 pub workflow: WorkflowId,
308 pub element: ElementPath,
309 #[label("overridden again here")]
310 pub span: Option<SourceSpan>,
311}
312
313#[derive(Debug, Error, Diagnostic)]
315#[error("workflow `{workflow}` overrides `{element}`, which it doesn't show")]
316#[diagnostic(code(svugy::unshown_override))]
317pub struct OverrideOfUnshownElement {
318 pub workflow: WorkflowId,
319 pub element: ElementPath,
320 #[help]
321 pub suggestion: Option<Suggestion<ElementPath>>,
322 #[label("overridden here")]
323 pub span: Option<SourceSpan>,
324}
325
326#[derive(Debug, Error, Diagnostic)]
328#[error("element `{element}` is shown by no workflow, so it is never drawn")]
329#[diagnostic(code(svugy::unshown_element), severity(Warning))]
330pub struct UnshownElement {
331 pub element: ElementPath,
332 #[label("declared here")]
333 pub span: Option<SourceSpan>,
334}
335
336#[derive(Debug, Error, Diagnostic)]
338#[error("{owner} has style `{key}`, which is not a v1 style key")]
339#[diagnostic(code(svugy::unknown_style))]
340pub struct UnknownStyleKey {
341 pub owner: StyleOwner,
342 pub key: String,
343 #[help]
344 pub suggestion: Option<Suggestion<&'static str>>,
345 #[label("styled here")]
346 pub span: Option<SourceSpan>,
347}
348
349#[derive(Debug, Error, Diagnostic)]
351#[error("{owner} sets style `{key}` to `{value}`, which is not {expected}")]
352#[diagnostic(code(svugy::mismatched_style_value))]
353pub struct MismatchedStyleValue {
354 pub owner: StyleOwner,
355 pub key: AnyStyleKey,
356 pub value: String,
357 pub expected: StyleValueKind,
358 #[label("styled here")]
359 pub span: Option<SourceSpan>,
360}
361
362#[derive(Debug, Error, Diagnostic)]
364#[error(
365 "workflow `{workflow}` asks `{element}` for a grid of 0 columns, which has no cell for any child"
366)]
367#[diagnostic(code(svugy::impossible_placement))]
368pub struct EmptyGridHint {
369 pub workflow: WorkflowId,
370 pub element: ElementPath,
371 #[label("overridden here")]
372 pub span: Option<SourceSpan>,
373}
374
375#[derive(Debug, Error, Diagnostic)]
377#[error(
378 "workflow `{workflow}` packs the {child_count} children of `{element}` into {columns} columns and {rows} rows, but a packed level holds at most {max} of either",
379 max = PlacementHint::MAX_PACKED_TRACKS
380)]
381#[diagnostic(code(svugy::impossible_placement))]
382pub struct UncountablePackedTracks {
383 pub workflow: WorkflowId,
384 pub element: ElementPath,
385 pub child_count: usize,
386 pub columns: usize,
387 pub rows: usize,
388 #[label("overridden here")]
389 pub span: Option<SourceSpan>,
390}
391
392#[derive(Debug, Error, Diagnostic)]
394#[error(
395 "workflow `{workflow}` gives `{element}` child placement `{child_placement:?}`, but shows no children of it"
396)]
397#[diagnostic(code(svugy::impossible_placement), severity(Warning))]
398pub struct ChildPlacementWithoutChildren {
399 pub workflow: WorkflowId,
400 pub element: ElementPath,
401 pub child_placement: PlacementHint,
402 #[label("overridden here")]
403 pub span: Option<SourceSpan>,
404}
405
406#[derive(Debug, Error, Diagnostic)]
408#[error(
409 "workflow `{workflow}` pins `{element}` at ({}, {}), which is not within {MAX_MAGNITUDE} plan units of its level's origin",
410 .position.x,
411 .position.y
412)]
413#[diagnostic(code(svugy::impossible_placement))]
414pub struct UnboundedPosition {
415 pub workflow: WorkflowId,
416 pub element: ElementPath,
417 pub position: Position,
418 #[label("overridden here")]
419 pub span: Option<SourceSpan>,
420}
421
422#[derive(Debug, Error, Diagnostic)]
424#[error(
425 "{site} links to `{link}`, which is not an element; write `./{link}` if it is a relative URL"
426)]
427#[diagnostic(code(svugy::unresolved_link), severity(Warning))]
428pub struct UnresolvedLink {
429 pub site: ElementSite,
430 pub link: String,
431 #[help]
432 pub suggestion: Option<Suggestion<ElementPath>>,
433 #[label("linked here")]
434 pub span: Option<SourceSpan>,
435}
436
437#[derive(Debug, Error, Diagnostic)]
439#[error(
440 "element `{element}` links to `{target}`, which workflow `{workflow}` doesn't show, so it links nowhere there"
441)]
442#[diagnostic(code(svugy::unshown_link_target), severity(Warning))]
443pub struct UnshownLinkTarget {
444 pub workflow: WorkflowId,
445 pub element: ElementPath,
446 pub target: ElementPath,
447 #[label("linked here")]
448 pub span: Option<SourceSpan>,
449}
450
451#[derive(Debug, Error, Diagnostic)]
453#[error("{site} points at code `{code}`, but {reason}")]
454#[diagnostic(code(svugy::malformed_code_reference))]
455pub struct MalformedCodeReference {
456 pub site: ElementSite,
457 pub code: String,
458 pub reason: MalformedCodeReason,
459 #[label("pointed here")]
460 pub span: Option<SourceSpan>,
461}
462
463#[derive(Debug, Error, Diagnostic)]
465#[error("the description of {owner} links to `#{target}`, which {reason}")]
466#[diagnostic(code(svugy::unresolved_description_target), severity(Warning))]
467pub struct UnresolvedDescriptionTarget {
468 pub owner: DescriptionOwner,
469 pub target: String,
470 pub reason: UnresolvedTargetReason,
471 #[help]
472 pub suggestion: Option<Suggestion<String>>,
473 #[label("described here")]
474 pub span: Option<SourceSpan>,
475}
476
477#[derive(Debug, Error, Diagnostic)]
479#[error(
480 "element `{element}` is a container in workflow `{workflow}`, so it is drawn as a slab there and its shape `{shape:?}` is ignored"
481)]
482#[diagnostic(code(svugy::ignored_container_shape), severity(Warning))]
483pub struct IgnoredContainerShape {
484 pub workflow: WorkflowId,
485 pub element: ElementPath,
486 pub shape: Shape,
487 #[label("shaped here")]
488 pub span: Option<SourceSpan>,
489}
490
491#[derive(Debug, Error, Diagnostic)]
493#[error(
494 "{site} stands {height} plan units tall, which is not a positive number no greater than {MAX_MAGNITUDE}"
495)]
496#[diagnostic(code(svugy::impossible_height))]
497pub struct ImpossibleHeight {
498 pub site: ElementSite,
499 pub height: f64,
500 #[label("declared here")]
501 pub span: Option<SourceSpan>,
502}
503
504#[derive(Debug, Error, Diagnostic)]
506#[error(
507 "element `{element}` is a container in workflow `{workflow}`, so it is drawn as a slab there and its height {height} is ignored"
508)]
509#[diagnostic(code(svugy::ignored_container_height), severity(Warning))]
510pub struct IgnoredContainerHeight {
511 pub workflow: WorkflowId,
512 pub element: ElementPath,
513 pub height: f64,
514 #[label("declared here")]
515 pub span: Option<SourceSpan>,
516}
517
518#[derive(Debug, Error, Diagnostic)]
520#[error("edge `{edge}` of workflow `{workflow}` is a badge, which needs a leaf at both ends")]
521#[diagnostic(code(svugy::badge_edge_on_container))]
522pub struct BadgeEdgeOnContainer {
523 pub workflow: WorkflowId,
524 pub edge: EdgeId,
525 #[label("declared here")]
526 pub span: Option<SourceSpan>,
527}
528
529#[derive(Debug, Clone, PartialEq, Eq)]
531pub struct Suggestion<T>(pub T);
532
533impl<T: fmt::Display> Suggestion<T> {
534 pub fn closest_to(unknown: &str, candidates: impl IntoIterator<Item = T>) -> Option<Self> {
536 let lowercase_unknown = unknown.to_lowercase();
537 let unknown_length = unknown.chars().count();
538 let max_distance = unknown_length / 3;
539 let distances = candidates.into_iter().map(|candidate| {
540 let candidate_text = candidate.to_string();
541 let lowercase_candidate = candidate_text.to_lowercase();
542 let distance = strsim::levenshtein(&lowercase_unknown, &lowercase_candidate);
543 (distance, candidate)
544 });
545 let (closest_distance, closest) =
546 distances.min_by_key(|(distance, _candidate)| *distance)?;
547 let is_too_far = closest_distance > max_distance;
548 if is_too_far {
549 return None;
550 }
551 let suggestion = Suggestion(closest);
552 Some(suggestion)
553 }
554}
555
556impl<T: fmt::Display> fmt::Display for Suggestion<T> {
557 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
558 write!(formatter, "did you mean `{}`?", self.0)
559 }
560}
561
562impl UnknownStepItem {
563 fn searched_scope(item: &StepItem, workflow: &WorkflowId) -> String {
565 match item {
566 StepItem::Edge(_id) => format!("workflow `{workflow}`"),
567 StepItem::Element(_path) => "the model".to_owned(),
568 }
569 }
570}
571
572impl fmt::Display for ElementSite {
573 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
574 match self {
575 Self::Declaration(path) => write!(formatter, "element `{path}`"),
576 Self::Override { workflow, element } => write!(
577 formatter,
578 "the override of `{element}` in workflow `{workflow}`"
579 ),
580 }
581 }
582}
583
584impl fmt::Display for StyleOwner {
585 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
586 match self {
587 Self::Element(site) => write!(formatter, "{site}"),
588 Self::Edge { workflow, edge } => {
589 write!(formatter, "edge `{edge}` of workflow `{workflow}`")
590 }
591 }
592 }
593}
594
595impl fmt::Display for UnresolvedTargetReason {
596 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
597 match self {
598 Self::NamesNothing => formatter.write_str(
599 "names no element `#PATH`, edge `#FROM->TO[n]` or step `#WORKFLOW/STEP`",
600 ),
601 Self::NamesNothingIn(workflow) => {
602 write!(formatter, "names nothing in workflow `{workflow}`")
603 }
604 Self::NamesSeveralEdgesIn(workflow) => write!(
605 formatter,
606 "names several edges in workflow `{workflow}`; add `[n]` to pick one"
607 ),
608 Self::IsNotShownBy(workflow) => {
609 write!(
610 formatter,
611 "names a component workflow `{workflow}` doesn't show"
612 )
613 }
614 }
615 }
616}
617
618pub trait ErrorSeverity {
620 fn is_error(&self) -> bool;
622}
623
624impl<D: Diagnostic + ?Sized> ErrorSeverity for D {
625 fn is_error(&self) -> bool {
626 let severity = self.severity();
627 let severity_or_error = severity.unwrap_or(Severity::Error);
628 severity_or_error == Severity::Error
629 }
630}