Skip to main content

ifc_step/
error.rs

1//! Why a STEP parse failed at the IFC adapter boundary.
2
3use ifc_model::{EntityId, ModelError};
4use thiserror::Error;
5
6/// Failures specific to reading or writing IFC STEP text.
7#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum StepError {
10    /// The bytes do not begin with the ISO-10303-21 magic.
11    #[error("not a STEP physical file: {0}")]
12    NotStep(String),
13
14    /// Malformed syntax at a known byte offset.
15    #[error("syntax error at byte {offset}: {detail}")]
16    Syntax {
17        /// Byte offset into the source.
18        offset: usize,
19        /// What went wrong.
20        detail: String,
21    },
22
23    /// A record in the DATA section had no `#id=` prefix.
24    #[error("entity record without an id at byte {offset}")]
25    MissingEntityId {
26        /// Byte offset into the source.
27        offset: usize,
28    },
29
30    /// A REAL token has no decimal point (`1E-05`).
31    ///
32    /// ISO 10303-21 requires the point, so a strict read refuses the token.
33    /// A lenient read ([`crate::StepCodec::lenient`]) reads it as the REAL
34    /// it spells and reports a diagnostic.
35    #[error("syntax error at byte {offset}: REAL `{token}` has no decimal point (ISO 10303-21 requires one)")]
36    RealWithoutDecimalPoint {
37        /// Byte offset of the token in the source.
38        offset: usize,
39        /// The token as written.
40        token: String,
41    },
42
43    /// The model contains a REAL value Part 21 cannot represent.
44    #[error("entity #{entity} slot {slot} contains a non-finite REAL")]
45    NonFiniteReal {
46        /// Entity containing the value.
47        entity: EntityId,
48        /// Top-level positional attribute containing the value.
49        slot: usize,
50    },
51
52    /// Underlying I/O failure.
53    #[error("io error: {0}")]
54    Io(String),
55}
56
57impl StepError {
58    /// Maps an `openbim-step` failure over `input` onto this crate's error.
59    ///
60    /// The substrate refuses a REAL without its decimal point with a typed
61    /// error whose span covers exactly the number; with the source at hand,
62    /// that becomes [`StepError::RealWithoutDecimalPoint`] naming the token
63    /// as written. Every other failure maps as [`From`] does.
64    pub(crate) fn from_step(input: &[u8], error: openbim_step::StepError) -> Self {
65        if error.is_real_without_point() {
66            let span = error.span();
67            if let Some(token) = input.get(span.start..span.end) {
68                return Self::RealWithoutDecimalPoint {
69                    offset: span.start,
70                    token: String::from_utf8_lossy(token).into_owned(),
71                };
72            }
73        }
74        error.into()
75    }
76}
77
78/// Without the source bytes the token cannot be quoted, so a REAL without
79/// its decimal point maps to [`StepError::Syntax`] at the token's offset,
80/// with the substrate's detail, which names the token.
81impl From<openbim_step::StepError> for StepError {
82    fn from(error: openbim_step::StepError) -> Self {
83        if error.is_not_step() {
84            Self::NotStep(error.detail().to_owned())
85        } else {
86            Self::Syntax {
87                offset: error.span().start,
88                detail: error.detail().to_owned(),
89            }
90        }
91    }
92}
93
94impl From<StepError> for ModelError {
95    fn from(error: StepError) -> Self {
96        match error {
97            StepError::NotStep(detail) => ModelError::WrongFormat {
98                expected: "STEP",
99                detail,
100            },
101            StepError::Syntax { offset, detail } => ModelError::Syntax { offset, detail },
102            StepError::MissingEntityId { offset } => ModelError::Syntax {
103                offset,
104                detail: "entity record without an id".into(),
105            },
106            StepError::RealWithoutDecimalPoint { offset, token } => ModelError::Syntax {
107                offset,
108                detail: format!(
109                    "REAL `{token}` has no decimal point (ISO 10303-21 requires one; \
110                     a lenient read accepts it)"
111                ),
112            },
113            StepError::NonFiniteReal { entity, slot } => ModelError::Write(format!(
114                "entity #{entity} slot {slot} contains a non-finite REAL"
115            )),
116            StepError::Io(message) => ModelError::Io(message),
117        }
118    }
119}