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