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