Skip to main content

freeswitch_esl_tokio/
lib.rs

1//! FreeSWITCH Event Socket Library (ESL) client for Rust
2//!
3//! This crate provides an async Rust client for FreeSWITCH's Event Socket Library (ESL),
4//! allowing applications to connect to FreeSWITCH, execute commands, and receive events.
5//!
6//! # Architecture
7//!
8//! The library uses a split reader/writer design:
9//! - [`EslClient`] (Clone + Send) — send commands from any task
10//! - [`EslEventStream`] — receive events from a background reader task
11//!
12//! # Examples
13//!
14//! ## Inbound Connection
15//!
16//! ```rust,no_run
17//! use freeswitch_esl_tokio::{EslClient, EslError};
18//!
19//! #[tokio::main]
20//! async fn main() -> Result<(), EslError> {
21//!     let (client, mut events) = EslClient::connect("localhost", 8021, "ClueCon").await?;
22//!
23//!     let response = client.api("status").await?;
24//!     println!("Status: {}", response.body().unwrap_or("No body"));
25//!
26//!     Ok(())
27//! }
28//! ```
29//!
30//! ## Outbound Mode
31//!
32//! In outbound mode, FreeSWITCH connects to *your* application via the
33//! [`socket`](https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Modules/mod_event_socket_1048924/)
34//! dialplan application. You run a TCP listener and accept connections:
35//!
36//! ```rust,no_run
37//! use freeswitch_esl_tokio::{EslClient, EslError, AppCommand, EventFormat, HeaderLookup};
38//! use tokio::net::TcpListener;
39//!
40//! #[tokio::main]
41//! async fn main() -> Result<(), EslError> {
42//!     let listener = TcpListener::bind("0.0.0.0:8040").await
43//!         .map_err(EslError::from)?;
44//!
45//!     let (client, mut events) = EslClient::accept_outbound(&listener).await?;
46//!
47//!     // First command must be connect_session — establishes the session
48//!     // and returns all channel variables as headers.
49//!     let channel_data = client.connect_session().await?;
50//!     println!("Channel: {}", channel_data.channel_name().unwrap_or("?"));
51//!
52//!     client.myevents(EventFormat::Plain).await?;
53//!     client.linger_timeout(None).await?; // keep socket open after hangup
54//!     client.resume().await?;     // resume dialplan on disconnect
55//!
56//!     client.send_command(AppCommand::answer()).await?;
57//!     client.send_command(AppCommand::playback("ivr/ivr-welcome.wav")).await?;
58//!
59//!     while let Some(Ok(event)) = events.recv().await {
60//!         println!("{:?}", event.event_type());
61//!     }
62//!     Ok(())
63//! }
64//! ```
65//!
66//! Configure FreeSWITCH to connect to your app:
67//! ```xml
68//! <action application="socket" data="127.0.0.1:8040 async full"/>
69//! ```
70//!
71//! See [`docs/outbound-esl-quirks.md`](https://github.com/ticpu/freeswitch-esl-tokio/blob/master/docs/outbound-esl-quirks.md)
72//! for protocol details and command availability by mode.
73//!
74//! ## Command Builders
75//!
76//! Typed builders for common API commands — no raw string assembly needed:
77//!
78//! ```rust
79//! use freeswitch_esl_tokio::{Originate, Endpoint, ApplicationList, Application};
80//!
81//! let cmd = Originate {
82//!     endpoint: Endpoint::SofiaGateway {
83//!         uri: "18005551234".into(),
84//!         profile: None,
85//!         gateway: "my_provider".into(),
86//!         variables: None,
87//!     },
88//!     applications: ApplicationList(vec![
89//!         Application::new("park", None::<&str>),
90//!     ]),
91//!     dialplan: None,
92//!     context: None,
93//!     cid_name: Some("Outbound Call".into()),
94//!     cid_num: Some("5551234".into()),
95//!     timeout: Some(30),
96//! };
97//!
98//! // Use with client.api(&cmd.to_string()) or client.bgapi(&cmd.to_string())
99//! assert!(cmd.to_string().contains("sofia/gateway/my_provider/18005551234"));
100//! ```
101//!
102//! See the [`commands`] module for `Originate`, `UuidBridge`, `UuidTransfer`,
103//! and other builders.
104//!
105//! ## Event Subscription
106//!
107//! ```rust,no_run
108//! use freeswitch_esl_tokio::{EslClient, EslEventType, EventFormat};
109//!
110//! #[tokio::main]
111//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
112//!     let (client, mut events) = EslClient::connect("localhost", 8021, "ClueCon").await?;
113//!
114//!     client.subscribe_events(EventFormat::Plain, &[
115//!         EslEventType::ChannelAnswer,
116//!         EslEventType::ChannelHangup
117//!     ]).await?;
118//!
119//!     while let Some(Ok(event)) = events.recv().await {
120//!         println!("Received event: {:?}", event.event_type());
121//!     }
122//!
123//!     Ok(())
124//! }
125//! ```
126
127#[macro_use]
128mod macros;
129
130pub mod app;
131pub mod bgjob;
132pub mod channel;
133pub mod commands;
134pub mod connection;
135pub mod error;
136pub mod event;
137pub mod headers;
138pub mod lookup;
139pub mod variables;
140
141pub(crate) mod buffer;
142pub(crate) mod command;
143pub mod constants;
144pub(crate) mod protocol;
145
146pub use app::dptools::AppCommand;
147pub use bgjob::{BgJobResult, BgJobTracker};
148pub use channel::{
149    AnswerState, CallDirection, CallState, ChannelState, ChannelTimetable, ParseTimetableError,
150    TimetablePrefix,
151};
152pub use command::parse_api_body;
153pub use command::{CommandBuilder, EslResponse, ReplyStatus};
154pub use commands::{
155    Application, ApplicationList, ConferenceDtmf, ConferenceHold, ConferenceMute, DialplanType,
156    Endpoint, HoldAction, MuteAction, Originate, OriginateError, UuidAnswer, UuidBridge,
157    UuidDeflect, UuidGetVar, UuidHold, UuidKill, UuidSendDtmf, UuidSetVar, UuidTransfer, Variables,
158    VariablesType,
159};
160pub use connection::{
161    ConnectionMode, ConnectionStatus, DisconnectReason, EslClient, EslConnectOptions,
162    EslEventStream,
163};
164pub use constants::DEFAULT_ESL_PORT;
165pub use error::{EslError, EslResult};
166pub use event::{EslEvent, EslEventPriority, EslEventType, EventFormat};
167pub use headers::{EventHeader, ParseEventHeaderError};
168pub use lookup::HeaderLookup;
169pub use variables::{
170    ChannelVariable, EslArray, MultipartBody, MultipartItem, ParseChannelVariableError,
171};