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}