modality_ingest_client/
dynamic.rs

1use modality_api::{AttrVal, TimelineId};
2use modality_ingest_protocol::{IngestMessage, InternedAttrKey};
3use thiserror::Error;
4
5use crate::{BoundTimelineState, IngestClient, IngestClientCommon, IngestError, ReadyState};
6
7/// A more dynamic ingest client, for places where the session types are difficult to use.
8pub struct DynamicIngestClient {
9    common: IngestClientCommon,
10    bound_timeline: Option<TimelineId>,
11}
12
13impl From<IngestClient<ReadyState>> for DynamicIngestClient {
14    fn from(c: IngestClient<ReadyState>) -> Self {
15        Self {
16            common: c.common,
17            bound_timeline: None,
18        }
19    }
20}
21
22impl From<IngestClient<BoundTimelineState>> for DynamicIngestClient {
23    fn from(c: IngestClient<BoundTimelineState>) -> Self {
24        Self {
25            common: c.common,
26            bound_timeline: Some(c.state.timeline_id),
27        }
28    }
29}
30
31impl DynamicIngestClient {
32    pub async fn declare_attr_key(
33        &mut self,
34        key_name: String,
35    ) -> Result<InternedAttrKey, IngestError> {
36        self.common.declare_attr_key(key_name).await
37    }
38
39    pub async fn open_timeline(&mut self, id: TimelineId) -> Result<(), IngestError> {
40        self.common
41            .send(&IngestMessage::OpenTimeline { id })
42            .await?;
43
44        self.bound_timeline = Some(id);
45        Ok(())
46    }
47
48    pub fn close_timeline(&mut self) {
49        self.bound_timeline = None;
50    }
51
52    pub async fn timeline_metadata(
53        &mut self,
54        attrs: impl IntoIterator<Item = (InternedAttrKey, AttrVal)>,
55    ) -> Result<(), DynamicIngestError> {
56        if self.bound_timeline.is_none() {
57            return Err(DynamicIngestError::NoBoundTimeline);
58        }
59
60        self.common.timeline_metadata(attrs).await?;
61        Ok(())
62    }
63
64    pub async fn event(
65        &mut self,
66        ordering: u128,
67        attrs: impl IntoIterator<Item = (InternedAttrKey, AttrVal)>,
68    ) -> Result<(), DynamicIngestError> {
69        if self.bound_timeline.is_none() {
70            return Err(DynamicIngestError::NoBoundTimeline);
71        }
72
73        self.common.event(ordering, attrs).await?;
74        Ok(())
75    }
76}
77
78#[derive(Error, Debug)]
79pub enum DynamicIngestError {
80    #[error(transparent)]
81    IngestError(#[from] IngestError),
82
83    #[error("Invalid state: a timeline must be bound before submitting events")]
84    NoBoundTimeline,
85}