fastobo/semantics/mod.rs
1//! Selection of useful traits that exceed the syntactic scope.
2
3use crate::ast::*;
4use crate::error::CardinalityError;
5
6mod treat_xrefs;
7pub(crate) use self::treat_xrefs::*;
8
9/// The cardinality constraint for a given clause type.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum Cardinality {
12 ZeroOrOne,
13 One,
14 NotOne,
15 Any,
16}
17
18impl Cardinality {
19 /// Return `true` if the cardinality matches the gievn element count.
20 pub fn is_match(&self, n: usize) -> bool {
21 match self {
22 Cardinality::ZeroOrOne => n < 2,
23 Cardinality::One => n == 1,
24 Cardinality::NotOne => n != 1,
25 Cardinality::Any => true,
26 }
27 }
28
29 /// Given a tag name, build an error from this cardinality if the count does not match.
30 pub fn to_error<S: Into<String>>(&self, n: usize, tag: S) -> Option<CardinalityError> {
31 use self::CardinalityError::*;
32 let name = tag.into();
33 match self {
34 Cardinality::ZeroOrOne if n > 1 => Some(DuplicateClauses { name }),
35 Cardinality::One if n == 0 => Some(MissingClause { name }),
36 Cardinality::One if n > 1 => Some(DuplicateClauses { name }),
37 Cardinality::NotOne if n == 1 => Some(SingleClause { name }),
38 _ => None,
39 }
40 }
41}
42
43/// A trait for structs that can be sorted in an order specified in the OBO spec.
44pub trait Orderable {
45 /// Sort the elements of the collection in the right serialization order.
46 ///
47 /// # See Also
48 /// - The [Serializer conventions](https://owlcollab.github.io/oboformat/doc/GO.format.obo-1_4.html#S.3.5)
49 /// section of the OBO Flat File format guide.
50 fn sort(&mut self);
51
52 /// Check if the collection is sorted in the right serialization order.
53 fn is_sorted(&self) -> bool;
54}
55
56/// Common attributes and operations for all frames.
57pub trait OboFrame {
58 type Clause: OboClause;
59
60 /// Get a vector of references to the clauses of a frame.
61 fn clauses_ref(&self) -> Vec<&Self::Clause>;
62
63 /// Check that the frame only contains clauses with the right cardinality.
64 ///
65 /// # Note
66 /// The current implementation does not check for missing clauses: good
67 /// ergonomics are to be found to provide a collection of required clauses
68 /// in a generic manner.
69 ///
70 fn cardinality_check(&self) -> Result<(), CardinalityError> {
71 use std::collections::HashMap;
72
73 // Group clauses by variant kind
74 let mut clause_index: HashMap<_, Vec<&Self::Clause>> = HashMap::new();
75 for clause in self.clauses_ref() {
76 clause_index.entry(clause.tag()).or_default().push(clause);
77 }
78
79 // Check each variant kind
80 for (tag, clauses) in clause_index {
81 let cardinality = clauses[0].cardinality();
82 if let Some(err) = cardinality.to_error(clauses.len(), tag) {
83 return Err(err);
84 }
85 }
86
87 Ok(())
88 }
89}
90
91/// Common attributes and operations for all clauses.
92pub trait OboClause {
93 /// Get the raw string corresponding to the tag of a clause.
94 ///
95 /// # Example
96 /// ```rust
97 /// # extern crate fastobo;
98 /// # use fastobo::ast::*;
99 /// # use fastobo::semantics::OboClause;
100 /// let clause = HeaderClause::SavedBy(Box::new("Martin Larralde".into()));
101 /// assert_eq!(clause.tag(), "saved-by");
102 /// ```
103 fn tag(&self) -> &str;
104
105 /// Get the cardinality expected for a clause variant.
106 ///
107 /// While most clauses can appear any number of time in a frame, some
108 /// have a constraint on how many time they can appear: for instance,
109 /// a `namespace` clause must appear exactly once in every entity frame,
110 /// and an `intersection_of` clause cannot appear only once.
111 ///
112 /// # Example
113 /// ```rust
114 /// # extern crate fastobo;
115 /// # use fastobo::ast::*;
116 /// # use fastobo::semantics::OboClause;
117 /// # use fastobo::semantics::Cardinality;
118 /// let clause = HeaderClause::SavedBy(Box::new("Martin Larralde".into()));
119 /// assert_eq!(clause.cardinality(), Cardinality::ZeroOrOne);
120 /// ```
121 fn cardinality(&self) -> Cardinality;
122}
123
124/// A trait for structs that have an identifier.
125pub trait Identified {
126 /// Get a reference to the identifier of the entity.
127 fn as_id(&self) -> &Ident;
128
129 /// Get a mutable reference to the identifier of the entity.
130 fn as_id_mut(&mut self) -> &mut Ident;
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 mod cardinality {
138 use super::*;
139
140 use pretty_assertions::assert_eq;
141
142 #[test]
143 fn is_match() {
144 // ZeroOrOne
145 assert!(Cardinality::ZeroOrOne.is_match(0));
146 assert!(Cardinality::ZeroOrOne.is_match(1));
147 assert!(!Cardinality::ZeroOrOne.is_match(2));
148
149 // One
150 assert!(!Cardinality::One.is_match(0));
151 assert!(Cardinality::One.is_match(1));
152 assert!(!Cardinality::One.is_match(2));
153
154 // NotOne
155 assert!(Cardinality::NotOne.is_match(0));
156 assert!(!Cardinality::NotOne.is_match(1));
157 assert!(Cardinality::NotOne.is_match(2));
158
159 // Any
160 assert!(Cardinality::Any.is_match(0));
161 assert!(Cardinality::Any.is_match(1));
162 assert!(Cardinality::Any.is_match(2));
163 }
164
165 #[test]
166 fn to_error() {
167 assert_eq!(Cardinality::ZeroOrOne.to_error(0, "ok"), None);
168 assert_eq!(
169 Cardinality::ZeroOrOne.to_error(2, "ok"),
170 Some(CardinalityError::duplicate("ok"))
171 );
172
173 assert_eq!(Cardinality::One.to_error(1, "ok"), None);
174 assert_eq!(
175 Cardinality::One.to_error(2, "ok"),
176 Some(CardinalityError::duplicate("ok"))
177 );
178 assert_eq!(
179 Cardinality::One.to_error(0, "ok"),
180 Some(CardinalityError::missing("ok"))
181 );
182
183 assert_eq!(Cardinality::NotOne.to_error(0, "ok"), None);
184 assert_eq!(
185 Cardinality::NotOne.to_error(1, "ok"),
186 Some(CardinalityError::single("ok"))
187 );
188
189 assert_eq!(Cardinality::Any.to_error(0, "ok"), None);
190 }
191 }
192}