issue_states/resolution.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//! State resolution facilities
27//!
28//! This module provides the `Resolvable` trait for resolution of a given
29//! issue's state as well as types implementing it for issue state containers.
30//!
31
32use std::collections;
33use std::slice;
34use std::sync::Arc;
35
36use condition::Condition;
37use error::*;
38use iter::LeftJoinable;
39use state;
40
41
42
43
44/// Map for tracking enabled and disabled states
45///
46type EnabledMap<C> = collections::BTreeMap<Arc<state::IssueState<C>>, bool>;
47
48
49/// Check whether the dependencies for an issue's state allow it to be enabled
50///
51/// For example, an issue state may only be enabled if all the states it extends
52/// are enabled. The computation is done purely based on a given `EnabledMap`,
53/// e.g. this function does not recurse into extended states.
54///
55/// This function may be used for implementing efficient computation of an
56/// issue's state.
57///
58fn deps_enabled<C>(state: &state::IssueState<C>, map: &EnabledMap<C>) -> Result<bool>
59 where C: Condition
60{
61 state
62 .relations
63 .iter()
64 .join_left(map.iter())
65 .filter_map(|item| match item.0 {
66 state::StateRelation::Extends => Some(item.1),
67 state::StateRelation::Overrides => None,
68 })
69 .fold(Some(true), |state, val| if let (Some(s), Some(v)) = (state, val) {
70 Some(s && *v)
71 } else {
72 None
73 })
74 .ok_or_else(|| Error::from(ErrorKind::DependencyError))
75 // TODO: replace with try_fold()
76}
77
78
79/// Trait providing operation for resolving issues' states
80///
81/// Implementations of trait provide the reesolution of an issue's state. It is
82/// generally assumed that the implementation encapsulates the states considered
83/// for the resolution. For example, this may be implemented for containers of
84/// issue states.
85///
86pub trait Resolvable<C>
87 where C: Condition
88{
89 /// Resolve the state for a given issue
90 ///
91 /// Given an issue, this function will yield the state selected for it out
92 /// of the issue states encapsulated in `self` --if any of the states is
93 /// enabled for the issue.
94 ///
95 /// If no state is enabled for the given issue, this function will yield
96 /// `None`.
97 ///
98 fn issue_state(&self, issue: &C::Issue) -> Result<Option<Arc<state::IssueState<C>>>>;
99}
100
101
102
103
104/// Set of issue states
105///
106/// This set of issue states is intended for the efficient computation of an
107/// issue's state.
108///
109pub struct IssueStateSet<C>
110 where C: Condition
111{
112 /// Container of states
113 ///
114 /// The states are kept in a linear sequence, ordered by dependency:
115 /// an iterator over the slice will yield a state only after all its
116 /// dependencies are yielded. Dependencies in this context are states
117 /// which are extended or overridden by the yielded state.
118 ///
119 data: Box<[Arc<state::IssueState<C>>]>,
120}
121
122
123impl<C> IssueStateSet<C>
124 where C: Condition
125{
126 /// Create an issue state set from a orderd set of issue states
127 ///
128 /// # Note:
129 ///
130 /// The set provided must be the (transitive) closure of all its elements
131 /// regarding its relations to other sets: if a state is in the set, all
132 /// states related to it must also be in the set. No explicit checking is
133 /// performed to assert this property.
134 ///
135 pub fn from_set(mut states: collections::BTreeSet<Arc<state::IssueState<C>>>) -> Result<Self> {
136 // We generate the state set by transferring states from the origin set
137 // (`states`) to the result sequence (`data`) dependencies first.
138 let mut data = Vec::default();
139 while !states.is_empty() {
140 let old_len = data.len();
141
142 // We add all states for which no dependencies are left in the
143 // origin set
144 data.extend(states
145 .iter()
146 .filter(|state| !state
147 .relations
148 .iter()
149 .join_left(states.iter().map(|item| (item, ())))
150 .any(|item| item.1.is_some())
151 )
152 .map(Clone::clone));
153
154 // Remove the states which are new in the target
155 for state in data.split_at(old_len).1 {
156 states.remove(state);
157 }
158
159 // If we did not find any state with no dependencies, there must be
160 // a dependency cycle in the remaining origin set. We do this after
161 // the removal for better reporting... eventually.
162 if data.len() == old_len {
163 return Err(Error::from(ErrorKind::CyclicDependency));
164 }
165 }
166
167 Ok(Self {data: data.into_boxed_slice()})
168 }
169
170 /// Get an iterator for iterating over the issue states within the set
171 ///
172 /// This iterator will yield an issue state only after all its dependencies.
173 ///
174 pub fn iter(&self) -> slice::Iter<Arc<state::IssueState<C>>> {
175 self.data.iter()
176 }
177}
178
179
180impl<C> Resolvable<C> for IssueStateSet<C>
181 where C: Condition
182{
183 fn issue_state(&self, issue: &C::Issue) -> Result<Option<Arc<state::IssueState<C>>>> {
184 let mut retval = None;
185 let mut enabled_map = EnabledMap::default();
186
187 // Since the data is nicely ordered in `data`, one liear pass over the
188 // states is sufficient for selecting one for any given issue. We simply
189 // determine the state foe each one as we go and keep the last of the
190 // enabled states.
191 for state in self.data.iter() {
192 let enabled = state.conditions_satisfied(issue)
193 && deps_enabled(&state, &enabled_map)?;
194 enabled_map.insert(state.clone(), enabled);
195 if enabled {
196 retval = Some(state);
197 }
198 }
199
200 Ok(retval.map(Clone::clone))
201 }
202}
203
204
205/// Create an issue state set directly from a vector
206///
207/// # Warning
208///
209/// Within the vector, the states must appear ordered by dependency: all
210/// dependencies of a state must appear before the state itself!
211///
212impl<C> From<state::IssueStateVec<C>> for IssueStateSet<C>
213 where C: Condition
214{
215 fn from(states: Vec<Arc<state::IssueState<C>>>) -> Self {
216 Self {data: states.into_boxed_slice()}
217 }
218}
219
220
221// Because #[derive(Default)] doesn't work for some reason
222impl<C> Default for IssueStateSet<C>
223 where C: Condition
224{
225 fn default() -> Self {
226 Self {data: Default::default()}
227 }
228}
229
230
231
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use test::TestState;
237
238 #[test]
239 fn smoke() {
240 let state1 : Arc<TestState> = state::IssueState::new("new".to_string()).into();
241
242 let state2 : Arc<TestState> = {
243 let mut tmp = state::IssueState::new("acknowledged".to_string());
244 tmp.conditions = vec!["acked".into()];
245 tmp.add_overridden([state1.clone()].into_iter().map(Clone::clone));
246 tmp
247 }.into();
248
249 let state3 : Arc<TestState> = {
250 let mut tmp = state::IssueState::new("assigned".to_string());
251 tmp.conditions = vec!["assigned".into()];
252 tmp.add_extended([state2.clone()].into_iter().map(Clone::clone));
253 tmp
254 }.into();
255
256 let state4 : Arc<TestState> = {
257 let mut tmp = state::IssueState::new("closed".to_string());
258 tmp.conditions = vec!["closed".into()];
259 tmp.add_overridden([state3.clone()].into_iter().map(Clone::clone));
260 tmp
261 }.into();
262
263 let states = IssueStateSet::from_set({
264 let mut set = collections::BTreeSet::new();
265 set.insert(state1);
266 set.insert(state2);
267 set.insert(state3);
268 set.insert(state4);
269 set
270 }).expect("Failed to create issue state set.");
271
272 {
273 let state = states
274 .issue_state(&collections::BTreeMap::new())
275 .expect("Failed to determine state.")
276 .expect("Wrongly determined no state.");
277 assert_eq!(state.name(), "new");
278 }
279
280 {
281 let mut issue = collections::BTreeMap::new();
282 issue.insert("acked", true);
283 let state = states
284 .issue_state(&issue)
285 .expect("Failed to determine state.")
286 .expect("Wrongly determined no state.");
287 assert_eq!(state.name(), "acknowledged");
288 }
289
290 {
291 let mut issue = collections::BTreeMap::new();
292 issue.insert("assigned", true);
293 let state = states
294 .issue_state(&issue)
295 .expect("Failed to determine state.")
296 .expect("Wrongly determined no state.");
297 assert_eq!(state.name(), "new");
298 }
299
300 {
301 let mut issue = collections::BTreeMap::new();
302 issue.insert("acked", true);
303 issue.insert("assigned", true);
304 let state = states
305 .issue_state(&issue)
306 .expect("Failed to determine state.")
307 .expect("Wrongly determined no state.");
308 assert_eq!(state.name(), "assigned");
309 }
310
311 {
312 let mut issue = collections::BTreeMap::new();
313 issue.insert("acked", true);
314 issue.insert("closed", true);
315 let state = states
316 .issue_state(&issue)
317 .expect("Failed to determine state.")
318 .expect("Wrongly determined no state.");
319 assert_eq!(state.name(), "closed");
320 }
321 }
322}
323