Skip to main content

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}