Skip to main content

videosdk/resources/
mod.rs

1//! The resource APIs, reached through accessors on [`Client`].
2
3pub mod agents;
4pub mod alert_rules;
5pub mod batch_calls;
6pub mod connectors;
7pub mod egress;
8pub mod hls;
9pub mod ingress;
10pub mod participants;
11pub mod recordings;
12pub mod resource_pool;
13pub mod rooms;
14pub mod rtmp;
15pub mod sessions;
16pub mod sip;
17pub mod transcodings;
18pub mod transcription;
19
20mod session_utils;
21
22#[cfg(test)]
23mod egress_tests;
24#[cfg(test)]
25mod phase5_tests;
26#[cfg(test)]
27mod resource_tests;
28#[cfg(test)]
29mod sip_tests;
30
31use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
32
33use crate::client::Client;
34
35/// Everything outside RFC 3986's *unreserved* set is escaped. Room ids look
36/// like `abcd-efgh-ijkl`, and custom room ids are caller-supplied, so the
37/// hyphen in particular must survive.
38const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC
39    .remove(b'-')
40    .remove(b'.')
41    .remove(b'_')
42    .remove(b'~');
43
44/// Percent-encodes a value for use as a single URL path segment.
45pub(crate) fn escape(segment: &str) -> String {
46    utf8_percent_encode(segment, PATH_SEGMENT).to_string()
47}
48
49/// A random v4 UUID, used for generated participant and secret names.
50pub(crate) fn random_uuid() -> String {
51    uuid::Uuid::new_v4().to_string()
52}
53
54/// Lowercases `name` into the secret-name charset (`^[a-z0-9_-]+$`), collapsing
55/// runs of other characters into a single dash.
56pub(crate) fn slugify_name(name: &str) -> String {
57    let mut slug = String::with_capacity(name.len());
58    let mut previous_was_dash = false;
59
60    for character in name.chars().flat_map(char::to_lowercase) {
61        if character.is_ascii_alphanumeric() || character == '_' || character == '-' {
62            slug.push(character);
63            previous_was_dash = false;
64        } else if !previous_was_dash {
65            slug.push('-');
66            previous_was_dash = true;
67        }
68    }
69
70    let slug = slug.trim_matches('-');
71    let slug: String = slug.chars().take(40).collect();
72    if slug.is_empty() {
73        "agent".to_string()
74    } else {
75        slug
76    }
77}
78
79impl Client {
80    /// The rooms (meetings) API.
81    pub fn rooms(&self) -> rooms::RoomsResource<'_> {
82        rooms::RoomsResource::new(self)
83    }
84
85    /// The sessions API, covering live and past sessions and their participants.
86    pub fn sessions(&self) -> sessions::SessionsResource<'_> {
87        sessions::SessionsResource::new(self)
88    }
89
90    /// A room-keyed view over a room's live participants.
91    pub fn participants(&self) -> participants::ParticipantsResource<'_> {
92        participants::ParticipantsResource::new(self)
93    }
94
95    /// The recordings API: room, participant, track, composite and merge.
96    pub fn recordings(&self) -> recordings::RecordingsResource<'_> {
97        recordings::RecordingsResource::new(self)
98    }
99
100    /// The HLS API.
101    pub fn hls(&self) -> hls::HlsResource<'_> {
102        hls::HlsResource::new(self)
103    }
104
105    /// The RTMP-out (livestream) API.
106    pub fn rtmp(&self) -> rtmp::RtmpResource<'_> {
107        rtmp::RtmpResource::new(self)
108    }
109
110    /// The SIP / telephony API.
111    pub fn sip(&self) -> sip::SipResource<'_> {
112        sip::SipResource::new(self)
113    }
114
115    /// The AI agents API: dispatch agents and manage cloud deployments.
116    pub fn agents(&self) -> agents::AgentsResource<'_> {
117        agents::AgentsResource::new(self)
118    }
119
120    /// The alert-rules API: metric-threshold alerts over your telemetry.
121    pub fn alert_rules(&self) -> alert_rules::AlertRulesResource<'_> {
122        alert_rules::AlertRulesResource::new(self)
123    }
124
125    /// The batch-calls API: bulk outbound phone campaigns.
126    pub fn batch_call(&self) -> batch_calls::BatchCallsResource<'_> {
127        batch_calls::BatchCallsResource::new(self)
128    }
129
130    /// The telephony connectors API.
131    pub fn connectors(&self) -> connectors::ConnectorsResource<'_> {
132        connectors::ConnectorsResource::new(self)
133    }
134
135    /// The resource-pool API: pre-acquire composition units. Enterprise tier.
136    pub fn resource_pool(&self) -> resource_pool::ResourcePoolResource<'_> {
137        resource_pool::ResourcePoolResource::new(self)
138    }
139
140    /// The transcoding API: post-process recordings and HLS into files.
141    pub fn transcodings(&self) -> transcodings::TranscodingsResource<'_> {
142        transcodings::TranscodingsResource::new(self)
143    }
144
145    /// The transcription API.
146    pub fn transcription(&self) -> transcription::TranscriptionResource<'_> {
147        transcription::TranscriptionResource::new(self)
148    }
149
150    /// The WHIP ingress builder: WebRTC-HTTP publish into a room.
151    pub fn whip(&self) -> ingress::WhipResource<'_> {
152        ingress::WhipResource::new(self)
153    }
154
155    /// The WHEP egress builder: standards-based WebRTC pull from a room.
156    pub fn whep(&self) -> ingress::WhepResource<'_> {
157        ingress::WhepResource::new(self)
158    }
159
160    /// Streams media and data frames into a room over a single-use WebSocket.
161    pub fn socket_ingress(&self) -> ingress::SocketIngressResource<'_> {
162        ingress::SocketIngressResource::new(self)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn escapes_path_segments_but_keeps_unreserved_characters() {
172        assert_eq!(escape("abcd-efgh-ijkl"), "abcd-efgh-ijkl");
173        assert_eq!(escape("a_b.c~d"), "a_b.c~d");
174        assert_eq!(escape("a/b"), "a%2Fb");
175        assert_eq!(escape("a b"), "a%20b");
176        assert_eq!(escape("../etc"), "..%2Fetc");
177        assert_eq!(escape("100%"), "100%25");
178    }
179
180    #[test]
181    fn slugify_constrains_names_to_the_secret_charset() {
182        assert_eq!(slugify_name("My Agent"), "my-agent");
183        assert_eq!(
184            slugify_name("keep_dashes-and_underscores"),
185            "keep_dashes-and_underscores"
186        );
187        assert_eq!(slugify_name("a!!!b"), "a-b", "runs collapse to one dash");
188        assert_eq!(slugify_name("--trim--"), "trim");
189        assert_eq!(slugify_name("!!!"), "agent", "an empty slug falls back");
190        assert_eq!(
191            slugify_name(&"x".repeat(60)).len(),
192            40,
193            "capped at 40 chars"
194        );
195    }
196
197    #[test]
198    fn random_uuids_are_distinct_and_well_formed() {
199        let uuid = random_uuid();
200        assert_eq!(uuid.len(), 36);
201        assert_ne!(uuid, random_uuid());
202    }
203}