bark/movement/
update.rs

1use std::collections::HashMap;
2use std::hash::Hash;
3
4use bdk_esplora::esplora_client::Amount;
5use bitcoin::SignedAmount;
6use chrono::DateTime;
7
8use ark::vtxo::VtxoRef;
9use ark::VtxoId;
10
11use crate::movement::{Movement, MovementDestination};
12
13/// Informs [Movement::apply_update] how to apply a [MovementUpdate].
14#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
15enum UpdateMethod<T> {
16	/// Combines the given values with any existing values, ensuring no duplicates are present.
17	Merge(T),
18	/// Replaces any existing values with the given value.
19	Replace(T),
20}
21
22/// A struct to allow easy updating of a [Movement]. Each field can be set individually; however,
23/// methods are available to allow construction of an update in a more expressive/declarative way.
24///
25/// Each [Option] field that is set to `None` will be ignored. The default behavior of each field is
26/// to merge existing values with the new ones unless a field is explicitly set to
27/// `UpdateMethod::Replace` or a method indicates otherwise. Duplicate [VtxoId] values will be
28/// ignored.
29///
30/// See `UpdateMethod` to understand how to control how the [MovementUpdate] is applied.
31#[derive(Debug, Clone)]
32pub struct MovementUpdate {
33	intended_balance: Option<SignedAmount>,
34	effective_balance: Option<SignedAmount>,
35	offchain_fee: Option<Amount>,
36	sent_to: Option<UpdateMethod<Vec<MovementDestination>>>,
37	received_on: Option<UpdateMethod<Vec<MovementDestination>>>,
38	consumed_vtxos: Option<UpdateMethod<Vec<VtxoId>>>,
39	produced_vtxos: Option<UpdateMethod<Vec<VtxoId>>>,
40	exited_vtxos: Option<UpdateMethod<Vec<VtxoId>>>,
41	metadata: Option<UpdateMethod<HashMap<String, serde_json::Value>>>,
42}
43
44impl MovementUpdate {
45	pub fn new() -> Self {
46		Self {
47			intended_balance: None,
48			effective_balance: None,
49			offchain_fee: None,
50			sent_to: None,
51			received_on: None,
52			consumed_vtxos: None,
53			produced_vtxos: None,
54			exited_vtxos: None,
55			metadata: None,
56		}
57	}
58
59	pub fn consumed_vtxo(self, vtxo: impl VtxoRef) -> Self {
60		self.consumed_vtxos([vtxo.vtxo_id()])
61	}
62
63	pub fn consumed_vtxo_if_some(self, vtxo: Option<impl VtxoRef>) -> Self {
64		if let Some(vtxo) = vtxo {
65			self.consumed_vtxo(vtxo)
66		} else {
67			self
68		}
69	}
70
71	pub fn consumed_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
72		let vtxos = vtxos.into_iter().map(|vtxo| vtxo.vtxo_id());
73		match &mut self.consumed_vtxos {
74			None => self.consumed_vtxos = Some(UpdateMethod::Merge(vtxos.collect())),
75			Some(vec) => vec.merge(vtxos),
76		}
77		self
78	}
79
80	pub fn effective_balance(mut self, effective: SignedAmount) -> Self {
81		self.effective_balance = Some(effective);
82		self
83	}
84
85	pub fn exited_vtxo(self, vtxo: impl VtxoRef) -> Self {
86		self.exited_vtxos([vtxo.vtxo_id()])
87	}
88
89	pub fn exited_vtxo_if_some(self, vtxo: Option<impl VtxoRef>) -> Self {
90		if let Some(vtxo) = vtxo {
91			self.exited_vtxo(vtxo)
92		} else {
93			self
94		}
95	}
96
97	pub fn exited_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
98		let vtxos = vtxos.into_iter().map(|vtxo| vtxo.vtxo_id());
99		match &mut self.exited_vtxos {
100			None => self.exited_vtxos = Some(UpdateMethod::Merge(vtxos.collect())),
101			Some(vec) => vec.merge(vtxos),
102		}
103		self
104	}
105
106	pub fn fee(mut self, offchain_fee: Amount) -> Self {
107		self.offchain_fee = Some(offchain_fee);
108		self
109	}
110
111	pub fn intended_balance(mut self, intended: SignedAmount) -> Self {
112		self.intended_balance = Some(intended);
113		self
114	}
115
116	pub fn intended_and_effective_balance(mut self, balance: SignedAmount) -> Self {
117		self.intended_balance = Some(balance);
118		self.effective_balance = Some(balance);
119		self
120	}
121
122	pub fn metadata(
123		mut self,
124		metadata: impl IntoIterator<Item = (String, serde_json::Value)>,
125	) -> Self {
126		match &mut self.metadata {
127			None => self.metadata = Some(UpdateMethod::Merge(metadata.into_iter().collect())),
128			Some(map) => map.insert(metadata),
129		}
130		self
131	}
132
133	pub fn produced_vtxo(self, vtxo: impl VtxoRef) -> Self {
134		self.produced_vtxos([vtxo])
135	}
136
137	pub fn produced_vtxo_if_some(self, vtxo: Option<impl VtxoRef>) -> Self {
138		if let Some(vtxo) = vtxo {
139			self.produced_vtxo(vtxo)
140		} else {
141			self
142		}
143	}
144
145	pub fn produced_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
146		let vtxos = vtxos.into_iter().map(|v| v.vtxo_id());
147		match &mut self.produced_vtxos {
148			None => self.produced_vtxos = Some(UpdateMethod::Merge(vtxos.collect())),
149			Some(vec) => vec.merge(vtxos),
150		}
151		self
152	}
153
154	pub fn received_on(mut self, received: impl IntoIterator<Item = MovementDestination>) -> Self {
155		match &mut self.received_on {
156			None => self.received_on = Some(UpdateMethod::Merge(received.into_iter().collect())),
157			Some(vec) => vec.merge(received),
158		}
159		self
160	}
161
162	pub fn sent_to(mut self, destinations: impl IntoIterator<Item = MovementDestination>) -> Self {
163		match &mut self.sent_to {
164			None => self.sent_to = Some(UpdateMethod::Merge(destinations.into_iter().collect())),
165			Some(vec) => vec.merge(destinations),
166		}
167		self
168	}
169
170	pub fn replace_consumed_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
171		self.consumed_vtxos = Some(UpdateMethod::Replace(
172			vtxos.into_iter().map(|v| v.vtxo_id()).collect(),
173		));
174		self
175	}
176
177	pub fn replace_exited_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
178		self.exited_vtxos = Some(UpdateMethod::Replace(
179			vtxos.into_iter().map(|v| v.vtxo_id()).collect(),
180		));
181		self
182	}
183
184	pub fn replace_metadata(
185		mut self,
186		metadata: impl IntoIterator<Item = (String, serde_json::Value)>,
187	) -> Self {
188		self.metadata = Some(UpdateMethod::Replace(metadata.into_iter().collect()));
189		self
190	}
191
192	pub fn replace_produced_vtxos(mut self, vtxos: impl IntoIterator<Item = impl VtxoRef>) -> Self {
193		self.produced_vtxos = Some(UpdateMethod::Replace(
194			vtxos.into_iter().map(|v| v.vtxo_id()).collect(),
195		));
196		self
197	}
198
199	pub fn replace_received_on(
200		mut self,
201		received: impl IntoIterator<Item = MovementDestination>,
202	) -> Self {
203		self.received_on = Some(UpdateMethod::Replace(
204			received.into_iter().collect(),
205		));
206		self
207	}
208
209	pub fn replace_sent_on(
210		mut self,
211		destinations: impl IntoIterator<Item = MovementDestination>,
212	) -> Self {
213		self.sent_to = Some(UpdateMethod::Replace(
214			destinations.into_iter().collect(),
215		));
216		self
217	}
218
219	pub fn apply_to(self, movement: &mut Movement, at: DateTime<chrono::Local>) {
220		movement.time.updated_at = at;
221		if let Some(metadata) = self.metadata {
222			metadata.apply_to(&mut movement.metadata);
223		}
224		if let Some(intended) = self.intended_balance {
225			movement.intended_balance = intended;
226		}
227		if let Some(effective) = self.effective_balance {
228			movement.effective_balance = effective;
229		}
230		if let Some(offchain_fee) = self.offchain_fee {
231			movement.offchain_fee = offchain_fee;
232		}
233		if let Some(sent_to) = self.sent_to {
234			sent_to.apply_to(&mut movement.sent_to);
235		}
236		if let Some(received_on) = self.received_on {
237			received_on.apply_to(&mut movement.received_on);
238		}
239		if let Some(input_vtxos) = self.consumed_vtxos {
240			input_vtxos.apply_unique_to(&mut movement.input_vtxos);
241		}
242		if let Some(output_vtxos) = self.produced_vtxos {
243			output_vtxos.apply_unique_to(&mut movement.output_vtxos);
244		}
245		if let Some(exited_vtxos) = self.exited_vtxos {
246			exited_vtxos.apply_unique_to(&mut movement.exited_vtxos);
247		}
248	}
249}
250
251impl<T: PartialEq + Eq> UpdateMethod<Vec<T>> {
252	pub fn apply_to(self, target: &mut Vec<T>) {
253		match self {
254			UpdateMethod::Merge(vec) => target.extend(vec),
255			UpdateMethod::Replace(vec) => *target = vec,
256		}
257	}
258
259	pub fn apply_unique_to(self, target: &mut Vec<T>) {
260		match self {
261			UpdateMethod::Merge(vec) => {
262				for value in vec {
263					if !target.contains(&value) {
264						target.push(value);
265					}
266				}
267			},
268			UpdateMethod::Replace(vec) => {
269				target.clear();
270				target.reserve(vec.len());
271				for value in vec {
272					if !target.contains(&value) {
273						target.push(value);
274					}
275				}
276			},
277		}
278	}
279
280	pub fn merge(&mut self, values: impl IntoIterator<Item = T>) {
281		let vec = match self {
282			UpdateMethod::Merge(vec) => vec,
283			UpdateMethod::Replace(vec) => vec,
284		};
285		values.into_iter().for_each(|value| vec.push(value));
286	}
287}
288
289impl<K: Eq + Hash, V> UpdateMethod<HashMap<K, V>> {
290	pub fn apply_to(self, target: &mut HashMap<K, V>) {
291		match self {
292			UpdateMethod::Merge(map) => target.extend(map),
293			UpdateMethod::Replace(map) => *target = map,
294		}
295	}
296
297	pub fn insert(&mut self, key_pairs: impl IntoIterator<Item = (K, V)>) {
298		let map = match self {
299			UpdateMethod::Merge(map) => map,
300			UpdateMethod::Replace(map) => map,
301		};
302		for (key, value) in key_pairs {
303			map.insert(key, value);
304		}
305	}
306}