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