1use std::fmt::Debug;
2use std::future;
3use std::time::SystemTime;
4
5use fedimint_core::core::OperationId;
6use fedimint_core::db::DatabaseTransaction;
7use fedimint_core::encoding::{
8 Decodable, DecodeError, Encodable, decode_field_from_finite_reader,
9 decode_legacy_system_time_from_finite_reader, encode_legacy_system_time, with_decoding_context,
10};
11use fedimint_core::module::registry::ModuleDecoderRegistry;
12use fedimint_core::task::{MaybeSend, MaybeSync};
13use fedimint_core::util::BoxStream;
14use fedimint_core::{apply, async_trait_maybe_send, maybe_add_send, maybe_add_send_sync};
15use futures::{StreamExt, stream};
16use serde::de::DeserializeOwned;
17use serde::{Deserialize, Serialize};
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21#[serde(transparent)]
22pub struct JsonStringed(pub serde_json::Value);
23
24#[cfg(feature = "uniffi")]
25uniffi::custom_type!(JsonStringed, String, {
26 lower: |j| serde_json::to_string(&j.0).expect("JSON serialization should not fail"),
27 try_lift: |s| {
28 let value = serde_json::from_str(&s).map_err(|e| DecodeError::from_err(e))?;
29 Ok(JsonStringed(value))
30 },
31});
32
33impl Encodable for JsonStringed {
34 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
35 let json_str = serde_json::to_string(&self.0).expect("JSON serialization should not fail");
36 json_str.consensus_encode(writer)
37 }
38}
39
40impl Decodable for JsonStringed {
41 fn consensus_decode_partial<R: std::io::Read>(
42 r: &mut R,
43 modules: &ModuleDecoderRegistry,
44 ) -> Result<Self, DecodeError> {
45 let json_str = String::consensus_decode_partial(r, modules)?;
46 let value = serde_json::from_str(&json_str).map_err(DecodeError::from_err)?;
47 Ok(JsonStringed(value))
48 }
49}
50
51#[apply(async_trait_maybe_send!)]
52pub trait IOperationLog {
53 async fn get_operation(&self, operation_id: OperationId) -> Option<OperationLogEntry>;
54
55 async fn get_operation_dbtx(
56 &self,
57 dbtx: &mut DatabaseTransaction<'_>,
58 operation_id: OperationId,
59 ) -> Option<OperationLogEntry>;
60
61 async fn add_operation_log_entry_dbtx(
62 &self,
63 dbtx: &mut DatabaseTransaction<'_>,
64 operation_id: OperationId,
65 operation_type: &str,
66 operation_meta: serde_json::Value,
67 );
68
69 fn caching_operation_update_stream(
77 &self,
78 operation_id: OperationId,
79 stream_gen: Box<maybe_add_send!(dyn FnOnce() -> BoxStream<'static, serde_json::Value>)>,
80 is_terminal: Box<maybe_add_send_sync!(dyn Fn(&serde_json::Value) -> bool)>,
81 ) -> BoxStream<'static, serde_json::Value>;
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
88pub struct OperationOutcome {
89 pub time: SystemTime,
90 pub outcome: JsonStringed,
91}
92
93impl Encodable for OperationOutcome {
94 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
95 encode_legacy_system_time(&self.time, writer)?;
96 self.outcome.consensus_encode(writer)
97 }
98}
99
100impl Decodable for OperationOutcome {
101 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
102 reader: &mut R,
103 modules: &ModuleDecoderRegistry,
104 ) -> Result<Self, DecodeError> {
105 Ok(Self {
106 time: with_decoding_context(
107 decode_legacy_system_time_from_finite_reader(reader, modules),
108 "Decoding named block field: OperationOutcome{ ... time ... }",
109 )?,
110 outcome: decode_field_from_finite_reader(
111 reader,
112 modules,
113 "Decoding named block field: OperationOutcome{ ... outcome ... }",
114 )?,
115 })
116 }
117}
118
119#[derive(Debug, Serialize, Deserialize, Encodable, Decodable)]
139#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
140pub struct OperationLogEntry {
141 pub(crate) operation_module_kind: String,
142 pub(crate) meta: JsonStringed,
143 pub(crate) outcome: Option<OperationOutcome>,
145}
146
147impl OperationLogEntry {
148 pub fn new(
149 operation_module_kind: String,
150 meta: JsonStringed,
151 outcome: Option<OperationOutcome>,
152 ) -> Self {
153 Self {
154 operation_module_kind,
155 meta,
156 outcome,
157 }
158 }
159
160 pub fn operation_module_kind(&self) -> &str {
162 &self.operation_module_kind
163 }
164
165 pub fn meta<M: DeserializeOwned>(&self) -> M {
171 self.try_meta()
172 .expect("JSON deserialization should not fail")
173 }
174
175 pub fn try_meta<M: DeserializeOwned>(&self) -> Result<M, serde_json::Error> {
179 serde_json::from_value(self.meta.0.clone())
180 }
181
182 pub fn outcome<D: DeserializeOwned>(&self) -> Option<D> {
200 self.try_outcome()
201 .expect("JSON deserialization should not fail")
202 }
203
204 pub fn try_outcome<D: DeserializeOwned>(&self) -> Result<Option<D>, serde_json::Error> {
208 self.outcome
209 .as_ref()
210 .map(|outcome| serde_json::from_value(outcome.outcome.0.clone()))
211 .transpose()
212 }
213
214 pub fn outcome_time(&self) -> Option<SystemTime> {
216 self.outcome.as_ref().map(|o| o.time)
217 }
218
219 pub fn set_outcome(&mut self, outcome: impl Into<Option<OperationOutcome>>) {
220 self.outcome = outcome.into();
221 }
222}
223
224pub enum UpdateStreamOrOutcome<U> {
227 UpdateStream(BoxStream<'static, U>),
228 Outcome(U),
229}
230
231impl<U: Debug> Debug for UpdateStreamOrOutcome<U> {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 match self {
234 UpdateStreamOrOutcome::UpdateStream(_) => write!(f, "UpdateStream"),
235 UpdateStreamOrOutcome::Outcome(o) => f.debug_tuple("Outcome").field(o).finish(),
236 }
237 }
238}
239
240impl<U> UpdateStreamOrOutcome<U>
241where
242 U: MaybeSend + MaybeSync + 'static,
243{
244 pub fn into_stream(self) -> BoxStream<'static, U> {
248 match self {
249 UpdateStreamOrOutcome::UpdateStream(stream) => stream,
250 UpdateStreamOrOutcome::Outcome(outcome) => {
251 Box::pin(stream::once(future::ready(outcome)))
252 }
253 }
254 }
255
256 pub async fn await_outcome(self) -> Option<U> {
260 match self {
261 UpdateStreamOrOutcome::Outcome(outcome) => Some(outcome),
262 UpdateStreamOrOutcome::UpdateStream(mut stream) => {
263 let mut last_update = None;
264 while let Some(update) = stream.next().await {
265 last_update = Some(update);
266 }
267 last_update
268 }
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use futures::stream;
276 use serde_json::Value;
277
278 use super::*;
279
280 #[tokio::test]
281 async fn test_await_outcome_cached() {
282 let test_value = serde_json::json!({"status": "completed", "amount": 100});
283 let cached_outcome = UpdateStreamOrOutcome::Outcome(test_value.clone());
284 let result = cached_outcome.await_outcome().await;
285 assert_eq!(result, Some(test_value));
286 }
287
288 #[tokio::test]
289 async fn test_await_outcome_uncached_with_updates() {
290 let update_stream = Box::pin(stream::iter(vec![
291 Value::from(0),
292 Value::from(1),
293 Value::from(2),
294 ]));
295 let uncached_outcome = UpdateStreamOrOutcome::UpdateStream(update_stream);
296 let result = uncached_outcome.await_outcome().await;
297 assert_eq!(result, Some(Value::from(2)));
298 }
299
300 #[tokio::test]
301 async fn test_await_outcome_uncached_empty_stream() {
302 let empty_stream = Box::pin(stream::empty::<serde_json::Value>());
303 let uncached_outcome = UpdateStreamOrOutcome::UpdateStream(empty_stream);
304 let result = uncached_outcome.await_outcome().await;
305 assert_eq!(result, None);
306 }
307
308 #[tokio::test]
309 async fn test_await_outcome_uncached_single_update() {
310 let update_stream = Box::pin(stream::once(async { Value::from(0) }));
311 let uncached_outcome = UpdateStreamOrOutcome::UpdateStream(update_stream);
312 let result = uncached_outcome.await_outcome().await;
313 assert_eq!(result, Some(Value::from(0)));
314 }
315}