1use super::event_type::EslEventType;
2use super::format::EventFormat;
3use crate::headers::EventHeader;
4use crate::sofia::SofiaEventSubclass;
5use std::fmt;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct EventSubscriptionError(pub String);
13
14impl fmt::Display for EventSubscriptionError {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 write!(f, "invalid event subscription: {}", self.0)
17 }
18}
19
20impl std::error::Error for EventSubscriptionError {}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
50#[non_exhaustive]
51pub struct EventSubscription {
52 format: EventFormat,
53 events: Vec<EslEventType>,
54 raw_events: Vec<String>,
55 custom_subclasses: Vec<String>,
56 filters: Vec<(String, String)>,
57}
58
59fn validate_wire_token(
65 s: &str,
66 label: &str,
67 reject_empty: bool,
68 reject_space: bool,
69) -> Result<(), EventSubscriptionError> {
70 if reject_empty && s.is_empty() {
71 return Err(EventSubscriptionError(format!("{} cannot be empty", label)));
72 }
73 if crate::wire_safety::contains_wire_terminator(s) {
74 return Err(EventSubscriptionError(format!(
75 "{} contains newline: {:?}",
76 label, s
77 )));
78 }
79 if reject_space && s.contains(' ') {
80 return Err(EventSubscriptionError(format!(
81 "{} contains space: {:?}",
82 label, s
83 )));
84 }
85 Ok(())
86}
87
88fn validate_raw_event(s: &str) -> Result<(), EventSubscriptionError> {
89 validate_wire_token(s, "raw event", true, true)
90}
91
92fn validate_custom_subclass(s: &str) -> Result<(), EventSubscriptionError> {
93 validate_wire_token(s, "custom subclass", true, true)
94}
95
96fn validate_filter_field(field: &str, label: &str) -> Result<(), EventSubscriptionError> {
97 validate_wire_token(field, &format!("filter {}", label), false, false)
98}
99
100impl EventSubscription {
101 pub fn new(format: EventFormat) -> Self {
103 Self {
104 format,
105 events: Vec::new(),
106 raw_events: Vec::new(),
107 custom_subclasses: Vec::new(),
108 filters: Vec::new(),
109 }
110 }
111
112 pub fn all(format: EventFormat) -> Self {
114 Self {
115 format,
116 events: vec![EslEventType::All],
117 raw_events: Vec::new(),
118 custom_subclasses: Vec::new(),
119 filters: Vec::new(),
120 }
121 }
122
123 pub fn event(mut self, event: EslEventType) -> Self {
125 self.events
126 .push(event);
127 self
128 }
129
130 pub fn events<T: IntoIterator<Item = impl std::borrow::Borrow<EslEventType>>>(
132 mut self,
133 events: T,
134 ) -> Self {
135 self.events
136 .extend(
137 events
138 .into_iter()
139 .map(|e| *e.borrow()),
140 );
141 self
142 }
143
144 pub fn event_raw(mut self, event: impl Into<String>) -> Result<Self, EventSubscriptionError> {
153 let s = event.into();
154 validate_raw_event(&s)?;
155 self.raw_events
156 .push(s);
157 Ok(self)
158 }
159
160 pub fn events_raw<I, S>(mut self, events: I) -> Result<Self, EventSubscriptionError>
164 where
165 I: IntoIterator<Item = S>,
166 S: Into<String>,
167 {
168 for e in events {
169 let s = e.into();
170 validate_raw_event(&s)?;
171 self.raw_events
172 .push(s);
173 }
174 Ok(self)
175 }
176
177 pub fn custom_subclass(
181 mut self,
182 subclass: impl Into<String>,
183 ) -> Result<Self, EventSubscriptionError> {
184 let s = subclass.into();
185 validate_custom_subclass(&s)?;
186 self.custom_subclasses
187 .push(s);
188 Ok(self)
189 }
190
191 pub fn custom_subclasses(
195 mut self,
196 subclasses: impl IntoIterator<Item = impl Into<String>>,
197 ) -> Result<Self, EventSubscriptionError> {
198 for s in subclasses {
199 let s = s.into();
200 validate_custom_subclass(&s)?;
201 self.custom_subclasses
202 .push(s);
203 }
204 Ok(self)
205 }
206
207 pub fn sofia_event(mut self, subclass: SofiaEventSubclass) -> Self {
212 self.custom_subclasses
213 .push(
214 subclass
215 .as_str()
216 .to_string(),
217 );
218 self
219 }
220
221 pub fn sofia_events(
223 mut self,
224 subclasses: impl IntoIterator<Item = impl std::borrow::Borrow<SofiaEventSubclass>>,
225 ) -> Self {
226 self.custom_subclasses
227 .extend(
228 subclasses
229 .into_iter()
230 .map(|s| {
231 s.borrow()
232 .as_str()
233 .to_string()
234 }),
235 );
236 self
237 }
238
239 pub fn filter(
243 self,
244 header: EventHeader,
245 value: impl Into<String>,
246 ) -> Result<Self, EventSubscriptionError> {
247 let v = value.into();
248 validate_filter_field(&v, "value")?;
249 let mut s = self;
250 s.filters
251 .push((
252 header
253 .as_str()
254 .to_string(),
255 v,
256 ));
257 Ok(s)
258 }
259
260 pub fn filter_raw(
264 self,
265 header: impl Into<String>,
266 value: impl Into<String>,
267 ) -> Result<Self, EventSubscriptionError> {
268 let h = header.into();
269 let v = value.into();
270 validate_filter_field(&h, "header")?;
271 validate_filter_field(&v, "value")?;
272 let mut s = self;
273 s.filters
274 .push((h, v));
275 Ok(s)
276 }
277
278 pub fn with_format(mut self, format: EventFormat) -> Self {
280 self.format = format;
281 self
282 }
283
284 pub fn format(&self) -> EventFormat {
286 self.format
287 }
288
289 pub fn format_mut(&mut self) -> &mut EventFormat {
291 &mut self.format
292 }
293
294 pub fn event_types(&self) -> &[EslEventType] {
296 &self.events
297 }
298
299 pub fn event_types_mut(&mut self) -> &mut Vec<EslEventType> {
301 &mut self.events
302 }
303
304 pub fn event_types_raw(&self) -> &[String] {
306 &self.raw_events
307 }
308
309 pub fn event_types_raw_mut(&mut self) -> &mut Vec<String> {
315 &mut self.raw_events
316 }
317
318 pub fn custom_subclass_list(&self) -> &[String] {
320 &self.custom_subclasses
321 }
322
323 pub fn custom_subclasses_mut(&mut self) -> &mut Vec<String> {
325 &mut self.custom_subclasses
326 }
327
328 pub fn filters(&self) -> &[(String, String)] {
330 &self.filters
331 }
332
333 pub fn filters_mut(&mut self) -> &mut Vec<(String, String)> {
335 &mut self.filters
336 }
337
338 pub fn is_all(&self) -> bool {
340 self.events
341 .contains(&EslEventType::All)
342 }
343
344 pub fn is_empty(&self) -> bool {
347 self.events
348 .is_empty()
349 && self
350 .raw_events
351 .is_empty()
352 && self
353 .custom_subclasses
354 .is_empty()
355 }
356
357 pub fn to_event_string(&self) -> Option<String> {
364 if self
365 .events
366 .contains(&EslEventType::All)
367 {
368 return Some("ALL".to_string());
369 }
370
371 let mut parts: Vec<&str> = self
372 .events
373 .iter()
374 .map(|e| e.as_str())
375 .collect();
376
377 parts.extend(
378 self.raw_events
379 .iter()
380 .map(|s| s.as_str()),
381 );
382
383 if !self
384 .custom_subclasses
385 .is_empty()
386 {
387 if !self
388 .events
389 .contains(&EslEventType::Custom)
390 {
391 parts.push("CUSTOM");
392 }
393 for sc in &self.custom_subclasses {
394 parts.push(sc.as_str());
395 }
396 }
397
398 if parts.is_empty() {
399 None
400 } else {
401 Some(parts.join(" "))
402 }
403 }
404}
405
406#[cfg(feature = "serde")]
407mod event_subscription_serde {
408 use super::*;
409 use serde::{Deserialize, Serialize};
410
411 #[derive(Serialize, Deserialize)]
412 struct EventSubscriptionRaw {
413 format: EventFormat,
414 #[serde(default)]
415 events: Vec<EslEventType>,
416 #[serde(default, skip_serializing_if = "Vec::is_empty")]
417 raw_events: Vec<String>,
418 #[serde(default)]
419 custom_subclasses: Vec<String>,
420 #[serde(default)]
421 filters: Vec<(String, String)>,
422 }
423
424 impl TryFrom<EventSubscriptionRaw> for EventSubscription {
425 type Error = EventSubscriptionError;
426
427 fn try_from(raw: EventSubscriptionRaw) -> Result<Self, Self::Error> {
428 for re in &raw.raw_events {
429 validate_raw_event(re)?;
430 }
431 for sc in &raw.custom_subclasses {
432 validate_custom_subclass(sc)?;
433 }
434 for (h, v) in &raw.filters {
435 validate_filter_field(h, "header")?;
436 validate_filter_field(v, "value")?;
437 }
438 Ok(EventSubscription {
439 format: raw.format,
440 events: raw.events,
441 raw_events: raw.raw_events,
442 custom_subclasses: raw.custom_subclasses,
443 filters: raw.filters,
444 })
445 }
446 }
447
448 impl Serialize for EventSubscription {
449 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
450 let raw = EventSubscriptionRaw {
451 format: self.format,
452 events: self
453 .events
454 .clone(),
455 raw_events: self
456 .raw_events
457 .clone(),
458 custom_subclasses: self
459 .custom_subclasses
460 .clone(),
461 filters: self
462 .filters
463 .clone(),
464 };
465 raw.serialize(serializer)
466 }
467 }
468
469 impl<'de> Deserialize<'de> for EventSubscription {
470 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
471 let raw = EventSubscriptionRaw::deserialize(deserializer)?;
472 EventSubscription::try_from(raw).map_err(serde::de::Error::custom)
473 }
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn new_creates_empty() {
483 let sub = EventSubscription::new(EventFormat::Plain);
484 assert!(sub.is_empty());
485 assert!(!sub.is_all());
486 assert_eq!(sub.format(), EventFormat::Plain);
487 assert!(sub
488 .event_types()
489 .is_empty());
490 assert!(sub
491 .custom_subclass_list()
492 .is_empty());
493 assert!(sub
494 .filters()
495 .is_empty());
496 }
497
498 #[test]
499 fn all_creates_all() {
500 let sub = EventSubscription::all(EventFormat::Json);
501 assert!(sub.is_all());
502 assert!(!sub.is_empty());
503 assert_eq!(sub.to_event_string(), Some("ALL".to_string()));
504 }
505
506 #[test]
507 fn event_string_typed_only() {
508 let sub = EventSubscription::new(EventFormat::Plain)
509 .event(EslEventType::ChannelCreate)
510 .event(EslEventType::ChannelAnswer);
511 assert_eq!(
512 sub.to_event_string(),
513 Some("CHANNEL_CREATE CHANNEL_ANSWER".to_string())
514 );
515 }
516
517 #[test]
518 fn event_string_custom_only() {
519 let sub = EventSubscription::new(EventFormat::Plain)
520 .custom_subclass("sofia::register")
521 .unwrap()
522 .custom_subclass("sofia::unregister")
523 .unwrap();
524 assert_eq!(
525 sub.to_event_string(),
526 Some("CUSTOM sofia::register sofia::unregister".to_string())
527 );
528 }
529
530 #[test]
531 fn event_string_mixed() {
532 let sub = EventSubscription::new(EventFormat::Plain)
533 .event(EslEventType::Heartbeat)
534 .custom_subclass("sofia::register")
535 .unwrap();
536 assert_eq!(
537 sub.to_event_string(),
538 Some("HEARTBEAT CUSTOM sofia::register".to_string())
539 );
540 }
541
542 #[test]
543 fn event_string_custom_not_duplicated() {
544 let sub = EventSubscription::new(EventFormat::Plain)
545 .event(EslEventType::Custom)
546 .custom_subclass("sofia::register")
547 .unwrap();
548 assert_eq!(
550 sub.to_event_string(),
551 Some("CUSTOM sofia::register".to_string())
552 );
553 }
554
555 #[test]
556 fn event_string_empty_is_none() {
557 let sub = EventSubscription::new(EventFormat::Plain);
558 assert_eq!(sub.to_event_string(), None);
559 }
560
561 #[test]
562 fn filters_preserve_order() {
563 let sub = EventSubscription::new(EventFormat::Plain)
564 .filter(EventHeader::CallDirection, "inbound")
565 .unwrap()
566 .filter_raw("X-Custom", "value1")
567 .unwrap()
568 .filter(EventHeader::ChannelState, "CS_EXECUTE")
569 .unwrap();
570 assert_eq!(
571 sub.filters(),
572 &[
573 ("Call-Direction".to_string(), "inbound".to_string()),
574 ("X-Custom".to_string(), "value1".to_string()),
575 ("Channel-State".to_string(), "CS_EXECUTE".to_string()),
576 ]
577 );
578 }
579
580 #[test]
581 fn builder_chain() {
582 let sub = EventSubscription::new(EventFormat::Plain)
583 .events(EslEventType::CHANNEL_EVENTS)
584 .event(EslEventType::Heartbeat)
585 .custom_subclass("sofia::register")
586 .unwrap()
587 .filter(EventHeader::CallDirection, "inbound")
588 .unwrap()
589 .with_format(EventFormat::Json);
590
591 assert_eq!(sub.format(), EventFormat::Json);
592 assert!(!sub.is_empty());
593 assert!(!sub.is_all());
594 assert!(sub
595 .event_types()
596 .contains(&EslEventType::ChannelCreate));
597 assert!(sub
598 .event_types()
599 .contains(&EslEventType::Heartbeat));
600 assert_eq!(sub.custom_subclass_list(), &["sofia::register"]);
601 assert_eq!(
602 sub.filters()
603 .len(),
604 1
605 );
606 }
607
608 #[test]
609 fn serde_round_trip_subscription() {
610 let sub = EventSubscription::new(EventFormat::Plain)
611 .event(EslEventType::ChannelCreate)
612 .event(EslEventType::Heartbeat)
613 .custom_subclass("sofia::register")
614 .unwrap()
615 .filter(EventHeader::CallDirection, "inbound")
616 .unwrap();
617
618 let json = serde_json::to_string(&sub).unwrap();
619 let deserialized: EventSubscription = serde_json::from_str(&json).unwrap();
620 assert_eq!(sub, deserialized);
621 }
622
623 #[test]
624 fn serde_rejects_invalid_subclass() {
625 let json =
626 r#"{"format":"Plain","events":[],"custom_subclasses":["bad subclass"],"filters":[]}"#;
627 let result: Result<EventSubscription, _> = serde_json::from_str(json);
628 assert!(result.is_err());
629 let err = result
630 .unwrap_err()
631 .to_string();
632 assert!(err.contains("space"), "error should mention space: {err}");
633 }
634
635 #[test]
636 fn serde_rejects_newline_in_filter() {
637 let json = r#"{"format":"Plain","events":[],"custom_subclasses":[],"filters":[["Header","val\n"]]}"#;
638 let result: Result<EventSubscription, _> = serde_json::from_str(json);
639 assert!(result.is_err());
640 let err = result
641 .unwrap_err()
642 .to_string();
643 assert!(
644 err.contains("newline"),
645 "error should mention newline: {err}"
646 );
647 }
648
649 #[test]
650 fn custom_subclass_rejects_space() {
651 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad subclass");
652 assert!(result.is_err());
653 }
654
655 #[test]
656 fn custom_subclass_rejects_newline() {
657 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad\nsubclass");
658 assert!(result.is_err());
659 }
660
661 #[test]
662 fn custom_subclass_rejects_empty() {
663 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("");
664 assert!(result.is_err());
665 }
666
667 #[test]
668 fn filter_raw_rejects_newline_in_header() {
669 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Bad\nHeader", "value");
670 assert!(result.is_err());
671 }
672
673 #[test]
674 fn filter_raw_rejects_newline_in_value() {
675 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Header", "bad\nvalue");
676 assert!(result.is_err());
677 }
678
679 #[test]
680 fn filter_typed_rejects_newline_in_value() {
681 let result = EventSubscription::new(EventFormat::Plain)
682 .filter(EventHeader::CallDirection, "bad\nvalue");
683 assert!(result.is_err());
684 }
685
686 #[test]
687 fn sofia_event_single() {
688 let sub =
689 EventSubscription::new(EventFormat::Plain).sofia_event(SofiaEventSubclass::Register);
690 assert_eq!(
691 sub.to_event_string(),
692 Some("CUSTOM sofia::register".to_string())
693 );
694 }
695
696 #[test]
697 fn sofia_events_group() {
698 let sub = EventSubscription::new(EventFormat::Plain)
699 .sofia_events(SofiaEventSubclass::GATEWAY_EVENTS);
700 let event_str = sub
701 .to_event_string()
702 .unwrap();
703 assert!(event_str.starts_with("CUSTOM"));
704 assert!(event_str.contains("sofia::gateway_state"));
705 assert!(event_str.contains("sofia::gateway_add"));
706 assert!(event_str.contains("sofia::gateway_delete"));
707 assert!(event_str.contains("sofia::gateway_invalid_digest_req"));
708 }
709
710 #[test]
711 fn event_raw_wire_string() {
712 let sub = EventSubscription::new(EventFormat::Plain)
713 .event(EslEventType::Heartbeat)
714 .event_raw("NEW_EVENT_NOT_IN_ENUM")
715 .unwrap();
716 assert_eq!(
717 sub.to_event_string(),
718 Some("HEARTBEAT NEW_EVENT_NOT_IN_ENUM".to_string())
719 );
720 }
721
722 #[test]
723 fn events_raw_wire_string() {
724 let sub = EventSubscription::new(EventFormat::Plain)
725 .events_raw(["FUTURE_A", "FUTURE_B"])
726 .unwrap();
727 assert_eq!(sub.to_event_string(), Some("FUTURE_A FUTURE_B".to_string()));
728 }
729
730 #[test]
731 fn event_raw_with_custom_subclass() {
732 let sub = EventSubscription::new(EventFormat::Plain)
733 .event_raw("NEW_EVENT")
734 .unwrap()
735 .custom_subclass("sofia::register")
736 .unwrap();
737 assert_eq!(
738 sub.to_event_string(),
739 Some("NEW_EVENT CUSTOM sofia::register".to_string())
740 );
741 }
742
743 #[test]
744 fn event_raw_rejects_newline() {
745 assert!(EventSubscription::new(EventFormat::Plain)
746 .event_raw("bad\nevent")
747 .is_err());
748 }
749
750 #[test]
751 fn event_raw_rejects_space() {
752 assert!(EventSubscription::new(EventFormat::Plain)
753 .event_raw("bad event")
754 .is_err());
755 }
756
757 #[test]
758 fn event_raw_rejects_empty() {
759 assert!(EventSubscription::new(EventFormat::Plain)
760 .event_raw("")
761 .is_err());
762 }
763
764 #[test]
765 fn events_raw_errors_on_first_invalid() {
766 let result =
767 EventSubscription::new(EventFormat::Plain).events_raw(["GOOD", "bad event", "OTHER"]);
768 assert!(result.is_err());
769 }
770
771 #[test]
772 fn event_types_raw_mut_mutable() {
773 let mut sub = EventSubscription::new(EventFormat::Plain);
774 sub.event_types_raw_mut()
775 .push("DIRECT_PUSH".to_string());
776 assert_eq!(sub.event_types_raw(), &["DIRECT_PUSH".to_string()]);
777 }
778
779 #[test]
780 fn is_empty_sees_raw_events() {
781 let sub = EventSubscription::new(EventFormat::Plain)
782 .event_raw("ONLY_RAW")
783 .unwrap();
784 assert!(!sub.is_empty());
785 }
786
787 #[test]
788 fn serde_round_trip_with_raw_events() {
789 let sub = EventSubscription::new(EventFormat::Plain)
790 .event(EslEventType::ChannelCreate)
791 .event_raw("FUTURE_EVENT")
792 .unwrap()
793 .custom_subclass("sofia::register")
794 .unwrap();
795
796 let json = serde_json::to_string(&sub).unwrap();
797 let deserialized: EventSubscription = serde_json::from_str(&json).unwrap();
798 assert_eq!(sub, deserialized);
799 }
800
801 #[test]
802 fn serde_rejects_invalid_raw_event() {
803 let json = r#"{"format":"Plain","events":[],"raw_events":["bad event"],"custom_subclasses":[],"filters":[]}"#;
804 let result: Result<EventSubscription, _> = serde_json::from_str(json);
805 assert!(result.is_err());
806 }
807
808 #[test]
809 fn serde_missing_raw_events_field_defaults_to_empty() {
810 let json =
813 r#"{"format":"Plain","events":["Heartbeat"],"custom_subclasses":[],"filters":[]}"#;
814 let sub: EventSubscription = serde_json::from_str(json).unwrap();
815 assert!(sub
816 .event_types_raw()
817 .is_empty());
818 }
819
820 #[test]
821 fn sofia_event_mixed_with_typed_events() {
822 let sub = EventSubscription::new(EventFormat::Plain)
823 .event(EslEventType::Heartbeat)
824 .sofia_event(SofiaEventSubclass::GatewayState);
825 assert_eq!(
826 sub.to_event_string(),
827 Some("HEARTBEAT CUSTOM sofia::gateway_state".to_string())
828 );
829 }
830}