bpi-rs 0.2.0

Bilibili API client library for Rust
Documentation
use crate::ids::{Aid, SeasonId};
use crate::{BpiError, BpiResult};

/// Sort field for `/x2/creative/web/seasons`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeasonListOrder {
    CreatedAt,
    UpdatedAt,
}

impl SeasonListOrder {
    const fn as_str(self) -> &'static str {
        match self {
            Self::CreatedAt => "ctime",
            Self::UpdatedAt => "mtime",
        }
    }
}

/// Sort direction for `/x2/creative/web/seasons`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeasonListSort {
    Asc,
    Desc,
}

impl SeasonListSort {
    const fn as_str(self) -> &'static str {
        match self {
            Self::Asc => "asc",
            Self::Desc => "desc",
        }
    }
}

/// Parameters for `/x2/creative/web/seasons`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeasonListParams {
    page: u32,
    page_size: u32,
    order: Option<SeasonListOrder>,
    sort: Option<SeasonListSort>,
}

impl SeasonListParams {
    pub fn new(page: u32, page_size: u32) -> BpiResult<Self> {
        Ok(Self {
            page: validate_non_zero_u32("pn", page)?,
            page_size: validate_non_zero_u32("ps", page_size)?,
            order: None,
            sort: None,
        })
    }

    pub fn with_order(mut self, order: SeasonListOrder) -> Self {
        self.order = Some(order);
        self
    }

    pub fn with_sort(mut self, sort: SeasonListSort) -> Self {
        self.sort = Some(sort);
        self
    }

    pub(crate) fn query_pairs(&self) -> Vec<(&'static str, String)> {
        let mut query = vec![
            ("pn", self.page.to_string()),
            ("ps", self.page_size.to_string()),
        ];

        if let Some(order) = self.order {
            query.push(("order", order.as_str().to_string()));
        }

        if let Some(sort) = self.sort {
            query.push(("sort", sort.as_str().to_string()));
        }

        query
    }
}

/// Parameters for `/x2/creative/web/season/aid`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeasonByAidParams {
    aid: Aid,
}

impl SeasonByAidParams {
    pub fn new(aid: Aid) -> Self {
        Self { aid }
    }

    pub fn query_pairs(self) -> [(&'static str, String); 1] {
        [("id", self.aid.to_string())]
    }
}

/// Parameters for `/x2/creative/web/season`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeasonInfoParams {
    season_id: SeasonId,
}

impl SeasonInfoParams {
    pub fn new(season_id: SeasonId) -> Self {
        Self { season_id }
    }

    pub fn query_pairs(self) -> [(&'static str, String); 1] {
        [("id", self.season_id.to_string())]
    }
}

/// Parameters for `/x2/creative/web/season/section`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeasonSectionEpisodesParams {
    season_id: SeasonId,
}

impl SeasonSectionEpisodesParams {
    pub fn new(season_id: SeasonId) -> Self {
        Self { season_id }
    }

    pub fn query_pairs(self) -> [(&'static str, String); 1] {
        [("id", self.season_id.to_string())]
    }
}

fn validate_non_zero_u32(field: &'static str, value: u32) -> BpiResult<u32> {
    if value == 0 {
        return Err(BpiError::invalid_parameter(field, "value must be non-zero"));
    }

    Ok(value)
}

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

    #[test]
    fn season_list_params_serializes_required_query() -> Result<(), BpiError> {
        let params = SeasonListParams::new(1, 10)?;

        assert_eq!(
            params.query_pairs(),
            vec![("pn", "1".to_string()), ("ps", "10".to_string())]
        );
        Ok(())
    }

    #[test]
    fn season_list_params_serializes_sorting_query() -> Result<(), BpiError> {
        let params = SeasonListParams::new(1, 10)?
            .with_order(SeasonListOrder::CreatedAt)
            .with_sort(SeasonListSort::Desc);

        assert_eq!(
            params.query_pairs(),
            vec![
                ("pn", "1".to_string()),
                ("ps", "10".to_string()),
                ("order", "ctime".to_string()),
                ("sort", "desc".to_string()),
            ]
        );
        Ok(())
    }

    #[test]
    fn season_list_params_rejects_zero_page() {
        let err = SeasonListParams::new(0, 10).unwrap_err();

        assert!(matches!(
            err,
            BpiError::InvalidParameter { field: "pn", .. }
        ));
    }

    #[test]
    fn season_info_params_serializes_query() -> Result<(), BpiError> {
        let params = SeasonInfoParams::new(SeasonId::new(4294056)?);

        assert_eq!(params.query_pairs(), [("id", "4294056".to_string())]);
        Ok(())
    }

    #[test]
    fn season_section_episodes_params_serializes_query() -> Result<(), BpiError> {
        let params = SeasonSectionEpisodesParams::new(SeasonId::new(176088)?);

        assert_eq!(params.query_pairs(), [("id", "176088".to_string())]);
        Ok(())
    }
}