Skip to main content

freeswitch_types/
event.rs

1//! ESL event types and structures
2
3use crate::headers::{normalize_header_key, EventHeader};
4use crate::lookup::HeaderLookup;
5use crate::variables::EslArray;
6use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
7use std::collections::HashMap;
8use std::fmt;
9use std::str::FromStr;
10
11/// Event format types supported by FreeSWITCH ESL
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[non_exhaustive]
15pub enum EventFormat {
16    /// Plain text format (default)
17    Plain,
18    /// JSON format
19    Json,
20    /// XML format
21    Xml,
22}
23
24impl EventFormat {
25    /// Determine event format from a Content-Type header value.
26    ///
27    /// Returns `Err` for unrecognized content types to avoid silently
28    /// misparsing events if FreeSWITCH adds a new format.
29    pub fn from_content_type(ct: &str) -> Result<Self, ParseEventFormatError> {
30        match ct {
31            "text/event-json" => Ok(Self::Json),
32            "text/event-xml" => Ok(Self::Xml),
33            "text/event-plain" => Ok(Self::Plain),
34            _ => Err(ParseEventFormatError(ct.to_string())),
35        }
36    }
37}
38
39impl fmt::Display for EventFormat {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            EventFormat::Plain => write!(f, "plain"),
43            EventFormat::Json => write!(f, "json"),
44            EventFormat::Xml => write!(f, "xml"),
45        }
46    }
47}
48
49impl FromStr for EventFormat {
50    type Err = ParseEventFormatError;
51
52    fn from_str(s: &str) -> Result<Self, Self::Err> {
53        if s.eq_ignore_ascii_case("plain") {
54            Ok(Self::Plain)
55        } else if s.eq_ignore_ascii_case("json") {
56            Ok(Self::Json)
57        } else if s.eq_ignore_ascii_case("xml") {
58            Ok(Self::Xml)
59        } else {
60            Err(ParseEventFormatError(s.to_string()))
61        }
62    }
63}
64
65/// Error returned when parsing an invalid event format string.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ParseEventFormatError(pub String);
68
69impl fmt::Display for ParseEventFormatError {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(f, "unknown event format: {}", self.0)
72    }
73}
74
75impl std::error::Error for ParseEventFormatError {}
76
77/// Generates `EslEventType` enum with `Display`, `FromStr`, `as_str`, and `parse_event_type`.
78macro_rules! esl_event_types {
79    (
80        $(
81            $(#[$attr:meta])*
82            $variant:ident => $wire:literal
83        ),+ $(,)?
84        ;
85        // Extra variants not in the main match (after All)
86        $(
87            $(#[$extra_attr:meta])*
88            $extra_variant:ident => $extra_wire:literal
89        ),* $(,)?
90    ) => {
91        /// FreeSWITCH event types matching the canonical order from `esl_event.h`
92        /// and `switch_event.c` EVENT_NAMES[].
93        ///
94        /// Variant names are the canonical wire names (e.g. `ChannelCreate` = `CHANNEL_CREATE`).
95        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97        #[non_exhaustive]
98        #[allow(missing_docs)]
99        pub enum EslEventType {
100            $(
101                $(#[$attr])*
102                $variant,
103            )+
104            $(
105                $(#[$extra_attr])*
106                $extra_variant,
107            )*
108        }
109
110        impl fmt::Display for EslEventType {
111            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112                f.write_str(self.as_str())
113            }
114        }
115
116        impl EslEventType {
117            /// Returns the canonical wire name as a static string slice.
118            pub const fn as_str(&self) -> &'static str {
119                match self {
120                    $( EslEventType::$variant => $wire, )+
121                    $( EslEventType::$extra_variant => $extra_wire, )*
122                }
123            }
124
125            /// Parse event type from wire name (canonical case).
126            pub fn parse_event_type(s: &str) -> Option<Self> {
127                match s {
128                    $( $wire => Some(EslEventType::$variant), )+
129                    $( $extra_wire => Some(EslEventType::$extra_variant), )*
130                    _ => None,
131                }
132            }
133        }
134
135        impl FromStr for EslEventType {
136            type Err = ParseEventTypeError;
137
138            fn from_str(s: &str) -> Result<Self, Self::Err> {
139                Self::parse_event_type(s).ok_or_else(|| ParseEventTypeError(s.to_string()))
140            }
141        }
142    };
143}
144
145esl_event_types! {
146    Custom => "CUSTOM",
147    Clone => "CLONE",
148    ChannelCreate => "CHANNEL_CREATE",
149    ChannelDestroy => "CHANNEL_DESTROY",
150    ChannelState => "CHANNEL_STATE",
151    ChannelCallstate => "CHANNEL_CALLSTATE",
152    ChannelAnswer => "CHANNEL_ANSWER",
153    ChannelHangup => "CHANNEL_HANGUP",
154    ChannelHangupComplete => "CHANNEL_HANGUP_COMPLETE",
155    ChannelExecute => "CHANNEL_EXECUTE",
156    ChannelExecuteComplete => "CHANNEL_EXECUTE_COMPLETE",
157    ChannelHold => "CHANNEL_HOLD",
158    ChannelUnhold => "CHANNEL_UNHOLD",
159    ChannelBridge => "CHANNEL_BRIDGE",
160    ChannelUnbridge => "CHANNEL_UNBRIDGE",
161    ChannelProgress => "CHANNEL_PROGRESS",
162    ChannelProgressMedia => "CHANNEL_PROGRESS_MEDIA",
163    ChannelOutgoing => "CHANNEL_OUTGOING",
164    ChannelPark => "CHANNEL_PARK",
165    ChannelUnpark => "CHANNEL_UNPARK",
166    ChannelApplication => "CHANNEL_APPLICATION",
167    ChannelOriginate => "CHANNEL_ORIGINATE",
168    ChannelUuid => "CHANNEL_UUID",
169    Api => "API",
170    Log => "LOG",
171    InboundChan => "INBOUND_CHAN",
172    OutboundChan => "OUTBOUND_CHAN",
173    Startup => "STARTUP",
174    Shutdown => "SHUTDOWN",
175    Publish => "PUBLISH",
176    Unpublish => "UNPUBLISH",
177    Talk => "TALK",
178    Notalk => "NOTALK",
179    SessionCrash => "SESSION_CRASH",
180    ModuleLoad => "MODULE_LOAD",
181    ModuleUnload => "MODULE_UNLOAD",
182    Dtmf => "DTMF",
183    Message => "MESSAGE",
184    PresenceIn => "PRESENCE_IN",
185    NotifyIn => "NOTIFY_IN",
186    PresenceOut => "PRESENCE_OUT",
187    PresenceProbe => "PRESENCE_PROBE",
188    MessageWaiting => "MESSAGE_WAITING",
189    MessageQuery => "MESSAGE_QUERY",
190    Roster => "ROSTER",
191    Codec => "CODEC",
192    BackgroundJob => "BACKGROUND_JOB",
193    DetectedSpeech => "DETECTED_SPEECH",
194    DetectedTone => "DETECTED_TONE",
195    PrivateCommand => "PRIVATE_COMMAND",
196    Heartbeat => "HEARTBEAT",
197    Trap => "TRAP",
198    AddSchedule => "ADD_SCHEDULE",
199    DelSchedule => "DEL_SCHEDULE",
200    ExeSchedule => "EXE_SCHEDULE",
201    ReSchedule => "RE_SCHEDULE",
202    ReloadXml => "RELOADXML",
203    Notify => "NOTIFY",
204    PhoneFeature => "PHONE_FEATURE",
205    PhoneFeatureSubscribe => "PHONE_FEATURE_SUBSCRIBE",
206    SendMessage => "SEND_MESSAGE",
207    RecvMessage => "RECV_MESSAGE",
208    RequestParams => "REQUEST_PARAMS",
209    ChannelData => "CHANNEL_DATA",
210    General => "GENERAL",
211    Command => "COMMAND",
212    SessionHeartbeat => "SESSION_HEARTBEAT",
213    ClientDisconnected => "CLIENT_DISCONNECTED",
214    ServerDisconnected => "SERVER_DISCONNECTED",
215    SendInfo => "SEND_INFO",
216    RecvInfo => "RECV_INFO",
217    RecvRtcpMessage => "RECV_RTCP_MESSAGE",
218    SendRtcpMessage => "SEND_RTCP_MESSAGE",
219    CallSecure => "CALL_SECURE",
220    Nat => "NAT",
221    RecordStart => "RECORD_START",
222    RecordStop => "RECORD_STOP",
223    PlaybackStart => "PLAYBACK_START",
224    PlaybackStop => "PLAYBACK_STOP",
225    CallUpdate => "CALL_UPDATE",
226    Failure => "FAILURE",
227    SocketData => "SOCKET_DATA",
228    MediaBugStart => "MEDIA_BUG_START",
229    MediaBugStop => "MEDIA_BUG_STOP",
230    ConferenceDataQuery => "CONFERENCE_DATA_QUERY",
231    ConferenceData => "CONFERENCE_DATA",
232    CallSetupReq => "CALL_SETUP_REQ",
233    CallSetupResult => "CALL_SETUP_RESULT",
234    CallDetail => "CALL_DETAIL",
235    DeviceState => "DEVICE_STATE",
236    Text => "TEXT",
237    ShutdownRequested => "SHUTDOWN_REQUESTED",
238    /// Subscribe to all events
239    All => "ALL";
240    // --- Not in libs/esl/ EVENT_NAMES[], only in switch_event.c ---
241    // check-event-types.sh stops scanning at the All variant above.
242    /// Present in `switch_event.c` but not in `libs/esl/` EVENT_NAMES[].
243    StartRecording => "START_RECORDING",
244}
245
246// -- Event group constants --------------------------------------------------
247//
248// Predefined slices for common subscription patterns. Pass directly to
249// `EslClient::subscribe_events()`.
250//
251// MAINTENANCE: when adding new `EslEventType` variants, check whether they
252// belong in any of these groups and update accordingly.
253
254impl EslEventType {
255    /// Every `CHANNEL_*` event type.
256    ///
257    /// Covers the full channel lifecycle: creation, state changes, execution,
258    /// bridging, hold, park, progress, originate, and destruction.
259    ///
260    /// ```rust
261    /// use freeswitch_types::EslEventType;
262    /// assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelCreate));
263    /// assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelHangupComplete));
264    /// ```
265    pub const CHANNEL_EVENTS: &[EslEventType] = &[
266        EslEventType::ChannelCreate,
267        EslEventType::ChannelDestroy,
268        EslEventType::ChannelState,
269        EslEventType::ChannelCallstate,
270        EslEventType::ChannelAnswer,
271        EslEventType::ChannelHangup,
272        EslEventType::ChannelHangupComplete,
273        EslEventType::ChannelExecute,
274        EslEventType::ChannelExecuteComplete,
275        EslEventType::ChannelHold,
276        EslEventType::ChannelUnhold,
277        EslEventType::ChannelBridge,
278        EslEventType::ChannelUnbridge,
279        EslEventType::ChannelProgress,
280        EslEventType::ChannelProgressMedia,
281        EslEventType::ChannelOutgoing,
282        EslEventType::ChannelPark,
283        EslEventType::ChannelUnpark,
284        EslEventType::ChannelApplication,
285        EslEventType::ChannelOriginate,
286        EslEventType::ChannelUuid,
287        EslEventType::ChannelData,
288    ];
289
290    /// In-call events: DTMF, VAD speech detection, media security, and call updates.
291    ///
292    /// Events that fire during an established call, tied to RTP/media activity
293    /// rather than signaling state transitions.
294    ///
295    /// ```rust
296    /// use freeswitch_types::EslEventType;
297    /// assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Dtmf));
298    /// assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Talk));
299    /// ```
300    pub const IN_CALL_EVENTS: &[EslEventType] = &[
301        EslEventType::Dtmf,
302        EslEventType::Talk,
303        EslEventType::Notalk,
304        EslEventType::CallSecure,
305        EslEventType::CallUpdate,
306        EslEventType::RecvRtcpMessage,
307        EslEventType::SendRtcpMessage,
308    ];
309
310    /// Media-related events: playback, recording, media bugs, and detection.
311    ///
312    /// Useful for IVR applications that need to track media operations without
313    /// subscribing to the full channel lifecycle.
314    ///
315    /// ```rust
316    /// use freeswitch_types::EslEventType;
317    /// assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::PlaybackStart));
318    /// assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::DetectedSpeech));
319    /// ```
320    pub const MEDIA_EVENTS: &[EslEventType] = &[
321        EslEventType::PlaybackStart,
322        EslEventType::PlaybackStop,
323        EslEventType::RecordStart,
324        EslEventType::RecordStop,
325        EslEventType::StartRecording,
326        EslEventType::MediaBugStart,
327        EslEventType::MediaBugStop,
328        EslEventType::DetectedSpeech,
329        EslEventType::DetectedTone,
330    ];
331
332    /// Presence and messaging events.
333    ///
334    /// For applications that track user presence (BLF, buddy lists) or
335    /// message-waiting indicators (voicemail MWI).
336    ///
337    /// ```rust
338    /// use freeswitch_types::EslEventType;
339    /// assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::PresenceIn));
340    /// assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::MessageWaiting));
341    /// ```
342    pub const PRESENCE_EVENTS: &[EslEventType] = &[
343        EslEventType::PresenceIn,
344        EslEventType::PresenceOut,
345        EslEventType::PresenceProbe,
346        EslEventType::MessageWaiting,
347        EslEventType::MessageQuery,
348        EslEventType::Roster,
349    ];
350
351    /// System lifecycle events.
352    ///
353    /// Server startup/shutdown, heartbeats, module loading, and XML reloads.
354    /// Useful for monitoring dashboards and operational tooling.
355    ///
356    /// ```rust
357    /// use freeswitch_types::EslEventType;
358    /// assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Heartbeat));
359    /// assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Shutdown));
360    /// ```
361    pub const SYSTEM_EVENTS: &[EslEventType] = &[
362        EslEventType::Startup,
363        EslEventType::Shutdown,
364        EslEventType::ShutdownRequested,
365        EslEventType::Heartbeat,
366        EslEventType::SessionHeartbeat,
367        EslEventType::SessionCrash,
368        EslEventType::ModuleLoad,
369        EslEventType::ModuleUnload,
370        EslEventType::ReloadXml,
371    ];
372
373    /// Conference-related events.
374    ///
375    /// ```rust
376    /// use freeswitch_types::EslEventType;
377    /// assert!(EslEventType::CONFERENCE_EVENTS.contains(&EslEventType::ConferenceData));
378    /// ```
379    pub const CONFERENCE_EVENTS: &[EslEventType] = &[
380        EslEventType::ConferenceDataQuery,
381        EslEventType::ConferenceData,
382    ];
383}
384
385/// Error returned when parsing an unknown event type string.
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct ParseEventTypeError(pub String);
388
389impl fmt::Display for ParseEventTypeError {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        write!(f, "unknown event type: {}", self.0)
392    }
393}
394
395impl std::error::Error for ParseEventTypeError {}
396
397/// Event priority levels matching FreeSWITCH `esl_priority_t`
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
400#[non_exhaustive]
401pub enum EslEventPriority {
402    /// Default priority.
403    Normal,
404    /// Lower than normal.
405    Low,
406    /// Higher than normal.
407    High,
408}
409
410impl fmt::Display for EslEventPriority {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        match self {
413            EslEventPriority::Normal => write!(f, "NORMAL"),
414            EslEventPriority::Low => write!(f, "LOW"),
415            EslEventPriority::High => write!(f, "HIGH"),
416        }
417    }
418}
419
420/// Error returned when parsing an invalid priority string.
421#[derive(Debug, Clone, PartialEq, Eq)]
422pub struct ParsePriorityError(pub String);
423
424impl fmt::Display for ParsePriorityError {
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        write!(f, "unknown priority: {}", self.0)
427    }
428}
429
430impl std::error::Error for ParsePriorityError {}
431
432impl FromStr for EslEventPriority {
433    type Err = ParsePriorityError;
434
435    fn from_str(s: &str) -> Result<Self, Self::Err> {
436        match s {
437            "NORMAL" => Ok(EslEventPriority::Normal),
438            "LOW" => Ok(EslEventPriority::Low),
439            "HIGH" => Ok(EslEventPriority::High),
440            _ => Err(ParsePriorityError(s.to_string())),
441        }
442    }
443}
444
445/// ESL Event structure containing headers and optional body
446#[derive(Debug, Clone, Eq)]
447#[cfg_attr(feature = "serde", derive(serde::Serialize))]
448pub struct EslEvent {
449    event_type: Option<EslEventType>,
450    headers: HashMap<String, String>,
451    #[cfg_attr(feature = "serde", serde(skip))]
452    original_keys: HashMap<String, String>,
453    body: Option<String>,
454}
455
456impl EslEvent {
457    /// Create a new empty event
458    pub fn new() -> Self {
459        Self {
460            event_type: None,
461            headers: HashMap::new(),
462            original_keys: HashMap::new(),
463            body: None,
464        }
465    }
466
467    /// Create event with specified type
468    pub fn with_type(event_type: EslEventType) -> Self {
469        Self {
470            event_type: Some(event_type),
471            headers: HashMap::new(),
472            original_keys: HashMap::new(),
473            body: None,
474        }
475    }
476
477    /// Parsed event type, if recognized.
478    pub fn event_type(&self) -> Option<EslEventType> {
479        self.event_type
480    }
481
482    /// Override the event type.
483    pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
484        self.event_type = event_type;
485    }
486
487    /// Look up a header by its [`EventHeader`] enum variant (case-sensitive).
488    ///
489    /// For headers not covered by `EventHeader`, use [`header_str()`](Self::header_str).
490    pub fn header(&self, name: EventHeader) -> Option<&str> {
491        self.headers
492            .get(name.as_str())
493            .map(|s| s.as_str())
494    }
495
496    /// Look up a header by name, trying the canonical key first then falling
497    /// back through the alias map for non-canonical lookups.
498    ///
499    /// Use [`header()`](Self::header) with an [`EventHeader`] variant for known
500    /// headers. This method is for headers not (yet) covered by the enum,
501    /// such as custom `X-` headers or FreeSWITCH headers added after this
502    /// library was published.
503    pub fn header_str(&self, name: &str) -> Option<&str> {
504        self.headers
505            .get(name)
506            .or_else(|| {
507                self.original_keys
508                    .get(name)
509                    .and_then(|normalized| {
510                        self.headers
511                            .get(normalized)
512                    })
513            })
514            .map(|s| s.as_str())
515    }
516
517    /// Look up a channel variable by its bare name.
518    ///
519    /// Equivalent to [`variable()`](Self::variable) but matches the
520    /// [`HeaderLookup`] trait signature.
521    pub fn variable_str(&self, name: &str) -> Option<&str> {
522        let key = format!("variable_{}", name);
523        self.header_str(&key)
524    }
525
526    /// All headers as a map.
527    pub fn headers(&self) -> &HashMap<String, String> {
528        &self.headers
529    }
530
531    /// Set or overwrite a header, normalizing the key.
532    pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
533        let original = name.into();
534        let normalized = normalize_header_key(&original);
535        if original != normalized {
536            self.original_keys
537                .insert(original, normalized.clone());
538        }
539        self.headers
540            .insert(normalized, value.into());
541    }
542
543    /// Remove a header, returning its value if it existed.
544    ///
545    /// Accepts both canonical and original (non-normalized) key names.
546    pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
547        let name = name.as_ref();
548        if let Some(value) = self
549            .headers
550            .remove(name)
551        {
552            return Some(value);
553        }
554        if let Some(normalized) = self
555            .original_keys
556            .remove(name)
557        {
558            return self
559                .headers
560                .remove(&normalized);
561        }
562        None
563    }
564
565    /// Event body (the content after the blank line in plain-text events).
566    pub fn body(&self) -> Option<&str> {
567        self.body
568            .as_deref()
569    }
570
571    /// Set the event body.
572    pub fn set_body(&mut self, body: impl Into<String>) {
573        self.body = Some(body.into());
574    }
575
576    /// Sets the `priority` header carried on the event.
577    ///
578    /// FreeSWITCH stores this as metadata but does **not** use it for dispatch
579    /// ordering — all events are delivered FIFO regardless of priority.
580    pub fn set_priority(&mut self, priority: EslEventPriority) {
581        self.set_header(EventHeader::Priority.as_str(), priority.to_string());
582    }
583
584    /// Append a value to a multi-value header (PUSH semantics).
585    ///
586    /// If the header doesn't exist, sets it as a plain value.
587    /// If it exists as a plain value, converts to `ARRAY::old|:new`.
588    /// If it already has an `ARRAY::` prefix, appends the new value.
589    ///
590    /// ```
591    /// # use freeswitch_types::EslEvent;
592    /// let mut event = EslEvent::new();
593    /// event.push_header("X-Test", "first");
594    /// event.push_header("X-Test", "second");
595    /// assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
596    /// ```
597    pub fn push_header(&mut self, name: &str, value: &str) {
598        self.stack_header(name, value, EslArray::push);
599    }
600
601    /// Prepend a value to a multi-value header (UNSHIFT semantics).
602    ///
603    /// Same conversion rules as `push_header()`, but inserts at the front.
604    ///
605    /// ```
606    /// # use freeswitch_types::EslEvent;
607    /// let mut event = EslEvent::new();
608    /// event.set_header("X-Test", "ARRAY::b|:c");
609    /// event.unshift_header("X-Test", "a");
610    /// assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
611    /// ```
612    pub fn unshift_header(&mut self, name: &str, value: &str) {
613        self.stack_header(name, value, EslArray::unshift);
614    }
615
616    fn stack_header(&mut self, name: &str, value: &str, op: fn(&mut EslArray, String)) {
617        match self
618            .headers
619            .get(name)
620        {
621            None => {
622                self.set_header(name, value);
623            }
624            Some(existing) => {
625                let mut arr = match EslArray::parse(existing) {
626                    Some(arr) => arr,
627                    None => EslArray::new(vec![existing.clone()]),
628                };
629                op(&mut arr, value.into());
630                self.set_header(name, arr.to_string());
631            }
632        }
633    }
634
635    /// Check whether this event matches the given type.
636    pub fn is_event_type(&self, event_type: EslEventType) -> bool {
637        self.event_type == Some(event_type)
638    }
639
640    /// Serialize to ESL plain text wire format with percent-encoded header values.
641    ///
642    /// This is the inverse of `EslParser::parse_plain_event()`. The output can
643    /// be fed back through the parser to reconstruct an equivalent `EslEvent`
644    /// (round-trip).
645    ///
646    /// `Event-Name` is emitted first, remaining headers are sorted alphabetically
647    /// for deterministic output. `Content-Length` from stored headers is skipped
648    /// and recomputed from the body if present.
649    pub fn to_plain_format(&self) -> String {
650        use std::fmt::Write;
651        let mut result = String::new();
652
653        let event_name_key = EventHeader::EventName.as_str();
654        if let Some(event_name) = self
655            .headers
656            .get(event_name_key)
657        {
658            let _ = writeln!(
659                result,
660                "{}: {}",
661                event_name_key,
662                percent_encode(event_name.as_bytes(), NON_ALPHANUMERIC)
663            );
664        }
665
666        let mut sorted_headers: Vec<_> = self
667            .headers
668            .iter()
669            .filter(|(k, _)| k.as_str() != event_name_key && k.as_str() != "Content-Length")
670            .collect();
671        sorted_headers.sort_by_key(|(k, _)| k.as_str());
672
673        for (key, value) in sorted_headers {
674            let _ = writeln!(
675                result,
676                "{}: {}",
677                key,
678                percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
679            );
680        }
681
682        if let Some(body) = &self.body {
683            let _ = writeln!(result, "Content-Length: {}", body.len());
684            result.push('\n');
685            result.push_str(body);
686        } else {
687            result.push('\n');
688        }
689
690        result
691    }
692}
693
694impl Default for EslEvent {
695    fn default() -> Self {
696        Self::new()
697    }
698}
699
700impl HeaderLookup for EslEvent {
701    fn header_str(&self, name: &str) -> Option<&str> {
702        EslEvent::header_str(self, name)
703    }
704
705    fn variable_str(&self, name: &str) -> Option<&str> {
706        let key = format!("variable_{}", name);
707        self.header_str(&key)
708    }
709}
710
711impl PartialEq for EslEvent {
712    fn eq(&self, other: &Self) -> bool {
713        self.event_type == other.event_type
714            && self.headers == other.headers
715            && self.body == other.body
716    }
717}
718
719#[cfg(feature = "serde")]
720impl<'de> serde::Deserialize<'de> for EslEvent {
721    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
722    where
723        D: serde::Deserializer<'de>,
724    {
725        #[derive(serde::Deserialize)]
726        struct Raw {
727            event_type: Option<EslEventType>,
728            headers: HashMap<String, String>,
729            body: Option<String>,
730        }
731        let raw = Raw::deserialize(deserializer)?;
732        let mut event = EslEvent::new();
733        event.event_type = raw.event_type;
734        event.body = raw.body;
735        for (k, v) in raw.headers {
736            event.set_header(k, v);
737        }
738        Ok(event)
739    }
740}
741
742#[cfg(test)]
743mod tests {
744    use super::*;
745
746    #[test]
747    fn test_notify_in_parse() {
748        assert_eq!(
749            EslEventType::parse_event_type("NOTIFY_IN"),
750            Some(EslEventType::NotifyIn)
751        );
752        assert_eq!(EslEventType::parse_event_type("notify_in"), None);
753    }
754
755    #[test]
756    fn test_notify_in_display() {
757        assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
758    }
759
760    #[test]
761    fn test_notify_in_distinct_from_notify() {
762        assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
763        assert_ne!(
764            EslEventType::Notify.to_string(),
765            EslEventType::NotifyIn.to_string()
766        );
767    }
768
769    #[test]
770    fn test_wire_names_match_c_esl() {
771        assert_eq!(
772            EslEventType::ChannelOutgoing.to_string(),
773            "CHANNEL_OUTGOING"
774        );
775        assert_eq!(EslEventType::Api.to_string(), "API");
776        assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
777        assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
778        assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
779        assert_eq!(EslEventType::Text.to_string(), "TEXT");
780        assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
781
782        assert_eq!(
783            EslEventType::parse_event_type("CHANNEL_OUTGOING"),
784            Some(EslEventType::ChannelOutgoing)
785        );
786        assert_eq!(
787            EslEventType::parse_event_type("API"),
788            Some(EslEventType::Api)
789        );
790        assert_eq!(
791            EslEventType::parse_event_type("RELOADXML"),
792            Some(EslEventType::ReloadXml)
793        );
794        assert_eq!(
795            EslEventType::parse_event_type("PRESENCE_IN"),
796            Some(EslEventType::PresenceIn)
797        );
798    }
799
800    #[test]
801    fn test_event_type_from_str() {
802        assert_eq!(
803            "CHANNEL_ANSWER".parse::<EslEventType>(),
804            Ok(EslEventType::ChannelAnswer)
805        );
806        assert!("channel_answer"
807            .parse::<EslEventType>()
808            .is_err());
809        assert!("UNKNOWN_EVENT"
810            .parse::<EslEventType>()
811            .is_err());
812    }
813
814    #[test]
815    fn test_remove_header() {
816        let mut event = EslEvent::new();
817        event.set_header("Foo", "bar");
818        event.set_header("Baz", "qux");
819
820        let removed = event.remove_header("Foo");
821        assert_eq!(removed, Some("bar".to_string()));
822        assert!(event
823            .header_str("Foo")
824            .is_none());
825        assert_eq!(event.header_str("Baz"), Some("qux"));
826
827        let removed_again = event.remove_header("Foo");
828        assert_eq!(removed_again, None);
829    }
830
831    #[test]
832    fn test_to_plain_format_basic() {
833        let mut event = EslEvent::with_type(EslEventType::Heartbeat);
834        event.set_header("Event-Name", "HEARTBEAT");
835        event.set_header("Core-UUID", "abc-123");
836
837        let plain = event.to_plain_format();
838
839        assert!(plain.starts_with("Event-Name: "));
840        assert!(plain.contains("Core-UUID: "));
841        assert!(plain.ends_with("\n\n"));
842    }
843
844    #[test]
845    fn test_to_plain_format_percent_encoding() {
846        let mut event = EslEvent::with_type(EslEventType::Heartbeat);
847        event.set_header("Event-Name", "HEARTBEAT");
848        event.set_header("Up-Time", "0 years, 0 days");
849
850        let plain = event.to_plain_format();
851
852        assert!(!plain.contains("0 years, 0 days"));
853        assert!(plain.contains("Up-Time: "));
854        assert!(plain.contains("%20"));
855    }
856
857    #[test]
858    fn test_to_plain_format_with_body() {
859        let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
860        event.set_header("Event-Name", "BACKGROUND_JOB");
861        event.set_header("Job-UUID", "def-456");
862        event.set_body("+OK result\n".to_string());
863
864        let plain = event.to_plain_format();
865
866        assert!(plain.contains("Content-Length: 11\n"));
867        assert!(plain.ends_with("\n\n+OK result\n"));
868    }
869
870    #[test]
871    fn test_set_priority_normal() {
872        let mut event = EslEvent::new();
873        event.set_priority(EslEventPriority::Normal);
874        assert_eq!(
875            event
876                .priority()
877                .unwrap(),
878            Some(EslEventPriority::Normal)
879        );
880        assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
881    }
882
883    #[test]
884    fn test_set_priority_high() {
885        let mut event = EslEvent::new();
886        event.set_priority(EslEventPriority::High);
887        assert_eq!(
888            event
889                .priority()
890                .unwrap(),
891            Some(EslEventPriority::High)
892        );
893        assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
894    }
895
896    #[test]
897    fn test_priority_display() {
898        assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
899        assert_eq!(EslEventPriority::Low.to_string(), "LOW");
900        assert_eq!(EslEventPriority::High.to_string(), "HIGH");
901    }
902
903    #[test]
904    fn test_priority_from_str() {
905        assert_eq!(
906            "NORMAL".parse::<EslEventPriority>(),
907            Ok(EslEventPriority::Normal)
908        );
909        assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
910        assert_eq!(
911            "HIGH".parse::<EslEventPriority>(),
912            Ok(EslEventPriority::High)
913        );
914        assert!("INVALID"
915            .parse::<EslEventPriority>()
916            .is_err());
917    }
918
919    #[test]
920    fn test_priority_from_str_rejects_wrong_case() {
921        assert!("normal"
922            .parse::<EslEventPriority>()
923            .is_err());
924        assert!("Low"
925            .parse::<EslEventPriority>()
926            .is_err());
927        assert!("hIgH"
928            .parse::<EslEventPriority>()
929            .is_err());
930    }
931
932    #[test]
933    fn test_push_header_new() {
934        let mut event = EslEvent::new();
935        event.push_header("X-Test", "first");
936        assert_eq!(event.header_str("X-Test"), Some("first"));
937    }
938
939    #[test]
940    fn test_push_header_existing_plain() {
941        let mut event = EslEvent::new();
942        event.set_header("X-Test", "first");
943        event.push_header("X-Test", "second");
944        assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
945    }
946
947    #[test]
948    fn test_push_header_existing_array() {
949        let mut event = EslEvent::new();
950        event.set_header("X-Test", "ARRAY::a|:b");
951        event.push_header("X-Test", "c");
952        assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
953    }
954
955    #[test]
956    fn test_unshift_header_new() {
957        let mut event = EslEvent::new();
958        event.unshift_header("X-Test", "only");
959        assert_eq!(event.header_str("X-Test"), Some("only"));
960    }
961
962    #[test]
963    fn test_unshift_header_existing_array() {
964        let mut event = EslEvent::new();
965        event.set_header("X-Test", "ARRAY::b|:c");
966        event.unshift_header("X-Test", "a");
967        assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
968    }
969
970    #[test]
971    fn test_sendevent_with_priority_wire_format() {
972        let mut event = EslEvent::with_type(EslEventType::Custom);
973        event.set_header("Event-Name", "CUSTOM");
974        event.set_header("Event-Subclass", "test::priority");
975        event.set_priority(EslEventPriority::High);
976
977        let plain = event.to_plain_format();
978        assert!(plain.contains("priority: HIGH\n"));
979    }
980
981    #[test]
982    fn test_convenience_accessors() {
983        let mut event = EslEvent::new();
984        event.set_header("Channel-Name", "sofia/internal/1000@example.com");
985        event.set_header("Caller-Caller-ID-Number", "1000");
986        event.set_header("Caller-Caller-ID-Name", "Alice");
987        event.set_header("Hangup-Cause", "NORMAL_CLEARING");
988        event.set_header("Event-Subclass", "sofia::register");
989        event.set_header("variable_sip_from_display", "Bob");
990
991        assert_eq!(
992            event.channel_name(),
993            Some("sofia/internal/1000@example.com")
994        );
995        assert_eq!(event.caller_id_number(), Some("1000"));
996        assert_eq!(event.caller_id_name(), Some("Alice"));
997        assert_eq!(
998            event
999                .hangup_cause()
1000                .unwrap(),
1001            Some(crate::channel::HangupCause::NormalClearing)
1002        );
1003        assert_eq!(event.event_subclass(), Some("sofia::register"));
1004        assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1005        assert_eq!(event.variable_str("nonexistent"), None);
1006    }
1007
1008    #[test]
1009    fn test_event_format_from_str() {
1010        assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1011        assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1012        assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1013        assert!("foo"
1014            .parse::<EventFormat>()
1015            .is_err());
1016    }
1017
1018    #[test]
1019    fn test_event_format_from_str_case_insensitive() {
1020        assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1021        assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1022        assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1023        assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1024    }
1025
1026    #[test]
1027    fn test_event_format_from_content_type() {
1028        assert_eq!(
1029            EventFormat::from_content_type("text/event-json"),
1030            Ok(EventFormat::Json)
1031        );
1032        assert_eq!(
1033            EventFormat::from_content_type("text/event-xml"),
1034            Ok(EventFormat::Xml)
1035        );
1036        assert_eq!(
1037            EventFormat::from_content_type("text/event-plain"),
1038            Ok(EventFormat::Plain)
1039        );
1040        assert!(EventFormat::from_content_type("unknown").is_err());
1041    }
1042
1043    // --- EslEvent accessor tests (via HeaderLookup trait) ---
1044
1045    #[test]
1046    fn test_event_channel_state_accessor() {
1047        use crate::channel::ChannelState;
1048        let mut event = EslEvent::new();
1049        event.set_header("Channel-State", "CS_EXECUTE");
1050        assert_eq!(
1051            event
1052                .channel_state()
1053                .unwrap(),
1054            Some(ChannelState::CsExecute)
1055        );
1056    }
1057
1058    #[test]
1059    fn test_event_channel_state_number_accessor() {
1060        use crate::channel::ChannelState;
1061        let mut event = EslEvent::new();
1062        event.set_header("Channel-State-Number", "4");
1063        assert_eq!(
1064            event
1065                .channel_state_number()
1066                .unwrap(),
1067            Some(ChannelState::CsExecute)
1068        );
1069    }
1070
1071    #[test]
1072    fn test_event_call_state_accessor() {
1073        use crate::channel::CallState;
1074        let mut event = EslEvent::new();
1075        event.set_header("Channel-Call-State", "ACTIVE");
1076        assert_eq!(
1077            event
1078                .call_state()
1079                .unwrap(),
1080            Some(CallState::Active)
1081        );
1082    }
1083
1084    #[test]
1085    fn test_event_answer_state_accessor() {
1086        use crate::channel::AnswerState;
1087        let mut event = EslEvent::new();
1088        event.set_header("Answer-State", "answered");
1089        assert_eq!(
1090            event
1091                .answer_state()
1092                .unwrap(),
1093            Some(AnswerState::Answered)
1094        );
1095    }
1096
1097    #[test]
1098    fn test_event_call_direction_accessor() {
1099        use crate::channel::CallDirection;
1100        let mut event = EslEvent::new();
1101        event.set_header("Call-Direction", "inbound");
1102        assert_eq!(
1103            event
1104                .call_direction()
1105                .unwrap(),
1106            Some(CallDirection::Inbound)
1107        );
1108    }
1109
1110    #[test]
1111    fn test_event_typed_accessors_missing_headers() {
1112        let event = EslEvent::new();
1113        assert_eq!(
1114            event
1115                .channel_state()
1116                .unwrap(),
1117            None
1118        );
1119        assert_eq!(
1120            event
1121                .channel_state_number()
1122                .unwrap(),
1123            None
1124        );
1125        assert_eq!(
1126            event
1127                .call_state()
1128                .unwrap(),
1129            None
1130        );
1131        assert_eq!(
1132            event
1133                .answer_state()
1134                .unwrap(),
1135            None
1136        );
1137        assert_eq!(
1138            event
1139                .call_direction()
1140                .unwrap(),
1141            None
1142        );
1143    }
1144
1145    // --- Repeating SIP header tests ---
1146
1147    #[test]
1148    fn test_sip_p_asserted_identity_comma_separated() {
1149        let mut event = EslEvent::new();
1150        // RFC 3325: P-Asserted-Identity can carry two identities (one sip:, one tel:)
1151        // FreeSWITCH stores the comma-separated value as a single channel variable
1152        event.set_header(
1153            "variable_sip_P-Asserted-Identity",
1154            "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1155        );
1156
1157        assert_eq!(
1158            event.variable_str("sip_P-Asserted-Identity"),
1159            Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1160        );
1161    }
1162
1163    #[test]
1164    fn test_sip_p_asserted_identity_array_format() {
1165        let mut event = EslEvent::new();
1166        // When FreeSWITCH stores repeated SIP headers via ARRAY format
1167        event.push_header(
1168            "variable_sip_P-Asserted-Identity",
1169            "<sip:alice@atlanta.example.com>",
1170        );
1171        event.push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>");
1172
1173        let raw = event
1174            .header_str("variable_sip_P-Asserted-Identity")
1175            .unwrap();
1176        assert_eq!(
1177            raw,
1178            "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1179        );
1180
1181        let arr = crate::variables::EslArray::parse(raw).unwrap();
1182        assert_eq!(arr.len(), 2);
1183        assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1184        assert_eq!(arr.items()[1], "<tel:+15551234567>");
1185    }
1186
1187    #[test]
1188    fn test_sip_header_with_colons_in_uri() {
1189        let mut event = EslEvent::new();
1190        // SIP URIs contain colons (sip:, sips:) which must not confuse ARRAY parsing
1191        event.push_header(
1192            "variable_sip_h_Diversion",
1193            "<sip:+15551234567@gw.example.com;reason=unconditional>",
1194        );
1195        event.push_header(
1196            "variable_sip_h_Diversion",
1197            "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1198        );
1199
1200        let raw = event
1201            .header_str("variable_sip_h_Diversion")
1202            .unwrap();
1203        let arr = crate::variables::EslArray::parse(raw).unwrap();
1204        assert_eq!(arr.len(), 2);
1205        assert_eq!(
1206            arr.items()[0],
1207            "<sip:+15551234567@gw.example.com;reason=unconditional>"
1208        );
1209        assert_eq!(
1210            arr.items()[1],
1211            "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1212        );
1213    }
1214
1215    #[test]
1216    fn test_sip_p_asserted_identity_plain_format_round_trip() {
1217        let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1218        event.set_header("Event-Name", "CHANNEL_CREATE");
1219        event.set_header(
1220            "variable_sip_P-Asserted-Identity",
1221            "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1222        );
1223
1224        let plain = event.to_plain_format();
1225        // The comma-separated value should be percent-encoded on the wire
1226        assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1227        // Angle brackets and comma should be encoded
1228        assert!(!plain.contains("<sip:alice"));
1229    }
1230
1231    // --- Header key normalization on EslEvent ---
1232    // set_header() normalizes keys so lookups via header(EventHeader::X)
1233    // and header_str() work regardless of the casing used at insertion.
1234
1235    #[test]
1236    fn set_header_normalizes_known_enum_variant() {
1237        let mut event = EslEvent::new();
1238        event.set_header("unique-id", "abc-123");
1239        assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1240    }
1241
1242    #[test]
1243    fn set_header_normalizes_codec_header() {
1244        let mut event = EslEvent::new();
1245        event.set_header("channel-read-codec-bit-rate", "128000");
1246        assert_eq!(
1247            event.header(EventHeader::ChannelReadCodecBitRate),
1248            Some("128000")
1249        );
1250    }
1251
1252    #[test]
1253    fn header_str_finds_by_original_key() {
1254        let mut event = EslEvent::new();
1255        event.set_header("unique-id", "abc-123");
1256        // Lookup by original non-canonical key should still work
1257        assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1258        // Lookup by canonical key also works
1259        assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1260    }
1261
1262    #[test]
1263    fn header_str_finds_unknown_dash_header_by_original() {
1264        let mut event = EslEvent::new();
1265        event.set_header("x-custom-header", "val");
1266        // Stored as Title-Case
1267        assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1268        // Original key also works via alias
1269        assert_eq!(event.header_str("x-custom-header"), Some("val"));
1270    }
1271
1272    #[test]
1273    fn set_header_underscore_passthrough_preserves_sip_h() {
1274        let mut event = EslEvent::new();
1275        event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1276        assert_eq!(
1277            event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1278            Some("val")
1279        );
1280    }
1281
1282    #[test]
1283    fn set_header_different_casing_overwrites() {
1284        let mut event = EslEvent::new();
1285        event.set_header("Unique-ID", "first");
1286        event.set_header("unique-id", "second");
1287        // Both normalize to "Unique-ID", second overwrites first
1288        assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1289    }
1290
1291    #[test]
1292    fn remove_header_by_original_key() {
1293        let mut event = EslEvent::new();
1294        event.set_header("unique-id", "abc-123");
1295        let removed = event.remove_header("unique-id");
1296        assert_eq!(removed, Some("abc-123".to_string()));
1297        assert_eq!(event.header(EventHeader::UniqueId), None);
1298    }
1299
1300    #[test]
1301    fn remove_header_by_canonical_key() {
1302        let mut event = EslEvent::new();
1303        event.set_header("unique-id", "abc-123");
1304        let removed = event.remove_header("Unique-ID");
1305        assert_eq!(removed, Some("abc-123".to_string()));
1306        assert_eq!(event.header_str("unique-id"), None);
1307    }
1308
1309    #[test]
1310    fn serde_round_trip_preserves_canonical_lookups() {
1311        let mut event = EslEvent::new();
1312        event.set_header("unique-id", "abc-123");
1313        event.set_header("channel-read-codec-bit-rate", "128000");
1314        let json = serde_json::to_string(&event).unwrap();
1315        let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1316        assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1317        assert_eq!(
1318            deserialized.header(EventHeader::ChannelReadCodecBitRate),
1319            Some("128000")
1320        );
1321    }
1322
1323    #[test]
1324    fn serde_deserialize_normalizes_external_json() {
1325        let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1326        let event: EslEvent = serde_json::from_str(json).unwrap();
1327        assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1328        assert_eq!(
1329            event.header(EventHeader::ChannelReadCodecBitRate),
1330            Some("128000")
1331        );
1332        assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1333    }
1334
1335    #[test]
1336    fn test_event_typed_accessors_invalid_values() {
1337        let mut event = EslEvent::new();
1338        event.set_header("Channel-State", "BOGUS");
1339        event.set_header("Channel-State-Number", "999");
1340        event.set_header("Channel-Call-State", "BOGUS");
1341        event.set_header("Answer-State", "bogus");
1342        event.set_header("Call-Direction", "bogus");
1343        assert!(event
1344            .channel_state()
1345            .is_err());
1346        assert!(event
1347            .channel_state_number()
1348            .is_err());
1349        assert!(event
1350            .call_state()
1351            .is_err());
1352        assert!(event
1353            .answer_state()
1354            .is_err());
1355        assert!(event
1356            .call_direction()
1357            .is_err());
1358    }
1359}