evidentsource_client/conversions/
state_view.rs

1//! StateView type conversions.
2
3use evidentsource_core::domain::{DatabaseName, EventSelector, StateView, StateViewName};
4
5use crate::com::evidentsource as proto;
6
7use super::error::ConversionError;
8use super::timestamps::timestamp_to_datetime;
9
10// =============================================================================
11// Proto -> Domain
12// =============================================================================
13
14impl TryFrom<proto::StateView> for StateView {
15    type Error = ConversionError;
16
17    fn try_from(proto: proto::StateView) -> Result<Self, Self::Error> {
18        let identity = proto
19            .identity
20            .ok_or_else(|| ConversionError::missing_field("StateView", "identity"))?;
21
22        let database = DatabaseName::new(&identity.database_name)?;
23        let name = StateViewName::new(&identity.state_view_name)?;
24
25        let event_selector_proto = proto
26            .event_selector
27            .ok_or_else(|| ConversionError::missing_field("StateView", "event_selector"))?;
28        let event_selector = EventSelector::try_from(event_selector_proto)?;
29
30        let last_modified_timestamp = proto.last_modified.map(timestamp_to_datetime).transpose()?;
31
32        let last_modified_revision = if proto.last_modified_revision > 0 {
33            Some(proto.last_modified_revision)
34        } else {
35            None
36        };
37
38        let content = match proto.content {
39            Some(proto::state_view::Content::Data(bytes)) => Some(bytes),
40            Some(proto::state_view::Content::Url(_)) => {
41                // URL-based content not currently supported, would need separate fetch
42                None
43            }
44            None => None,
45        };
46
47        Ok(StateView {
48            database,
49            name,
50            version: identity.state_view_version,
51            event_selector,
52            last_modified_revision,
53            last_modified_timestamp,
54            content_type: proto.content_type,
55            content_schema_url: proto.content_schema_url.filter(|s| !s.is_empty()),
56            content,
57        })
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_state_view_conversion() {
67        let proto = proto::StateView {
68            identity: Some(proto::StateViewIdentity {
69                database_name: "test-db".to_string(),
70                state_view_name: "test-view".to_string(),
71                state_view_version: 1,
72            }),
73            event_selector: Some(proto::EventSelector {
74                selector: Some(proto::event_selector::Selector::Equals(
75                    proto::EventAttribute {
76                        attribute: Some(proto::event_attribute::Attribute::Stream(
77                            "my-stream".to_string(),
78                        )),
79                    },
80                )),
81            }),
82            last_modified: None,
83            last_modified_revision: 0,
84            content_type: "application/json".to_string(),
85            content_schema_url: None,
86            content: Some(proto::state_view::Content::Data(
87                b"{\"count\": 42}".to_vec(),
88            )),
89        };
90
91        let domain: StateView = proto.try_into().unwrap();
92        assert_eq!(domain.database.as_str(), "test-db");
93        assert_eq!(domain.name.as_str(), "test-view");
94        assert_eq!(domain.version, 1);
95        assert_eq!(domain.content_type, "application/json");
96        assert!(domain.content.is_some());
97    }
98}