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