ic_backup/policy/selection/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 inventory::{
6 InventoryRecord, InventoryRecordError, InventoryTargetRecord, MAX_INVENTORY_TARGETS,
7 },
8};
9use std::collections::{BTreeSet, VecDeque};
10use thiserror::Error;
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum SelectionExpansion {
15 Exact,
17 DirectChildren,
19 Descendants,
21}
22
23#[derive(Clone, Debug)]
25pub struct SelectionRequest {
26 pub canister_ids: Vec<String>,
28 pub expansion: SelectionExpansion,
30}
31
32#[derive(Clone, Debug)]
34pub struct SelectionView<'a> {
35 pub inventory: ArtifactChecksumRecord,
37 pub targets: Vec<&'a InventoryTargetRecord>,
41}
42
43pub fn select<'a>(
48 inventory: &'a InventoryRecord,
49 request: &SelectionRequest,
50) -> Result<SelectionView<'a>, SelectionError> {
51 if request.canister_ids.is_empty() {
52 return Err(SelectionError::EmptySelection);
53 }
54 if request.canister_ids.len() > MAX_INVENTORY_TARGETS {
55 return Err(SelectionError::TooManySelectors);
56 }
57 let mut selected = BTreeSet::new();
58 let mut queue = VecDeque::new();
59 for id in &request.canister_ids {
60 let target = inventory.target(id)?;
61 if !selected.insert(target.canister_id()) {
62 return Err(SelectionError::DuplicateSelector(
63 target.canister_id().into(),
64 ));
65 }
66 queue.push_back(target.canister_id());
67 }
68 if request.expansion != SelectionExpansion::Exact {
69 while let Some(parent) = queue.pop_front() {
70 for child in inventory
71 .targets()
72 .iter()
73 .filter(|target| target.parent_canister_id() == Some(parent))
74 {
75 if selected.insert(child.canister_id())
76 && request.expansion == SelectionExpansion::Descendants
77 {
78 queue.push_back(child.canister_id());
79 }
80 }
81 }
82 }
83 Ok(SelectionView {
84 inventory: inventory.digest(),
85 targets: inventory
86 .targets()
87 .iter()
88 .filter(|target| selected.contains(target.canister_id()))
89 .collect(),
90 })
91}
92
93#[derive(Debug, Error)]
95pub enum SelectionError {
96 #[error("selection contains no targets")]
98 EmptySelection,
99 #[error("selection exceeds {MAX_INVENTORY_TARGETS} selectors")]
101 TooManySelectors,
102 #[error("duplicate physical selector {0}")]
104 DuplicateSelector(String),
105 #[error(transparent)]
107 Inventory(#[from] InventoryRecordError),
108}
109
110#[cfg(test)]
111mod tests;