videosdk-server-sdk 0.1.0

Rust server SDK for the VideoSDK v2 REST APIs
Documentation
//! Internal query-string builder.
//!
//! Mirrors the wire behavior of the TypeScript SDK: `None` and empty strings are
//! omitted entirely, and list values are comma-joined rather than repeated.

use std::fmt::Display;

/// Accumulates query-string parameters, omitting absent and empty values.
#[derive(Debug, Clone, Default)]
pub(crate) struct QueryBuilder {
    pairs: Vec<(String, String)>,
}

impl QueryBuilder {
    pub(crate) fn new() -> Self {
        Self::default()
    }

    /// Sets a value unconditionally.
    pub(crate) fn set(mut self, key: &str, value: impl Display) -> Self {
        self.pairs.push((key.to_string(), value.to_string()));
        self
    }

    /// Sets a value if present. Zero and `false` are sent; only `None` is omitted.
    pub(crate) fn opt(self, key: &str, value: Option<impl Display>) -> Self {
        match value {
            Some(value) => self.set(key, value),
            None => self,
        }
    }

    /// Sets a string value if present and non-empty.
    pub(crate) fn opt_str(self, key: &str, value: Option<impl AsRef<str>>) -> Self {
        match value {
            Some(value) if !value.as_ref().is_empty() => self.set(key, value.as_ref()),
            _ => self,
        }
    }

    /// Sets a string value if non-empty.
    pub(crate) fn str_val(self, key: &str, value: impl AsRef<str>) -> Self {
        self.opt_str(key, Some(value))
    }

    /// Sets a comma-joined list value, omitting an empty list. This is the
    /// format the API expects — not repeated `key=a&key=b` pairs.
    pub(crate) fn csv(self, key: &str, values: &[impl AsRef<str>]) -> Self {
        if values.is_empty() {
            return self;
        }
        let joined = values
            .iter()
            .map(AsRef::as_ref)
            .collect::<Vec<_>>()
            .join(",");
        self.set(key, joined)
    }

    pub(crate) fn into_pairs(self) -> Vec<(String, String)> {
        self.pairs
    }
}

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

    #[test]
    fn omits_none_and_empty_strings() {
        let pairs = QueryBuilder::new()
            .opt_str("a", None::<&str>)
            .opt_str("b", Some(""))
            .opt_str("c", Some("kept"))
            .str_val("d", "")
            .into_pairs();
        assert_eq!(pairs, [("c".to_string(), "kept".to_string())]);
    }

    #[test]
    fn keeps_zero_and_false_but_omits_none() {
        let pairs = QueryBuilder::new()
            .opt("page", Some(0))
            .opt("enabled", Some(false))
            .opt("skipped", None::<i32>)
            .into_pairs();
        assert_eq!(
            pairs,
            [
                ("page".to_string(), "0".to_string()),
                ("enabled".to_string(), "false".to_string()),
            ]
        );
    }

    #[test]
    fn comma_joins_lists_and_omits_empty_ones() {
        let pairs = QueryBuilder::new()
            .csv("kinds", &["audio", "video"])
            .csv("empty", &[] as &[&str])
            .into_pairs();
        assert_eq!(pairs, [("kinds".to_string(), "audio,video".to_string())]);
    }
}