heos 0.3.1

Rust bindings for HEOS ecosystem API
Documentation
//! Data types representing HEOS groups.
//!
//! A HEOS group comprises two or more [players](player), with one player being designated as the
//! "group leader". Controlling a group is done via controlling the group leader, and all other
//! players in the group will synchronize with said leader.

use serde::{Deserialize, Serialize};
use std::str::FromStr;
use qstring::QString;
use strum::EnumString;
use crate::command::CommandError;
use super::*;
use crate::data::player::PlayerId;

id_type! {
    /// ID representing a HEOS group.
    ///
    /// This ID can be any number, and is generated by the HEOS system, making it effectively
    /// opaque.
    pub struct GroupId(pub i64);
}

/// The roles a player can have while participating in a group.
#[derive(Serialize, Deserialize, EnumString, strum::Display, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(into = "String", try_from = "String")]
#[strum(serialize_all = "lowercase")]
pub enum GroupRole {
    /// This player is the group leader.
    ///
    /// Only one player may be the group leader.
    Leader,
    /// This player is a group member.
    ///
    /// All players except for the leader are members.
    Member,
}
impl_enum_string_conversions!(GroupRole);

/// Information about a player that is participating in a group.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct GroupPlayer {
    /// The user-friendly name of the player.
    pub name: String,
    /// ID of the player.
    #[serde(rename = "pid")]
    pub player_id: PlayerId,
    /// The role the player has in the group.
    pub role: GroupRole,
}

/// Information about a specific group.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GroupInfo {
    /// The user-friendly name of the group.
    pub name: String,
    /// ID of the group.
    #[serde(rename = "gid")]
    pub group_id: GroupId,
    /// List of all players in the group.
    pub players: Vec<GroupPlayer>,
}
impl_try_from_response_payload!(GroupInfo);
impl_try_from_response_payload!(Vec<GroupInfo>);

impl GroupInfo {
    /// Get the group's leader.
    ///
    /// # Panics
    ///
    /// Panics if no group member is designated as the leader, which should _not_ occur.
    pub fn leader(&self) -> &GroupPlayer {
        for player in &self.players {
            if player.role == GroupRole::Leader {
                return player
            }
        }
        panic!("every group should have a leader")
    }

    /// Retrieve a player from the group by ID.
    ///
    /// If no player exists in the group with the specified ID, yields `None`.
    pub fn player(&self, player_id: PlayerId) -> Option<&GroupPlayer> {
        for player in &self.players {
            if player.player_id == player_id {
                return Some(player)
            }
        }
        None
    }
}

/// Result of using the [SetGroup](crate::command::group::SetGroup) command.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum SetGroupResult {
    /// The group was deleted.
    Deleted,
    /// The group was either created or modified.
    ///
    /// These are effectively treated as the same.
    CreatedOrModified {
        /// The ID of the group.
        #[serde(rename = "gid")]
        group_id: GroupId,
        /// The name of the group.
        name: String,
    }
}
impl_try_from_response_qs!(SetGroupResult);

impl TryFrom<QString> for SetGroupResult {
    type Error = CommandError;

    fn try_from(qs: QString) -> Result<Self, Self::Error> {
        let group_id = match qs.get("gid") {
            Some(gid_str) => {
                gid_str.parse::<GroupId>()
                    .map_err(|err| CommandError::MalformedResponse(format!("could not parse 'gid': {err:?}")))?
            },
            None => return Ok(Self::Deleted),
        };
        let name = match qs.get("name") {
            Some(name) => name.to_string(),
            None => return Ok(Self::Deleted),
        };
        Ok(Self::CreatedOrModified {
            group_id,
            name,
        })
    }
}