ifc_model/diagnostic.rs
1//! Codec-neutral, non-fatal problems found while reading a model.
2//!
3//! A codec that recovers from damaged input must be able to say what it
4//! dropped: silent recovery turns a corrupt file into a plausible-looking
5//! model with no trace of the loss. These diagnostics are attached to the
6//! [`Model`](crate::Model) rather than returned separately so the information
7//! survives being passed around an application.
8//!
9//! The type is codec-neutral on purpose. STEP reports byte offsets, ifcXML
10//! would report element positions, so the location is an optional byte range
11//! rather than a STEP span, and this crate names no codec type.
12
13use std::fmt;
14use std::ops::Range;
15
16/// How serious a non-fatal finding is.
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
18#[non_exhaustive]
19pub enum Severity {
20 /// Input was read, but not fully represented — something was dropped.
21 #[default]
22 Warning,
23}
24
25/// A non-fatal problem found while reading a model.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Diagnostic {
28 severity: Severity,
29 byte_range: Option<Range<usize>>,
30 detail: String,
31}
32
33impl Diagnostic {
34 /// Records a warning covering a byte range of the source.
35 pub fn warning(byte_range: Range<usize>, detail: impl Into<String>) -> Self {
36 Self {
37 severity: Severity::Warning,
38 byte_range: Some(byte_range),
39 detail: detail.into(),
40 }
41 }
42
43 /// Records a warning with no source location.
44 pub fn unlocated(detail: impl Into<String>) -> Self {
45 Self {
46 severity: Severity::Warning,
47 byte_range: None,
48 detail: detail.into(),
49 }
50 }
51
52 /// Severity of this finding.
53 pub const fn severity(&self) -> Severity {
54 self.severity
55 }
56
57 /// Byte range of the source this finding covers, when the codec has one.
58 pub const fn byte_range(&self) -> Option<&Range<usize>> {
59 self.byte_range.as_ref()
60 }
61
62 /// Human-readable description without a location prefix.
63 pub fn detail(&self) -> &str {
64 &self.detail
65 }
66}
67
68impl fmt::Display for Diagnostic {
69 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match &self.byte_range {
71 Some(range) => write!(
72 formatter,
73 "warning at bytes {}..{}: {}",
74 range.start, range.end, self.detail
75 ),
76 None => write!(formatter, "warning: {}", self.detail),
77 }
78 }
79}