issue_states/
state.rs

1// Issue states
2//
3// Copyright (c) 2018 Julian Ganz
4//
5// MIT License
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in all
15// copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23// SOFTWARE.
24//
25
26//! Issue states and conditions
27//!
28//! This module provides the datamodel or representation of issue states.
29//!
30
31use std::collections::BTreeMap;
32use std::cmp::Ordering;
33use std::sync::Arc;
34
35use condition::Condition;
36
37
38
39
40/// Enumeration type for classificatoin of relations
41///
42/// Instances of this enum describe the relation between two states.
43///
44#[derive(PartialEq, Eq, Debug, Clone)]
45pub enum StateRelation {
46    /// The issue extends another state
47    ///
48    /// All conditions are inherited. If both the extending and the extended
49    /// state are enabled for an issue, the extending state is chosen.
50    Extends,
51    /// The issue overrides another state
52    ///
53    /// If both the overriding and the overridden state are enabled for an
54    /// issue, the overriding state is chosen. However, no conditions are
55    /// inherited from the overridden state.
56    Overrides,
57}
58
59
60
61
62/// Convenience of the description of a state's relation to ther states
63///
64pub type StateRelations<C> = BTreeMap<Arc<IssueState<C>>, StateRelation>;
65
66
67/// Representaiton of an issue state
68///
69/// An issue state is a named transient property depending on an issue's
70/// metadata. For a given issue, a state is either enabled or disabled based on
71/// the `Conditions` attached to state. Additionally, a state may or may not be
72/// related to other issues. Those relations affect whether a state is selected
73/// by a resolver for a given issue, provided that it is enabled for saif issue.
74///
75pub struct IssueState<C>
76    where C: Condition + Sized
77{
78    /// The name of the state
79    name: String,
80    /// Metadata conditions of the state
81    pub conditions: Vec<C>,
82    /// Relations to ther states
83    pub relations: StateRelations<C>,
84}
85
86
87impl<C> IssueState<C>
88    where C: Condition + Sized
89{
90    /// Create an issue state with a given name
91    ///
92    pub fn new(name: String) -> Self {
93        Self {
94            name: name,
95            conditions: Vec::new(),
96            relations: StateRelations::new(),
97        }
98    }
99
100    /// Retrieve the name of the issue state
101    ///
102    pub fn name(&self) -> &String {
103        &self.name
104    }
105
106    /// Add states on which this state depends on
107    ///
108    pub fn add_extended<I>(&mut self, dependencies: I)
109        where I: IntoIterator<Item = Arc<IssueState<C>>>
110    {
111        let entries = dependencies
112            .into_iter()
113            .map(|state| (state, StateRelation::Extends));
114        self.relations.extend(entries)
115    }
116
117    /// Add states which will override this state
118    ///
119    pub fn add_overridden<I>(&mut self, overridden_by: I)
120        where I: IntoIterator<Item = Arc<IssueState<C>>>
121    {
122        let entries = overridden_by
123            .into_iter()
124            .map(|state| (state, StateRelation::Overrides));
125        self.relations.extend(entries)
126    }
127
128    /// Check whether all conditions of the state are satisfied for an issue
129    ///
130    /// # Note:
131    ///
132    /// Conditions inherited from states extended by this state are not
133    /// considered. Thus, this function alone can not be used for assessing
134    /// whether the state is enabled or not.
135    ///
136    pub fn conditions_satisfied(&self, issue: &C::Issue) -> bool {
137        self.conditions.iter().all(|c| c.satisfied_by(issue))
138    }
139}
140
141
142impl<C> PartialEq for IssueState<C>
143    where C: Condition
144{
145    fn eq(&self, other: &Self) -> bool {
146        self.name == other.name
147    }
148}
149
150
151impl<C> Eq for IssueState<C>
152    where C: Condition
153{}
154
155
156impl<C> PartialOrd for IssueState<C>
157    where C: Condition
158{
159    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
160        Some(self.cmp(&other))
161    }
162}
163
164
165impl<C> Ord for IssueState<C>
166    where C: Condition
167{
168    fn cmp(&self, other: &Self) -> Ordering {
169        self.name.cmp(&other.name)
170    }
171}
172
173
174
175
176/// Convenience type for a vector of issue states
177///
178pub type IssueStateVec<C> = Vec<Arc<IssueState<C>>>;
179
180
181
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use test::TestState;
187
188    #[test]
189    fn smoke() {
190        let mut issue = BTreeMap::new();
191        issue.insert("foo", true);
192        issue.insert("bar", true);
193        issue.insert("baz", false);
194
195        let mut state : TestState = IssueState::new("state".to_string());
196        state.conditions = vec!["foo".into(), "bar".into()];
197        assert!(state.conditions_satisfied(&issue));
198
199        state.conditions = vec!["foo".into(), "baz".into()];
200        assert!(!state.conditions_satisfied(&issue));
201    }
202}
203