Skip to main content

svugy_model/
problem.rs

1//! Describe each problem validation reports, as a diagnostic with its severity, labels and did-you-mean help.
2
3use 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/// Report one problem: something the author must fix (an error) or should look at (a warning).
16#[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/// Name where a component property was written: on the component itself, or in one workflow's override of it.
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum ElementSite {
116    Declaration(ElementPath),
117    Override {
118        workflow: WorkflowId,
119        element: ElementPath,
120    },
121}
122
123/// Identify the owner of a style entry: a component site or a workflow edge.
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum StyleOwner {
126    Element(ElementSite),
127    Edge { workflow: WorkflowId, edge: EdgeId },
128}
129
130/// Tell why a description link's target doesn't resolve, and in which workflow when the reason is scoped to one.
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub enum UnresolvedTargetReason {
133    NamesNothing,
134    NamesNothingIn(WorkflowId),
135    NamesSeveralEdgesIn(WorkflowId),
136    IsNotShownBy(WorkflowId),
137}
138
139/// Report an element whose ancestor is missing: its enclosing container was never declared.
140#[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/// Report a duplicate element: one declared twice at the same path.
153#[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/// Report a model without workflows, which draws nothing.
163#[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/// Report a workflow id used by an earlier workflow.
169#[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/// Report a workflow id that is not spelled like a component id.
179#[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/// Report a workflow without steps, which shows nothing.
191#[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/// Report a step id used by an earlier step of the same workflow.
201#[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/// Report a step id that is not spelled like a component id.
212#[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/// Report a step listing nothing.
225#[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/// Report an item a step lists twice.
236#[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/// Report a step item naming no edge of its workflow or no element of the model.
248#[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/// Report a workflow edge no step lists, which is never drawn.
265#[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/// Report a dangling edge endpoint: a path that is not an element.
276#[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/// Report an edge joining an element to itself, its ancestor or its descendant, which no layer can draw.
290#[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/// Report a second override of one element in one workflow.
303#[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/// Report an override of an element the workflow doesn't show, which changes nothing.
314#[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/// Report an element no workflow shows, which is never drawn.
327#[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/// Report an unknown style key: one outside the v1 set.
337#[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/// Report a mismatched style value: a v1 style key set to a value of the wrong kind.
350#[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/// Report an empty grid hint: zero columns, so no cell to place a child in.
363#[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/// Report a packing hint laying children in more tracks, columns or rows, than a packed level can count.
376#[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/// Report a child placement hint on an element the workflow shows without children, which has nothing to place.
393#[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/// Report a pinned position with a coordinate that is not a finite number within `MAX_MAGNITUDE` of its level's origin.
407#[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/// Report an unresolved link: one shaped like an element path that names no element, the likely typo of a drill-down target.
423#[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/// Report a link to an element a workflow doesn't show, so the linking element links nowhere there.
438#[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/// Report a `code` value that names no place in the repository, so no tree check could resolve it.
452#[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/// Report a description link whose `#` target doesn't resolve, so the runtime shows its text unlinked.
464#[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/// Report a container's shape, which a workflow ignores: there the element is a slab, whose rectangular top holds its interior.
478#[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/// Report a height that is not a positive number within `MAX_MAGNITUDE` plan units, so no solid can stand that tall.
492#[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/// Report a container's height, which a workflow ignores: there the element is a slab, whose tag hangs from its low front face.
505#[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/// Report a badge edge with a container at either end in its workflow, since a badge is drawn from a leaf's top face as a mini of a leaf.
519#[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/// Suggest the known name the author most likely meant.
530#[derive(Debug, Clone, PartialEq, Eq)]
531pub struct Suggestion<T>(pub T);
532
533impl<T: fmt::Display> Suggestion<T> {
534    /// Suggest c* = argmin over c of levenshtein(lower(unknown), lower(c)), kept iff that distance ≤ ⌊len(unknown) / 3⌋.
535    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    /// Name where a step item is looked up: its workflow for an edge, the whole model for an element.
564    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
618/// Classify a diagnostic as an error, one that blocks a build or a load, when it says so or states no severity at all.
619pub trait ErrorSeverity {
620    /// Tell whether the diagnostic is an error.
621    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}