recoco_core/setup/
components.rs1use super::{CombinedState, ResourceSetupChange, SetupChangeType, StateChange};
14use crate::prelude::*;
15
16pub trait State<Key>: Debug + Send + Sync {
17 fn key(&self) -> Key;
18}
19
20#[async_trait]
21pub trait SetupOperator: 'static + Send + Sync {
22 type Key: Debug + Hash + Eq + Clone + Send + Sync;
23 type State: State<Self::Key>;
24 type SetupState: Send + Sync + IntoIterator<Item = Self::State>;
25 type Context: Sync;
26
27 fn describe_key(&self, key: &Self::Key) -> String;
28
29 fn describe_state(&self, state: &Self::State) -> String;
30
31 fn is_up_to_date(&self, current: &Self::State, desired: &Self::State) -> bool;
32
33 async fn create(&self, state: &Self::State, context: &Self::Context) -> Result<()>;
34
35 async fn delete(&self, key: &Self::Key, context: &Self::Context) -> Result<()>;
36
37 async fn update(&self, state: &Self::State, context: &Self::Context) -> Result<()> {
38 self.delete(&state.key(), context).await?;
39 self.create(state, context).await
40 }
41}
42
43#[derive(Debug)]
44struct CompositeStateUpsert<S> {
45 state: S,
46 already_exists: bool,
47}
48#[derive(Debug)]
49pub struct SetupChange<D: SetupOperator> {
50 desc: D,
51 keys_to_delete: IndexSet<D::Key>,
52 states_to_upsert: Vec<CompositeStateUpsert<D::State>>,
53}
54
55impl<D: SetupOperator> SetupChange<D> {
56 pub fn create(
57 desc: D,
58 desired: Option<D::SetupState>,
59 existing: CombinedState<D::SetupState>,
60 ) -> Result<Self> {
61 let existing_component_states = CombinedState {
62 current: existing.current.map(|s| {
63 s.into_iter()
64 .map(|s| (s.key(), s))
65 .collect::<IndexMap<_, _>>()
66 }),
67 staging: existing
68 .staging
69 .into_iter()
70 .map(|s| match s {
71 StateChange::Delete => StateChange::Delete,
72 StateChange::Upsert(s) => {
73 StateChange::Upsert(s.into_iter().map(|s| (s.key(), s)).collect())
74 }
75 })
76 .collect(),
77 legacy_state_key: existing.legacy_state_key,
78 };
79 let mut keys_to_delete = IndexSet::new();
80 let mut states_to_upsert = vec![];
81
82 for c in existing_component_states.possible_versions() {
84 keys_to_delete.extend(c.keys().cloned());
85 }
86
87 if let Some(desired_state) = desired {
88 for desired_comp_state in desired_state {
89 let key = desired_comp_state.key();
90
91 keys_to_delete.shift_remove(&key);
93
94 let is_up_to_date = existing_component_states.always_exists()
96 && existing_component_states.possible_versions().all(|v| {
97 v.get(&key)
98 .is_some_and(|s| desc.is_up_to_date(s, &desired_comp_state))
99 });
100 if !is_up_to_date {
101 let already_exists = existing_component_states
102 .possible_versions()
103 .any(|v| v.contains_key(&key));
104 states_to_upsert.push(CompositeStateUpsert {
105 state: desired_comp_state,
106 already_exists,
107 });
108 }
109 }
110 }
111
112 Ok(Self {
113 desc,
114 keys_to_delete,
115 states_to_upsert,
116 })
117 }
118}
119
120impl<D: SetupOperator + Send + Sync> ResourceSetupChange for SetupChange<D> {
121 fn describe_changes(&self) -> Vec<setup::ChangeDescription> {
122 let mut result = vec![];
123
124 for key in &self.keys_to_delete {
125 result.push(setup::ChangeDescription::Action(format!(
126 "Delete {}",
127 self.desc.describe_key(key)
128 )));
129 }
130
131 for state in &self.states_to_upsert {
132 result.push(setup::ChangeDescription::Action(format!(
133 "{} {}",
134 if state.already_exists {
135 "Update"
136 } else {
137 "Create"
138 },
139 self.desc.describe_state(&state.state)
140 )));
141 }
142
143 result
144 }
145
146 fn change_type(&self) -> SetupChangeType {
147 if self.keys_to_delete.is_empty() && self.states_to_upsert.is_empty() {
148 SetupChangeType::NoChange
149 } else if self.keys_to_delete.is_empty() {
150 SetupChangeType::Create
151 } else if self.states_to_upsert.is_empty() {
152 SetupChangeType::Delete
153 } else {
154 SetupChangeType::Update
155 }
156 }
157}
158
159pub async fn apply_component_changes<D: SetupOperator>(
160 changes: Vec<&SetupChange<D>>,
161 context: &D::Context,
162) -> Result<()> {
163 for change in changes.iter() {
165 for key in &change.keys_to_delete {
166 change.desc.delete(key, context).await?;
167 }
168 }
169
170 for change in changes.iter() {
172 for state in &change.states_to_upsert {
173 if state.already_exists {
174 change.desc.update(&state.state, context).await?;
175 } else {
176 change.desc.create(&state.state, context).await?;
177 }
178 }
179 }
180
181 Ok(())
182}
183
184impl<A: ResourceSetupChange, B: ResourceSetupChange> ResourceSetupChange for (A, B) {
185 fn describe_changes(&self) -> Vec<setup::ChangeDescription> {
186 let mut result = vec![];
187 result.extend(self.0.describe_changes());
188 result.extend(self.1.describe_changes());
189 result
190 }
191
192 fn change_type(&self) -> SetupChangeType {
193 match (self.0.change_type(), self.1.change_type()) {
194 (SetupChangeType::Invalid, _) | (_, SetupChangeType::Invalid) => {
195 SetupChangeType::Invalid
196 }
197 (SetupChangeType::NoChange, b) => b,
198 (a, _) => a,
199 }
200 }
201}