cloudpub_sdk/
lib.rs

1//! # CloudPub Rust SDK
2//!
3//! CloudPub Rust SDK предоставляет простой интерфейс для взаимодействия с
4//! платформой CloudPub. CloudPub обеспечивает безопасное туннелирование и публикацию сервисов,
5//! позволяя безопасно открывать локальные сервисы в интернет.
6//!
7//! ## Возможности
8//!
9//! - **Публикация сервисов**: Открытие локальных сервисов (HTTP, HTTPS, TCP, UDP, WebSocket, RTSP) в интернет
10//! - **Безопасное туннелирование**: Все соединения шифруются и аутентифицируются
11//! - **Несколько протоколов**: Поддержка различных протоколов, включая HTTP/HTTPS, TCP/UDP, WebSocket и RTSP
12//! - **Аутентификация**: Встроенная поддержка аутентификации на основе токенов и учетных данных
13//! - **Управление сервисами**: Запуск, остановка, список и управление опубликованными сервисами
14//! - **Событийно-ориентированная архитектура**: Поддержка async/await с управлением состоянием на основе событий
15//!
16//! ## Быстрый старт
17//!
18//! Добавьте SDK в ваш `Cargo.toml`:
19//!
20//! ```toml
21//! [dependencies]
22//! cloudpub-sdk = "2.4.2"
23//! ```
24//!
25//! ## Базовое использование
26//!
27//! ### Простая публикация сервиса
28//!
29//! ```no_run
30//! use cloudpub_sdk::Connection;
31//! use cloudpub_sdk::protocol::{Protocol, Auth, Endpoint};
32//!
33//! #[tokio::main]
34//! async fn main() -> anyhow::Result<()> {
35//!     // Создание соединения с учетными данными
36//!     let mut conn = Connection::builder()
37//!         .credentials("user@example.com", "password")
38//!         .build()
39//!         .await?;
40//!
41//!     // Публикация локального веб-сервиса
42//!     let endpoint = conn.publish(
43//!         Protocol::Http,
44//!         "localhost:3000".to_string(),
45//!         Some("My Web App".to_string()),
46//!         Some(Auth::None),
47//!         None, // acl
48//!         None, // headers
49//!         None, // rules
50//!     ).await?;
51//!
52//!     println!("Service published at: {}", endpoint.as_url());
53//!
54//!     // Ожидание сигнала Ctrl+C для остановки
55//!     tokio::signal::ctrl_c().await?;
56//!
57//!     // Очистка
58//!     conn.unpublish(endpoint.guid).await?;
59//!     Ok(())
60//! }
61//! ```
62//!
63//! ### Расширенная публикация сервиса с ACL, заголовками и правилами фильтрации
64//!
65//! ```no_run
66//! use cloudpub_sdk::Connection;
67//! use cloudpub_sdk::protocol::{
68//!     Protocol, Auth, Endpoint, Acl, Header, FilterRule,
69//!     Role, FilterAction
70//! };
71//!
72//! #[tokio::main]
73//! async fn main() -> anyhow::Result<()> {
74//!     let mut conn = Connection::builder()
75//!         .credentials("admin@example.com", "password")
76//!         .build()
77//!         .await?;
78//!
79//!     // Настройка списка контроля доступа
80//!     let acl = vec![
81//!         Acl { user: "admin@example.com".to_string(), role: Role::Admin as i32 },
82//!         Acl { user: "user@example.com".to_string(), role: Role::Reader as i32 },
83//!         Acl { user: "writer@example.com".to_string(), role: Role::Writer as i32 },
84//!     ];
85//!
86//!     // Добавление пользовательских HTTP заголовков
87//!     let headers = vec![
88//!         Header { name: "X-API-Version".to_string(), value: "2.0".to_string() },
89//!         Header { name: "Access-Control-Allow-Origin".to_string(), value: "*".to_string() },
90//!         Header { name: "X-Custom-Header".to_string(), value: "CloudPub".to_string() },
91//!     ];
92//!
93//!     // Определение правил фильтрации для обработки запросов
94//!     let rules = vec![
95//!         FilterRule {
96//!             order: 0,
97//!             action_type: FilterAction::FilterAllow as i32,
98//!             action_value: None,
99//!             data: "http.path starts_with \"/api/\" and http.headers[\"X-API-Key\"] contains \"valid\"".to_string(),
100//!         },
101//!         FilterRule {
102//!             order: 1,
103//!             action_type: FilterAction::FilterDeny as i32,
104//!             action_value: None,
105//!             data: "http.path matches \"^/admin.*\" and not (ip.src >= 192.168.1.0 and ip.src <= 192.168.1.255)".to_string(),
106//!         },
107//!         FilterRule {
108//!             order: 2,
109//!             action_type: FilterAction::FilterRedirect as i32,
110//!             action_value: Some("https://api.example.com/v2/".to_string()),  // URL перенаправления
111//!             data: "http.path starts_with \"/api/v1/\"".to_string(),
112//!         },
113//!     ];
114//!
115//!     // Публикация сервиса со всеми расширенными возможностями
116//!     let endpoint = conn.publish(
117//!         Protocol::Https,
118//!         "localhost:8443".to_string(),
119//!         Some("Secured API Server".to_string()),
120//!         Some(Auth::Basic),
121//!         Some(acl),
122//!         Some(headers),
123//!         Some(rules),
124//!     ).await?;
125//!
126//!     println!("Advanced service published at: {}", endpoint.as_url());
127//!
128//!     Ok(())
129//! }
130//! ```
131//!
132//! ### Расширенная конфигурация
133//!
134//! ```no_run
135//! # async fn example() -> anyhow::Result<()> {
136//! use cloudpub_sdk::Connection;
137//! use std::time::Duration;
138//!
139//! // Создание соединения с расширенной конфигурацией
140//! let conn = Connection::builder()
141//!     .config_path("/custom/path/config.toml")  // Пользовательский файл конфигурации
142//!     .log_level("debug")                       // Включить отладочное логирование
143//!     .verbose(true)                            // Вывод логов в консоль
144//!     .timeout(Duration::from_secs(60))         // Установить таймаут операции
145//!     .token("existing-auth-token")             // Использовать существующий токен
146//!     .build()
147//!     .await?;
148//! # Ok(())
149//! # }
150//! ```
151//!
152//! ## Расширенные возможности
153//!
154//! ### Списки контроля доступа (ACL)
155//!
156//! Контроль доступа к вашим сервисам на основе ролей пользователей:
157//!
158//! ```no_run
159//! # use cloudpub_sdk::protocol::{Acl, Role};
160//! let acl = vec![
161//!     Acl {
162//!         user: "admin@example.com".to_string(),
163//!         role: Role::Admin as i32  // Полный доступ
164//!     },
165//!     Acl {
166//!         user: "reader@example.com".to_string(),
167//!         role: Role::Reader as i32  // Доступ только на чтение
168//!     },
169//!     Acl {
170//!         user: "writer@example.com".to_string(),
171//!         role: Role::Writer as i32  // Доступ на чтение-запись
172//!     },
173//!     Acl {
174//!         user: "guest@example.com".to_string(),
175//!         role: Role::Nobody as i32  // Нет доступа
176//!     },
177//! ];
178//! ```
179//!
180//! ### Пользовательские HTTP заголовки
181//!
182//! Добавление пользовательских заголовков в HTTP/HTTPS ответы:
183//!
184//! ```no_run
185//! # use cloudpub_sdk::protocol::Header;
186//! let headers = vec![
187//!     Header {
188//!         name: "X-API-Version".to_string(),
189//!         value: "2.0".to_string()
190//!     },
191//!     Header {
192//!         name: "Access-Control-Allow-Origin".to_string(),
193//!         value: "*".to_string()
194//!     },
195//!     Header {
196//!         name: "Cache-Control".to_string(),
197//!         value: "no-cache, no-store, must-revalidate".to_string()
198//!     },
199//! ];
200//! ```
201//!
202//! ### Правила фильтрации
203//!
204//! Определение правил для управления маршрутизацией запросов и доступом на основе различных параметров соединения.
205//! Правила обрабатываются по порядку (по полю `order`), и первое подходящее правило применяется.
206//!
207//! ```no_run
208//! # use cloudpub_sdk::protocol::{FilterRule, FilterAction};
209//! let rules = vec![
210//!     // Разрешить доступ к API только из локальной сети
211//!     FilterRule {
212//!         order: 0,
213//!         action_type: FilterAction::FilterAllow as i32,
214//!         action_value: None,
215//!         data: "ip.src >= 192.168.1.0 and ip.src <= 192.168.1.255 and http.path starts_with \"/api/\"".to_string(),
216//!     },
217//!
218//!     // Блокировать доступ к админ-панели снаружи
219//!     FilterRule {
220//!         order: 1,
221//!         action_type: FilterAction::FilterDeny as i32,
222//!         action_value: None,
223//!         data: "http.path matches \"^/admin.*\"".to_string(),
224//!     },
225//!
226//!     // Перенаправление старых API эндпоинтов на новую версию
227//!     // action_value содержит URL перенаправления для FilterRedirect
228//!     FilterRule {
229//!         order: 2,
230//!         action_type: FilterAction::FilterRedirect as i32,
231//!         action_value: Some("https://api.example.com/v2/".to_string()),  // Redirect URL
232//!         data: "http.path starts_with \"/api/v1/\"".to_string(),
233//!     },
234//!
235//!     // Блокировать запросы из определенных стран
236//!     FilterRule {
237//!         order: 3,
238//!         action_type: FilterAction::FilterDeny as i32,
239//!         action_value: None,
240//!         data: "geo.country != \"Россия\"".to_string(),
241//!     },
242//!
243//!     // Разрешить только аутентифицированные API запросы
244//!     FilterRule {
245//!         order: 4,
246//!         action_type: FilterAction::FilterAllow as i32,
247//!         action_value: None,
248//!         data: "http.path starts_with \"/api/\" and http.headers[\"Authorization\"] contains \"Bearer\"".to_string(),
249//!     },
250//! ];
251//! ```
252//!
253//! **Доступные переменные для фильтр выражений:**
254//! - `ip.src`, `ip.dst` - IP адреса источника и назначения
255//! - `port.src`, `port.dst` - Порты источника и назначения
256//! - `protocol` - Протокол соединения ("tcp", "udp", "http")
257//! - `http.host`, `http.path`, `http.method` - HTTP-специфичные поля
258//! - `http.headers["Header-Name"]` - Доступ к HTTP заголовкам
259//! - `geo.country`, `geo.region`, `geo.city`, `geo.code` - Данные геолокации
260//!
261//! **Операторы:**
262//! - Сравнение: `==`, `!=`, `>`, `<`, `>=`, `<=`
263//! - Строковые: `matches` (regex), `contains`, `starts_with`, `ends_with`
264//! - Логические: `and`, `or`, `not`
265//!
266//! ## Типы сервисов
267//!
268//! CloudPub поддерживает несколько протоколов сервисов:
269//!
270//! ### HTTP/HTTPS сервисы
271//!
272//! ```no_run
273//! # async fn http_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
274//! use cloudpub_sdk::protocol::{Protocol, Auth, Endpoint};
275//!
276//! // Публикация HTTP сервиса с базовой аутентификацией
277//! let http_endpoint = conn.publish(
278//!     Protocol::Http,
279//!     "localhost:8080".to_string(),
280//!     Some("API Server".to_string()),
281//!     Some(Auth::Basic),  // Требовать пароль для доступа
282//!     None, // acl
283//!     None, // headers
284//!     None, // rules
285//! ).await?;
286//!
287//! // Публикация HTTPS сервиса
288//! let https_endpoint = conn.publish(
289//!     Protocol::Https,
290//!     "localhost:8443".to_string(),
291//!     Some("Secure API".to_string()),
292//!     Some(Auth::Basic),  // Требовать токен для доступа
293//!     None, // acl
294//!     None, // headers
295//!     None, // rules
296//! ).await?;
297//! # Ok(())
298//! # }
299//! ```
300//!
301//! ### TCP/UDP сервисы
302//!
303//! ```no_run
304//! # async fn tcp_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
305//! use cloudpub_sdk::protocol::{Protocol, Auth};
306//!
307//! // Публикация TCP сервиса (например, SSH)
308//! let tcp_endpoint = conn.publish(
309//!     Protocol::Tcp,
310//!     "localhost:22".to_string(),
311//!     Some("SSH Server".to_string()),
312//!     Some(Auth::None),
313//!     None, // acl
314//!     None, // headers
315//!     None, // rules
316//! ).await?;
317//!
318//! // Публикация UDP сервиса (например, DNS)
319//! let udp_endpoint = conn.publish(
320//!     Protocol::Udp,
321//!     "localhost:53".to_string(),
322//!     Some("DNS Server".to_string()),
323//!     Some(Auth::None),
324//!     None, // acl
325//!     None, // headers
326//!     None, // rules
327//! ).await?;
328//! # Ok(())
329//! # }
330//! ```
331//!
332//! ### WebSocket сервисы
333//!
334//! ```no_run
335//! # async fn ws_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
336//! use cloudpub_sdk::protocol::{Protocol, Auth};
337//!
338//! // Публикация TCP сервиса
339//! let tcp_endpoint = conn.publish(
340//!     Protocol::Tcp,
341//!     "localhost:8080".to_string(),
342//!     Some("TCP Server".to_string()),
343//!     Some(Auth::None),
344//!     None, // acl
345//!     None, // headers
346//!     None, // rules
347//! ).await?;
348//!
349//! // Публикация UDP сервиса
350//! let udp_endpoint = conn.publish(
351//!     Protocol::Udp,
352//!     "localhost:8443".to_string(),
353//!     Some("UDP Server".to_string()),
354//!     Some(Auth::Basic),
355//!     None, // acl
356//!     None, // headers
357//!     None, // rules
358//! ).await?;
359//! # Ok(())
360//! # }
361//! ```
362//!
363//! ### RTSP стриминговые сервисы
364//!
365//! ```no_run
366//! # async fn rtsp_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
367//! use cloudpub_sdk::protocol::{Protocol, Auth};
368//!
369//! // Публикация RTSP потока с учетными данными в URL
370//! // Формат: rtsp://username:password@host:port/path
371//! let rtsp_endpoint = conn.publish(
372//!     Protocol::Rtsp,
373//!     "rtsp://camera:secret123@192.168.1.100:554/live/stream1".to_string(),
374//!     Some("Security Camera".to_string()),
375//!     Some(Auth::Basic),  // Аутентификация доступа в CloudPub
376//!     None, // acl
377//!     None, // headers
378//!     None, // rules
379//! ).await?;
380//!
381//! // RTSP без учетных данных (публичный поток)
382//! let public_rtsp = conn.publish(
383//!     Protocol::Rtsp,
384//!     "rtsp://localhost:554/public".to_string(),
385//!     Some("Public Stream".to_string()),
386//!     Some(Auth::None),
387//!     None, // acl
388//!     None, // headers
389//!     None, // rules
390//! ).await?;
391//! # Ok(())
392//! # }
393//! ```
394//!
395//! ## Управление сервисами
396//!
397//! ### Список сервисов
398//!
399//! ```no_run
400//! # async fn list_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
401//! use cloudpub_sdk::protocol::Endpoint;
402//! // Получить все зарегистрированные сервисы
403//! let services = conn.ls().await?;
404//!
405//! for service in services {
406//!     let name = service.client.as_ref()
407//!         .and_then(|c| c.description.clone())
408//!         .unwrap_or_else(|| "Без имени".to_string());
409//!     println!("Service: {}", name);
410//!     println!("  GUID: {}", service.guid);
411//!     println!("  URL: {}", service.as_url());
412//!     println!("  Status: {}", service.status.unwrap_or_else(|| "Unknown".to_string()));
413//!     println!("  Protocol: {:?}", service.remote_proto);
414//! }
415//! # Ok(())
416//! # }
417//! ```
418//!
419//! ### Запуск и остановка сервисов
420//!
421//! ```no_run
422//! # async fn control_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
423//! // Временно остановить сервис
424//! conn.stop("service-guid-123".to_string()).await?;
425//!
426//! // Перезапустить сервис
427//! conn.start("service-guid-123".to_string()).await?;
428//!
429//! // Постоянно удалить сервис
430//! conn.unpublish("service-guid-123".to_string()).await?;
431//!
432//! // Удалить все сервисы
433//! conn.clean().await?;
434//! # Ok(())
435//! # }
436//! ```
437//!
438//! ## Управление конфигурацией
439//!
440//! ### Чтение и запись опций
441//!
442//! ```no_run
443//! # fn config_example(conn: &cloudpub_sdk::Connection) -> anyhow::Result<()> {
444//! // Установка значений конфигурации
445//! conn.set("server", "api.cloudpub.com")?;
446//! conn.set("port", "443")?;
447//! conn.set("ssl", "true")?;
448//!
449//! // Получение значений конфигурации
450//! let server = conn.get("server")?;
451//! println!("Server: {}", server);
452//!
453//! // Получение всех опций конфигурации
454//! let options = conn.options();
455//! for (key, value) in options {
456//!     println!("{}: {}", key, value);
457//! }
458//! # Ok(())
459//! # }
460//! ```
461//!
462//! ### Управление аутентификацией
463//!
464//! ```no_run
465//! # async fn auth_example() -> anyhow::Result<()> {
466//! use cloudpub_sdk::Connection;
467//!
468//! // Аутентификация с учетными данными
469//! let mut conn = Connection::builder()
470//!     .credentials("user@example.com", "password")
471//!     .build()
472//!     .await?;
473//!
474//! // Позже, выход для очистки токена
475//! conn.logout()?;
476//!
477//! // Повторная аутентификация с сохраненным токеном
478//! let conn = Connection::builder()
479//!     .token("saved-auth-token")
480//!     .build()
481//!     .await?;
482//! # Ok(())
483//! # }
484//! ```
485//!
486//! ## Вспомогательные функции
487//!
488//! ### Проверка состояния сервера
489//!
490//! ```no_run
491//! # async fn ping_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
492//! // Измерение задержки сервера (возвращает микросекунды)
493//! let latency_us = conn.ping().await?;
494//! let latency_ms = latency_us as f64 / 1000.0;
495//! println!("Server latency: {}μs ({:.2}ms)", latency_us, latency_ms);
496//!
497//! if latency_ms > 100.0 {
498//!     println!("Warning: High latency detected!");
499//! }
500//! # Ok(())
501//! # }
502//! ```
503//!
504//! ### Управление кэшем
505//!
506//! ```no_run
507//! # fn cache_example(conn: &cloudpub_sdk::Connection) -> anyhow::Result<()> {
508//! // Очистка локального кэша
509//! conn.purge()?;
510//! println!("Cache cleared");
511//! # Ok(())
512//! # }
513//! ```
514//!
515//! ## Обработка ошибок
516//!
517//! Все методы SDK возвращают `Result<T>` для правильной обработки ошибок:
518//!
519//! ```no_run
520//! # async fn error_example() -> anyhow::Result<()> {
521//! use cloudpub_sdk::Connection;
522//!
523//! match Connection::builder()
524//!     .credentials("user@example.com", "wrong-password")
525//!     .build()
526//!     .await
527//! {
528//!     Ok(conn) => {
529//!         println!("Connected successfully");
530//!     },
531//!     Err(e) => {
532//!         eprintln!("Connection failed: {}", e);
533//!         // Обработка специфичных типов ошибок
534//!         if e.to_string().contains("authentication") {
535//!             eprintln!("Authentication failed. Please check your credentials.");
536//!         }
537//!     }
538//! }
539//! # Ok(())
540//! # }
541//! ```
542//!
543//! ## Полный пример
544//!
545//! Вот полный пример, демонстрирующий полный жизненный цикл с расширенными возможностями:
546//!
547//! ```no_run
548//! use cloudpub_sdk::Connection;
549//! use cloudpub_sdk::protocol::{
550//!     Protocol, Auth, Endpoint, Acl, Header, FilterRule,
551//!     Role, FilterAction
552//! };
553//! use std::time::Duration;
554//! use tokio::time::sleep;
555//!
556//! #[tokio::main]
557//! async fn main() -> anyhow::Result<()> {
558//!     // Инициализация соединения с конфигурацией
559//!     println!("Connecting to CloudPub...");
560//!     let mut conn = Connection::builder()
561//!         .credentials("admin@example.com", "secure-password")
562//!         .log_level("info")
563//!         .verbose(true)
564//!         .timeout_secs(30)
565//!         .build()
566//!         .await?;
567//!
568//!     println!("Connected successfully!");
569//!
570//!     // Публикация нескольких сервисов
571//!     println!("\nPublishing services...");
572//!
573//!     // Простой веб-сервис без расширенных возможностей
574//!     let web_service = conn.publish(
575//!         Protocol::Http,
576//!         "localhost:3000".to_string(),
577//!         Some("Web Application".to_string()),
578//!         Some(Auth::None),
579//!         None, // Нет ACL ограничений
580//!         None, // Нет пользовательских заголовков
581//!         None, // Нет правил фильтрации
582//!     ).await?;
583//!     println!("✓ Web app: {}", web_service.as_url());
584//!
585//!     // API сервер с ACL и пользовательскими заголовками
586//!     let api_acl = vec![
587//!         Acl { user: "api_admin@example.com".to_string(), role: Role::Admin as i32 },
588//!         Acl { user: "api_user@example.com".to_string(), role: Role::Reader as i32 },
589//!     ];
590//!     let api_headers = vec![
591//!         Header { name: "X-API-Version".to_string(), value: "2.0".to_string() },
592//!         Header { name: "Access-Control-Allow-Origin".to_string(), value: "*".to_string() },
593//!     ];
594//!     let api_service = conn.publish(
595//!         Protocol::Https,
596//!         "localhost:8443".to_string(),
597//!         Some("API Server".to_string()),
598//!         Some(Auth::Basic),
599//!         Some(api_acl),
600//!         Some(api_headers),
601//!         None, // Нет правил фильтрации
602//!     ).await?;
603//!     println!("✓ API server: {}", api_service.as_url());
604//!
605//!     // SSH сервис с правилами фильтрации для безопасности
606//!     let ssh_rules = vec![
607//!         FilterRule {
608//!             order: 0,
609//!             action_type: FilterAction::FilterAllow as i32,
610//!             action_value: None,
611//!             data: "ip.src == 192.168.1.10 or ip.src == 10.0.0.5".to_string(),  // Разрешить только определенные IP
612//!         },
613//!         FilterRule {
614//!             order: 1,
615//!             action_type: FilterAction::FilterDeny as i32,
616//!             action_value: None,
617//!             data: "ip.src matches \".*\"".to_string(),  // Заблокировать все остальные IP
618//!         },
619//!     ];
620//!     let ssh_service = conn.publish(
621//!         Protocol::Tcp,
622//!         "localhost:22".to_string(),
623//!         Some("SSH Access".to_string()),
624//!         Some(Auth::Basic),
625//!         None, // Нет ACL
626//!         None, // Нет заголовков (TCP не использует HTTP заголовки)
627//!         Some(ssh_rules),
628//!     ).await?;
629//!     println!("✓ SSH: {}", ssh_service.as_url());
630//!
631//!     // Проверка состояния сервера
632//!     println!("\nChecking server health...");
633//!     let latency_us = conn.ping().await?;
634//!     println!("Server latency: {}μs ({:.2}ms)", latency_us, latency_us as f64 / 1000.0);
635//!
636//!     // Список всех сервисов
637//!     println!("\nActive services:");
638//!     let services = conn.ls().await?;
639//!     for (i, service) in services.iter().enumerate() {
640//!         let name = service.client.as_ref()
641//!             .and_then(|c| c.description.clone())
642//!             .unwrap_or_else(|| "Без имени".to_string());
643//!         println!("{}. {} - {}",
644//!             i + 1,
645//!             name,
646//!             service.as_url()
647//!         );
648//!     }
649//!
650//!     // Работать некоторое время
651//!     println!("\nServices are running. Press Ctrl+C to stop...");
652//!     tokio::select! {
653//!         _ = tokio::signal::ctrl_c() => {
654//!             println!("\nShutting down...");
655//!         }
656//!         _ = sleep(Duration::from_secs(3600)) => {
657//!             println!("\nTimeout reached");
658//!         }
659//!     }
660//!
661//!     // Очистка
662//!     println!("Очистка сервисов...");
663//!     for service in services {
664//!         conn.unpublish(service.guid).await?;
665//!         let name = service.client.as_ref()
666//!             .and_then(|c| c.description.clone())
667//!             .unwrap_or_else(|| "Без имени".to_string());
668//!         println!("✓ Removed: {}", name);
669//!     }
670//!
671//!     println!("All services stopped. Goodbye!");
672//!     Ok(())
673//! }
674//! ```
675//! ## Потокобезопасность
676//!
677//! Структура `Connection` использует внутреннюю синхронизацию и может безопасно использоваться
678//! между потоками через `Arc<Mutex<Connection>>` при необходимости.
679//!
680//! ## Лицензия
681//!
682//! Этот SDK лицензирован под лицензией Apache 2.0.
683
684pub use cloudpub_client::config::ClientOpts;
685pub use cloudpub_common::protocol;
686
687mod builder;
688mod connection;
689
690pub use builder::ConnectionBuilder;
691pub use connection::{CheckSignalFn, Connection, ConnectionEvent};