sara_core/model/
field.rs

1//! Field name definitions for YAML frontmatter.
2//!
3//! This module provides a single source of truth for all field names
4//! used in YAML frontmatter serialization and deserialization.
5
6/// All field names used in YAML frontmatter.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum FieldName {
9    // Required fields
10    Id,
11    Type,
12    Name,
13
14    // Optional metadata
15    Description,
16
17    // Upstream traceability (relationships)
18    Refines,
19    DerivesFrom,
20    Satisfies,
21
22    // Downstream traceability (relationships)
23    IsRefinedBy,
24    Derives,
25    IsSatisfiedBy,
26
27    // Peer relationships
28    DependsOn,
29    IsRequiredBy,
30
31    // Type-specific attributes
32    Specification,
33    Platform,
34    JustifiedBy,
35}
36
37impl FieldName {
38    /// Returns all known field names.
39    pub const fn all() -> &'static [FieldName] {
40        &[
41            Self::Id,
42            Self::Type,
43            Self::Name,
44            Self::Description,
45            Self::Refines,
46            Self::DerivesFrom,
47            Self::Satisfies,
48            Self::IsRefinedBy,
49            Self::Derives,
50            Self::IsSatisfiedBy,
51            Self::DependsOn,
52            Self::IsRequiredBy,
53            Self::Specification,
54            Self::Platform,
55            Self::JustifiedBy,
56        ]
57    }
58
59    /// Returns the YAML field name (snake_case).
60    ///
61    /// Used for serialization, deserialization, and error messages.
62    pub const fn as_str(&self) -> &'static str {
63        match self {
64            Self::Id => "id",
65            Self::Type => "type",
66            Self::Name => "name",
67            Self::Description => "description",
68            Self::Refines => "refines",
69            Self::DerivesFrom => "derives_from",
70            Self::Satisfies => "satisfies",
71            Self::IsRefinedBy => "is_refined_by",
72            Self::Derives => "derives",
73            Self::IsSatisfiedBy => "is_satisfied_by",
74            Self::DependsOn => "depends_on",
75            Self::IsRequiredBy => "is_required_by",
76            Self::Specification => "specification",
77            Self::Platform => "platform",
78            Self::JustifiedBy => "justified_by",
79        }
80    }
81
82    /// Returns the human-readable display name.
83    ///
84    /// Used for user-facing output like change summaries.
85    pub const fn display_name(&self) -> &'static str {
86        match self {
87            Self::Id => "ID",
88            Self::Type => "Type",
89            Self::Name => "Name",
90            Self::Description => "Description",
91            Self::Refines => "Refines",
92            Self::DerivesFrom => "Derives from",
93            Self::Satisfies => "Satisfies",
94            Self::IsRefinedBy => "Is refined by",
95            Self::Derives => "Derives",
96            Self::IsSatisfiedBy => "Is satisfied by",
97            Self::DependsOn => "Depends on",
98            Self::IsRequiredBy => "Is required by",
99            Self::Specification => "Specification",
100            Self::Platform => "Platform",
101            Self::JustifiedBy => "Justified by",
102        }
103    }
104
105    /// Returns true if this is an upstream traceability field.
106    pub const fn is_upstream(&self) -> bool {
107        matches!(self, Self::Refines | Self::DerivesFrom | Self::Satisfies)
108    }
109
110    /// Returns true if this is a downstream traceability field.
111    pub const fn is_downstream(&self) -> bool {
112        matches!(
113            self,
114            Self::IsRefinedBy | Self::Derives | Self::IsSatisfiedBy
115        )
116    }
117
118    /// Returns true if this is a peer relationship field.
119    pub const fn is_peer(&self) -> bool {
120        matches!(self, Self::DependsOn | Self::IsRequiredBy)
121    }
122
123    /// Returns true if this is a traceability field (upstream, downstream, or peer).
124    pub const fn is_traceability(&self) -> bool {
125        self.is_upstream() || self.is_downstream() || self.is_peer()
126    }
127}
128
129impl std::fmt::Display for FieldName {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        write!(f, "{}", self.as_str())
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_field_name_as_str() {
141        assert_eq!(FieldName::Id.as_str(), "id");
142        assert_eq!(FieldName::DerivesFrom.as_str(), "derives_from");
143        assert_eq!(FieldName::Specification.as_str(), "specification");
144    }
145
146    #[test]
147    fn test_field_name_is_upstream() {
148        assert!(FieldName::Refines.is_upstream());
149        assert!(FieldName::DerivesFrom.is_upstream());
150        assert!(FieldName::Satisfies.is_upstream());
151        assert!(!FieldName::IsRefinedBy.is_upstream());
152        assert!(!FieldName::Specification.is_upstream());
153    }
154
155    #[test]
156    fn test_field_name_is_downstream() {
157        assert!(FieldName::IsRefinedBy.is_downstream());
158        assert!(FieldName::Derives.is_downstream());
159        assert!(FieldName::IsSatisfiedBy.is_downstream());
160        assert!(!FieldName::Refines.is_downstream());
161    }
162
163    #[test]
164    fn test_field_name_all() {
165        let all = FieldName::all();
166        assert!(all.contains(&FieldName::Id));
167        assert!(all.contains(&FieldName::Refines));
168        assert!(all.contains(&FieldName::Specification));
169        assert_eq!(all.len(), 15);
170    }
171
172    #[test]
173    fn test_field_name_display() {
174        assert_eq!(format!("{}", FieldName::DerivesFrom), "derives_from");
175    }
176}