fedimint_client_module/
oplog.rs1use std::fmt::Debug;
2use std::future;
3use std::time::SystemTime;
4
5use fedimint_core::core::OperationId;
6use fedimint_core::db::{Database, DatabaseTransaction};
7use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
8use fedimint_core::module::registry::ModuleDecoderRegistry;
9use fedimint_core::task::{MaybeSend, MaybeSync};
10use fedimint_core::util::BoxStream;
11use fedimint_core::{apply, async_trait_maybe_send};
12use futures::{StreamExt, stream};
13use serde::de::DeserializeOwned;
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(transparent)]
19pub struct JsonStringed(pub serde_json::Value);
20
21#[cfg(feature = "uniffi")]
22uniffi::custom_type!(JsonStringed, String, {
23 lower: |j| serde_json::to_string(&j.0).expect("JSON serialization should not fail"),
24 try_lift: |s| {
25 let value = serde_json::from_str(&s).map_err(|e| DecodeError::from_err(e))?;
26 Ok(JsonStringed(value))
27 },
28});
29
30impl Encodable for JsonStringed {
31 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
32 let json_str = serde_json::to_string(&self.0).expect("JSON serialization should not fail");
33 json_str.consensus_encode(writer)
34 }
35}
36
37impl Decodable for JsonStringed {
38 fn consensus_decode_partial<R: std::io::Read>(
39 r: &mut R,
40 modules: &ModuleDecoderRegistry,
41 ) -> Result<Self, DecodeError> {
42 let json_str = String::consensus_decode_partial(r, modules)?;
43 let value = serde_json::from_str(&json_str).map_err(DecodeError::from_err)?;
44 Ok(JsonStringed(value))
45 }
46}
47
48#[apply(async_trait_maybe_send!)]
49pub trait IOperationLog {
50 async fn get_operation(&self, operation_id: OperationId) -> Option<OperationLogEntry>;
51
52 async fn get_operation_dbtx(
53 &self,
54 dbtx: &mut DatabaseTransaction<'_>,
55 operation_id: OperationId,
56 ) -> Option<OperationLogEntry>;
57
58 async fn add_operation_log_entry_dbtx(
59 &self,
60 dbtx: &mut DatabaseTransaction<'_>,
61 operation_id: OperationId,
62 operation_type: &str,
63 operation_meta: serde_json::Value,
64 );
65
66 fn outcome_or_updates(
67 &self,
68 db: &Database,
69 operation_id: OperationId,
70 operation_log_entry: OperationLogEntry,
71 stream_gen: Box<dyn FnOnce() -> BoxStream<'static, serde_json::Value>>,
72 ) -> UpdateStreamOrOutcome<serde_json::Value>;
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable, PartialEq, Eq)]
78#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
79pub struct OperationOutcome {
80 pub time: SystemTime,
81 pub outcome: JsonStringed,
82}
83
84#[derive(Debug, Serialize, Deserialize, Encodable, Decodable)]
104#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
105pub struct OperationLogEntry {
106 pub(crate) operation_module_kind: String,
107 pub(crate) meta: JsonStringed,
108 pub(crate) outcome: Option<OperationOutcome>,
110}
111
112impl OperationLogEntry {
113 pub fn new(
114 operation_module_kind: String,
115 meta: JsonStringed,
116 outcome: Option<OperationOutcome>,
117 ) -> Self {
118 Self {
119 operation_module_kind,
120 meta,
121 outcome,
122 }
123 }
124
125 pub fn operation_module_kind(&self) -> &str {
127 &self.operation_module_kind
128 }
129
130 pub fn meta<M: DeserializeOwned>(&self) -> M {
136 self.try_meta()
137 .expect("JSON deserialization should not fail")
138 }
139
140 pub fn try_meta<M: DeserializeOwned>(&self) -> Result<M, serde_json::Error> {
144 serde_json::from_value(self.meta.0.clone())
145 }
146
147 pub fn outcome<D: DeserializeOwned>(&self) -> Option<D> {
165 self.try_outcome()
166 .expect("JSON deserialization should not fail")
167 }
168
169 pub fn try_outcome<D: DeserializeOwned>(&self) -> Result<Option<D>, serde_json::Error> {
173 self.outcome
174 .as_ref()
175 .map(|outcome| serde_json::from_value(outcome.outcome.0.clone()))
176 .transpose()
177 }
178
179 pub fn outcome_time(&self) -> Option<SystemTime> {
181 self.outcome.as_ref().map(|o| o.time)
182 }
183
184 pub fn set_outcome(&mut self, outcome: impl Into<Option<OperationOutcome>>) {
185 self.outcome = outcome.into();
186 }
187}
188
189pub enum UpdateStreamOrOutcome<U> {
192 UpdateStream(BoxStream<'static, U>),
193 Outcome(U),
194}
195
196impl<U: Debug> Debug for UpdateStreamOrOutcome<U> {
197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198 match self {
199 UpdateStreamOrOutcome::UpdateStream(_) => write!(f, "UpdateStream"),
200 UpdateStreamOrOutcome::Outcome(o) => f.debug_tuple("Outcome").field(o).finish(),
201 }
202 }
203}
204
205impl<U> UpdateStreamOrOutcome<U>
206where
207 U: MaybeSend + MaybeSync + 'static,
208{
209 pub fn into_stream(self) -> BoxStream<'static, U> {
213 match self {
214 UpdateStreamOrOutcome::UpdateStream(stream) => stream,
215 UpdateStreamOrOutcome::Outcome(outcome) => {
216 Box::pin(stream::once(future::ready(outcome)))
217 }
218 }
219 }
220
221 pub async fn await_outcome(self) -> Option<U> {
225 match self {
226 UpdateStreamOrOutcome::Outcome(outcome) => Some(outcome),
227 UpdateStreamOrOutcome::UpdateStream(mut stream) => {
228 let mut last_update = None;
229 while let Some(update) = stream.next().await {
230 last_update = Some(update);
231 }
232 last_update
233 }
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use futures::stream;
241 use serde_json::Value;
242
243 use super::*;
244
245 #[tokio::test]
246 async fn test_await_outcome_cached() {
247 let test_value = serde_json::json!({"status": "completed", "amount": 100});
248 let cached_outcome = UpdateStreamOrOutcome::Outcome(test_value.clone());
249 let result = cached_outcome.await_outcome().await;
250 assert_eq!(result, Some(test_value));
251 }
252
253 #[tokio::test]
254 async fn test_await_outcome_uncached_with_updates() {
255 let update_stream = Box::pin(stream::iter(vec![
256 Value::from(0),
257 Value::from(1),
258 Value::from(2),
259 ]));
260 let uncached_outcome = UpdateStreamOrOutcome::UpdateStream(update_stream);
261 let result = uncached_outcome.await_outcome().await;
262 assert_eq!(result, Some(Value::from(2)));
263 }
264
265 #[tokio::test]
266 async fn test_await_outcome_uncached_empty_stream() {
267 let empty_stream = Box::pin(stream::empty::<serde_json::Value>());
268 let uncached_outcome = UpdateStreamOrOutcome::UpdateStream(empty_stream);
269 let result = uncached_outcome.await_outcome().await;
270 assert_eq!(result, None);
271 }
272
273 #[tokio::test]
274 async fn test_await_outcome_uncached_single_update() {
275 let update_stream = Box::pin(stream::once(async { Value::from(0) }));
276 let uncached_outcome = UpdateStreamOrOutcome::UpdateStream(update_stream);
277 let result = uncached_outcome.await_outcome().await;
278 assert_eq!(result, Some(Value::from(0)));
279 }
280}