videosdk-server-sdk 0.1.0

Rust server SDK for the VideoSDK v2 REST APIs
Documentation
//! Helpers shared by the session-keyed and room-keyed participant views.

use reqwest::Method;
use serde_json::{Map, Value};

use crate::client::{CallOptions, Client};
use crate::error::Result;
use crate::query::QueryBuilder;

/// Resolves a room's current (ongoing) session id, or `None` if none is live.
///
/// Shared by the room-keyed resources: participants, and WHIP/WHEP teardown.
pub(crate) async fn resolve_active_session_id(
    client: &Client,
    room_id: &str,
) -> Result<Option<String>> {
    let query = QueryBuilder::new()
        .str_val("roomId", room_id)
        .str_val("status", "ongoing")
        .set("perPage", 1)
        .into_pairs();

    let raw: Value = client
        .json(Method::GET, "/v2/sessions", CallOptions::new().query(query))
        .await?;

    Ok(raw
        .get("data")
        .and_then(Value::as_array)
        .and_then(|sessions| sessions.first())
        .and_then(|session| session.get("id"))
        .and_then(Value::as_str)
        .map(str::to_string))
}

/// Reshapes the participants response into the envelope the paginator expects.
///
/// The API answers `/v2/sessions/{id}/participants` with `{pageInfo, data}`
/// where `data` is a *session* (or a one-element array of one), and the
/// participants hang off it. Lift them to the top level as `data`.
pub(crate) fn reshape_session_participants(raw: Value) -> Value {
    let mut out = Map::new();
    if let Some(page_info) = raw.get("pageInfo") {
        out.insert("pageInfo".to_string(), page_info.clone());
    }

    let session = match raw.get("data") {
        Some(Value::Array(sessions)) => sessions.first(),
        other => other,
    };
    let participants = session
        .and_then(|session| session.get("participants"))
        .filter(|participants| participants.is_array())
        .cloned()
        .unwrap_or_else(|| Value::Array(Vec::new()));

    out.insert("data".to_string(), participants);
    Value::Object(out)
}

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

    #[test]
    fn lifts_participants_out_of_a_session_object() {
        let raw = json!({
            "pageInfo": {"currentPage": 1, "lastPage": 2},
            "data": {"id": "s-1", "participants": [{"participantId": "p-1"}]},
        });
        assert_eq!(
            reshape_session_participants(raw),
            json!({
                "pageInfo": {"currentPage": 1, "lastPage": 2},
                "data": [{"participantId": "p-1"}],
            })
        );
    }

    #[test]
    fn lifts_participants_out_of_a_single_element_array() {
        let raw = json!({"data": [{"id": "s-1", "participants": [{"participantId": "p-1"}]}]});
        assert_eq!(
            reshape_session_participants(raw),
            json!({"data": [{"participantId": "p-1"}]})
        );
    }

    #[test]
    fn yields_an_empty_list_when_there_is_nothing_to_lift() {
        for raw in [
            json!({}),
            json!({"data": []}),
            json!({"data": null}),
            json!({"data": {"id": "s-1"}}),
            json!({"data": {"id": "s-1", "participants": null}}),
        ] {
            assert_eq!(
                reshape_session_participants(raw.clone()),
                json!({"data": []}),
                "for {raw}"
            );
        }
    }

    #[test]
    fn page_info_is_preserved_when_present_and_omitted_otherwise() {
        let with = reshape_session_participants(json!({"pageInfo": {"total": 3}, "data": []}));
        assert_eq!(with["pageInfo"], json!({"total": 3}));
        assert!(reshape_session_participants(json!({"data": []}))
            .get("pageInfo")
            .is_none());
    }
}