evidentsource_client/conversions/
selectors.rs

1//! EventSelector type conversions.
2
3use evidentsource_core::domain::{
4    EventAttribute, EventAttributePrefix, EventSelector, EventSubject, EventType, StreamName,
5};
6
7use crate::com::evidentsource as proto;
8
9use super::error::ConversionError;
10
11// =============================================================================
12// Domain -> Proto (infallible)
13// =============================================================================
14
15impl From<EventSelector> for proto::EventSelector {
16    fn from(selector: EventSelector) -> Self {
17        use proto::event_selector::Selector;
18
19        let selector_variant = match selector {
20            EventSelector::Equals(attr) => Selector::Equals(attr.into()),
21            EventSelector::StartsWith(prefix) => Selector::StartsWith(prefix.into()),
22            EventSelector::And { left, right } => {
23                Selector::And(Box::new(proto::event_selector::LogicalAnd {
24                    left: Some(Box::new((*left).into())),
25                    right: Some(Box::new((*right).into())),
26                }))
27            }
28            EventSelector::Or { left, right } => {
29                Selector::Or(Box::new(proto::event_selector::LogicalOr {
30                    left: Some(Box::new((*left).into())),
31                    right: Some(Box::new((*right).into())),
32                }))
33            }
34        };
35
36        proto::EventSelector {
37            selector: Some(selector_variant),
38        }
39    }
40}
41
42impl From<EventAttribute> for proto::EventAttribute {
43    fn from(attr: EventAttribute) -> Self {
44        use proto::event_attribute::Attribute;
45
46        let attribute = match attr {
47            EventAttribute::Stream(name) => Attribute::Stream(name.to_string()),
48            EventAttribute::Subject(subject) => {
49                Attribute::Subject(proto::event_attribute::SubjectValue {
50                    has_value: subject.is_some(),
51                    value: subject.map(|s| s.to_string()).unwrap_or_default(),
52                })
53            }
54            EventAttribute::EventType(et) => Attribute::EventType(et.to_string()),
55        };
56
57        proto::EventAttribute {
58            attribute: Some(attribute),
59        }
60    }
61}
62
63impl From<EventAttributePrefix> for proto::event_selector::StartsWith {
64    fn from(prefix: EventAttributePrefix) -> Self {
65        use proto::event_selector::starts_with::Attribute;
66
67        let attribute = match prefix {
68            EventAttributePrefix::Stream(name) => Attribute::Stream(name.to_string()),
69            EventAttributePrefix::Subject(subject) => Attribute::Subject(subject.to_string()),
70            EventAttributePrefix::EventType(et) => Attribute::EventType(et.to_string()),
71        };
72
73        proto::event_selector::StartsWith {
74            attribute: Some(attribute),
75        }
76    }
77}
78
79// =============================================================================
80// Proto -> Domain (fallible)
81// =============================================================================
82
83impl TryFrom<proto::EventSelector> for EventSelector {
84    type Error = ConversionError;
85
86    fn try_from(proto: proto::EventSelector) -> Result<Self, Self::Error> {
87        use proto::event_selector::Selector;
88
89        let selector = proto
90            .selector
91            .ok_or_else(|| ConversionError::missing_oneof("EventSelector", "selector"))?;
92
93        match selector {
94            Selector::Equals(attr) => {
95                let domain_attr = EventAttribute::try_from(attr)?;
96                Ok(EventSelector::Equals(domain_attr))
97            }
98            Selector::StartsWith(starts_with) => {
99                let domain_prefix = EventAttributePrefix::try_from(starts_with)?;
100                Ok(EventSelector::StartsWith(domain_prefix))
101            }
102            Selector::And(logical_and) => {
103                let left = logical_and.left.ok_or_else(|| {
104                    ConversionError::missing_field("EventSelector.LogicalAnd", "left")
105                })?;
106                let right = logical_and.right.ok_or_else(|| {
107                    ConversionError::missing_field("EventSelector.LogicalAnd", "right")
108                })?;
109
110                let left_domain = EventSelector::try_from(*left)
111                    .map_err(|e| ConversionError::nested("And.left", e))?;
112                let right_domain = EventSelector::try_from(*right)
113                    .map_err(|e| ConversionError::nested("And.right", e))?;
114
115                Ok(EventSelector::And {
116                    left: Box::new(left_domain),
117                    right: Box::new(right_domain),
118                })
119            }
120            Selector::Or(logical_or) => {
121                let left = logical_or.left.ok_or_else(|| {
122                    ConversionError::missing_field("EventSelector.LogicalOr", "left")
123                })?;
124                let right = logical_or.right.ok_or_else(|| {
125                    ConversionError::missing_field("EventSelector.LogicalOr", "right")
126                })?;
127
128                let left_domain = EventSelector::try_from(*left)
129                    .map_err(|e| ConversionError::nested("Or.left", e))?;
130                let right_domain = EventSelector::try_from(*right)
131                    .map_err(|e| ConversionError::nested("Or.right", e))?;
132
133                Ok(EventSelector::Or {
134                    left: Box::new(left_domain),
135                    right: Box::new(right_domain),
136                })
137            }
138        }
139    }
140}
141
142impl TryFrom<proto::EventAttribute> for EventAttribute {
143    type Error = ConversionError;
144
145    fn try_from(proto: proto::EventAttribute) -> Result<Self, Self::Error> {
146        use proto::event_attribute::Attribute;
147
148        let attr = proto
149            .attribute
150            .ok_or_else(|| ConversionError::missing_oneof("EventAttribute", "attribute"))?;
151
152        match attr {
153            Attribute::Stream(s) => {
154                let name = StreamName::new(s)?;
155                Ok(EventAttribute::Stream(name))
156            }
157            Attribute::Subject(subj) => {
158                if subj.has_value {
159                    let subject = EventSubject::new(subj.value)?;
160                    Ok(EventAttribute::Subject(Some(subject)))
161                } else {
162                    Ok(EventAttribute::Subject(None))
163                }
164            }
165            Attribute::EventType(et) => {
166                let event_type = EventType::new(et)?;
167                Ok(EventAttribute::EventType(event_type))
168            }
169        }
170    }
171}
172
173impl TryFrom<proto::event_selector::StartsWith> for EventAttributePrefix {
174    type Error = ConversionError;
175
176    fn try_from(proto: proto::event_selector::StartsWith) -> Result<Self, Self::Error> {
177        use proto::event_selector::starts_with::Attribute;
178
179        let attr = proto
180            .attribute
181            .ok_or_else(|| ConversionError::missing_oneof("StartsWith", "attribute"))?;
182
183        match attr {
184            Attribute::Stream(s) => {
185                let name = StreamName::new(s)?;
186                Ok(EventAttributePrefix::Stream(name))
187            }
188            Attribute::Subject(s) => {
189                let subject = EventSubject::new(s)?;
190                Ok(EventAttributePrefix::Subject(subject))
191            }
192            Attribute::EventType(et) => {
193                let event_type = EventType::new(et)?;
194                Ok(EventAttributePrefix::EventType(event_type))
195            }
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_roundtrip_simple_equals() {
206        let domain = EventSelector::stream_equals("my-stream").unwrap();
207        let proto: proto::EventSelector = domain.clone().into();
208        let back: EventSelector = proto.try_into().unwrap();
209        assert_eq!(domain, back);
210    }
211
212    #[test]
213    fn test_roundtrip_nested_and_or() {
214        let domain = EventSelector::stream_equals("stream1")
215            .unwrap()
216            .and(EventSelector::event_type_equals("type1").unwrap())
217            .or(EventSelector::subject_equals("subject1").unwrap());
218
219        let proto: proto::EventSelector = domain.clone().into();
220        let back: EventSelector = proto.try_into().unwrap();
221        assert_eq!(domain, back);
222    }
223
224    #[test]
225    fn test_missing_oneof_returns_error() {
226        let proto = proto::EventSelector { selector: None };
227        let result: Result<EventSelector, _> = proto.try_into();
228        assert!(matches!(result, Err(ConversionError::MissingOneof { .. })));
229    }
230
231    #[test]
232    fn test_no_subject_roundtrip() {
233        let domain = EventSelector::no_subject();
234        let proto: proto::EventSelector = domain.clone().into();
235        let back: EventSelector = proto.try_into().unwrap();
236        assert_eq!(domain, back);
237    }
238}