Skip to main content

ifc_validate/report/
path.rs

1//! Where in a file a finding applies.
2
3use std::fmt;
4
5use ifc_model::EntityId;
6
7/// Where in the file a finding applies.
8///
9/// Deliberately not a string: a caller that wants to jump to the offending
10/// attribute needs the entity and slot as data, and formatting is a
11/// presentation choice made at the edge.
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13#[non_exhaustive]
14pub enum Path {
15    /// The file as a whole, e.g. a header or global-rule finding.
16    File,
17    /// One entity instance.
18    Entity(EntityId),
19    /// One attribute slot of one entity.
20    Attribute {
21        /// The entity carrying the attribute.
22        entity: EntityId,
23        /// Zero-based slot index in Part 21 positional order.
24        index: usize,
25        /// The attribute's schema name, when the schema declares one.
26        name: Option<String>,
27    },
28}
29
30impl fmt::Display for Path {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::File => formatter.write_str("<file>"),
34            Self::Entity(id) => write!(formatter, "{id}"),
35            Self::Attribute {
36                entity,
37                index,
38                name,
39            } => match name {
40                Some(name) => write!(formatter, "{entity}.{name}"),
41                None => write!(formatter, "{entity}[{index}]"),
42            },
43        }
44    }
45}
46
47/// Total order over paths: file first, then by entity, then by slot.
48///
49/// Used by report sorting; kept beside `Path` so a new variant cannot be
50/// added without confronting its ordering.
51#[must_use]
52pub(crate) fn path_key(path: &Path) -> (u64, usize) {
53    match path {
54        Path::File => (0, 0),
55        Path::Entity(id) => (id.0, 0),
56        Path::Attribute { entity, index, .. } => (entity.0, *index + 1),
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn an_attribute_path_names_the_slot_when_the_schema_does() {
66        let named = Path::Attribute {
67            entity: EntityId(7),
68            index: 2,
69            name: Some("Name".into()),
70        };
71        assert_eq!(named.to_string(), "#7.Name");
72        let anonymous = Path::Attribute {
73            entity: EntityId(7),
74            index: 2,
75            name: None,
76        };
77        assert_eq!(anonymous.to_string(), "#7[2]");
78    }
79
80    /// The file itself sorts before any entity.
81    #[test]
82    fn the_file_path_sorts_first() {
83        assert!(path_key(&Path::File) < path_key(&Path::Entity(EntityId(1))));
84    }
85}