videosdk-server-sdk 0.1.0

Rust server SDK for the VideoSDK v2 REST APIs
Documentation
//! The resource APIs, reached through accessors on [`Client`].

pub mod agents;
pub mod alert_rules;
pub mod batch_calls;
pub mod connectors;
pub mod egress;
pub mod hls;
pub mod ingress;
pub mod participants;
pub mod recordings;
pub mod resource_pool;
pub mod rooms;
pub mod rtmp;
pub mod sessions;
pub mod sip;
pub mod transcodings;
pub mod transcription;

mod session_utils;

#[cfg(test)]
mod egress_tests;
#[cfg(test)]
mod phase5_tests;
#[cfg(test)]
mod resource_tests;
#[cfg(test)]
mod sip_tests;

use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};

use crate::client::Client;

/// Everything outside RFC 3986's *unreserved* set is escaped. Room ids look
/// like `abcd-efgh-ijkl`, and custom room ids are caller-supplied, so the
/// hyphen in particular must survive.
const PATH_SEGMENT: &AsciiSet = &NON_ALPHANUMERIC
    .remove(b'-')
    .remove(b'.')
    .remove(b'_')
    .remove(b'~');

/// Percent-encodes a value for use as a single URL path segment.
pub(crate) fn escape(segment: &str) -> String {
    utf8_percent_encode(segment, PATH_SEGMENT).to_string()
}

/// A random v4 UUID, used for generated participant and secret names.
pub(crate) fn random_uuid() -> String {
    uuid::Uuid::new_v4().to_string()
}

/// Lowercases `name` into the secret-name charset (`^[a-z0-9_-]+$`), collapsing
/// runs of other characters into a single dash.
pub(crate) fn slugify_name(name: &str) -> String {
    let mut slug = String::with_capacity(name.len());
    let mut previous_was_dash = false;

    for character in name.chars().flat_map(char::to_lowercase) {
        if character.is_ascii_alphanumeric() || character == '_' || character == '-' {
            slug.push(character);
            previous_was_dash = false;
        } else if !previous_was_dash {
            slug.push('-');
            previous_was_dash = true;
        }
    }

    let slug = slug.trim_matches('-');
    let slug: String = slug.chars().take(40).collect();
    if slug.is_empty() {
        "agent".to_string()
    } else {
        slug
    }
}

impl Client {
    /// The rooms (meetings) API.
    pub fn rooms(&self) -> rooms::RoomsResource<'_> {
        rooms::RoomsResource::new(self)
    }

    /// The sessions API, covering live and past sessions and their participants.
    pub fn sessions(&self) -> sessions::SessionsResource<'_> {
        sessions::SessionsResource::new(self)
    }

    /// A room-keyed view over a room's live participants.
    pub fn participants(&self) -> participants::ParticipantsResource<'_> {
        participants::ParticipantsResource::new(self)
    }

    /// The recordings API: room, participant, track, composite and merge.
    pub fn recordings(&self) -> recordings::RecordingsResource<'_> {
        recordings::RecordingsResource::new(self)
    }

    /// The HLS API.
    pub fn hls(&self) -> hls::HlsResource<'_> {
        hls::HlsResource::new(self)
    }

    /// The RTMP-out (livestream) API.
    pub fn rtmp(&self) -> rtmp::RtmpResource<'_> {
        rtmp::RtmpResource::new(self)
    }

    /// The SIP / telephony API.
    pub fn sip(&self) -> sip::SipResource<'_> {
        sip::SipResource::new(self)
    }

    /// The AI agents API: dispatch agents and manage cloud deployments.
    pub fn agents(&self) -> agents::AgentsResource<'_> {
        agents::AgentsResource::new(self)
    }

    /// The alert-rules API: metric-threshold alerts over your telemetry.
    pub fn alert_rules(&self) -> alert_rules::AlertRulesResource<'_> {
        alert_rules::AlertRulesResource::new(self)
    }

    /// The batch-calls API: bulk outbound phone campaigns.
    pub fn batch_call(&self) -> batch_calls::BatchCallsResource<'_> {
        batch_calls::BatchCallsResource::new(self)
    }

    /// The telephony connectors API.
    pub fn connectors(&self) -> connectors::ConnectorsResource<'_> {
        connectors::ConnectorsResource::new(self)
    }

    /// The resource-pool API: pre-acquire composition units. Enterprise tier.
    pub fn resource_pool(&self) -> resource_pool::ResourcePoolResource<'_> {
        resource_pool::ResourcePoolResource::new(self)
    }

    /// The transcoding API: post-process recordings and HLS into files.
    pub fn transcodings(&self) -> transcodings::TranscodingsResource<'_> {
        transcodings::TranscodingsResource::new(self)
    }

    /// The transcription API.
    pub fn transcription(&self) -> transcription::TranscriptionResource<'_> {
        transcription::TranscriptionResource::new(self)
    }

    /// The WHIP ingress builder: WebRTC-HTTP publish into a room.
    pub fn whip(&self) -> ingress::WhipResource<'_> {
        ingress::WhipResource::new(self)
    }

    /// The WHEP egress builder: standards-based WebRTC pull from a room.
    pub fn whep(&self) -> ingress::WhepResource<'_> {
        ingress::WhepResource::new(self)
    }

    /// Streams media and data frames into a room over a single-use WebSocket.
    pub fn socket_ingress(&self) -> ingress::SocketIngressResource<'_> {
        ingress::SocketIngressResource::new(self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn escapes_path_segments_but_keeps_unreserved_characters() {
        assert_eq!(escape("abcd-efgh-ijkl"), "abcd-efgh-ijkl");
        assert_eq!(escape("a_b.c~d"), "a_b.c~d");
        assert_eq!(escape("a/b"), "a%2Fb");
        assert_eq!(escape("a b"), "a%20b");
        assert_eq!(escape("../etc"), "..%2Fetc");
        assert_eq!(escape("100%"), "100%25");
    }

    #[test]
    fn slugify_constrains_names_to_the_secret_charset() {
        assert_eq!(slugify_name("My Agent"), "my-agent");
        assert_eq!(
            slugify_name("keep_dashes-and_underscores"),
            "keep_dashes-and_underscores"
        );
        assert_eq!(slugify_name("a!!!b"), "a-b", "runs collapse to one dash");
        assert_eq!(slugify_name("--trim--"), "trim");
        assert_eq!(slugify_name("!!!"), "agent", "an empty slug falls back");
        assert_eq!(
            slugify_name(&"x".repeat(60)).len(),
            40,
            "capped at 40 chars"
        );
    }

    #[test]
    fn random_uuids_are_distinct_and_well_formed() {
        let uuid = random_uuid();
        assert_eq!(uuid.len(), 36);
        assert_ne!(uuid, random_uuid());
    }
}