earthmc 2.0.6

Async client for interacting with the EarthMC API
Documentation
use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::model::{
    location::{ChunkCoordinate, WorldLocation},
    named_uuid::{NamedUuid, NamedUuidOpt},
    permission::TownyPermissions,
    timestamp::Timestamp,
};

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Town {
    pub name: String,
    pub uuid: Uuid,
    /// Shown on `/t` in-game.
    pub board: Option<String>,
    /// Shown on `/t` in-game.
    pub founder: String,
    pub wiki: Option<String>,
    pub mayor: NamedUuid,
    pub nation: NamedUuidOpt,
    pub timestamps: TownTimestamps,
    pub status: TownStatus,
    pub stats: TownStats,
    pub perms: TownyPermissions,
    pub coordinates: TownCoordinates,
    pub residents: Vec<NamedUuid>,
    pub trusted: Vec<NamedUuid>,
    pub outlaws: Vec<NamedUuid>,
    pub quarters: Vec<NamedUuid>,
    pub ranks: BTreeMap<TownRankKind, Vec<NamedUuid>>,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TownTimestamps {
    /// Creation time.
    #[cfg_attr(
        feature = "chrono",
        serde(with = "crate::model::timestamp::seconds")
    )]
    pub registered: Timestamp,
    /// Current nation join time.
    #[cfg_attr(
        feature = "chrono",
        serde(with = "crate::model::timestamp::optional_seconds")
    )]
    pub joined_nation_at: Option<Timestamp>,
    /// Ruin time.
    #[cfg_attr(
        feature = "chrono",
        serde(with = "crate::model::timestamp::optional_seconds")
    )]
    pub ruined_at: Option<Timestamp>,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TownStatus {
    pub is_public: bool,
    /// Invite-less joining is enabled.
    pub is_open: bool,
    pub is_neutral: bool,
    pub is_capital: bool,
    pub is_over_claimed: bool,
    pub is_ruined: bool,
    pub is_for_sale: bool,
    pub has_nation: bool,
    /// Outsiders may teleport to the town spawn.
    pub can_outsiders_spawn: bool,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TownStats {
    pub num_town_blocks: i32,
    pub max_town_blocks: i32,
    pub bonus_blocks: i32,
    pub num_residents: i32,
    pub num_trusted: i32,
    pub num_outlaws: i32,
    /// Shown on `/t` in-game.
    pub balance: f64,
    /// Sale price when the town is listed.
    pub for_sale_price: Option<f64>,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TownCoordinates {
    pub spawn: WorldLocation,
    pub home_block: ChunkCoordinate,
    pub town_blocks: Vec<ChunkCoordinate>,
}

#[derive(
    Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord, Debug,
)]
#[serde(rename_all = "PascalCase")]
pub enum TownRankKind {
    Councillor,
    Builder,
    Recruiter,
    Police,
    #[serde(rename = "Tax-exempt")]
    TaxExempt,
    Treasurer,
    Realtor,
    Settler,
    #[serde(untagged)]
    Other(String),
}

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

    use crate::{
        endpoints::towns::TownCoordinates,
        model::location::{ChunkCoordinate, WorldLocation},
    };

    fn spawn() -> WorldLocation {
        WorldLocation {
            world: "earth".to_string(),
            x: 1.0,
            y: 64.0,
            z: 2.0,
            pitch: 0.0,
            yaw: 90.0,
        }
    }

    #[test]
    fn deserializes_town_blocks_from_coordinate_arrays() {
        let coordinates: TownCoordinates = serde_json::from_value(json!({
            "spawn": {
                "world": "earth",
                "x": 1.0,
                "y": 64.0,
                "z": 2.0,
                "pitch": 0.0,
                "yaw": 90.0
            },
            "homeBlock": [10, 20],
            "townBlocks": [[10, 20], [11, 20]]
        }))
        .unwrap();

        assert_eq!(coordinates.home_block, ChunkCoordinate { x: 10, z: 20 });
        assert_eq!(
            coordinates.town_blocks,
            vec![
                ChunkCoordinate { x: 10, z: 20 },
                ChunkCoordinate { x: 11, z: 20 },
            ]
        );
    }

    #[test]
    fn serializes_town_blocks_as_coordinate_arrays() {
        let coordinates = TownCoordinates {
            spawn: spawn(),
            home_block: ChunkCoordinate { x: 10, z: 20 },
            town_blocks: vec![
                ChunkCoordinate { x: 10, z: 20 },
                ChunkCoordinate { x: 11, z: 20 },
            ],
        };

        assert_eq!(
            serde_json::to_value(coordinates).unwrap(),
            json!({
                "spawn": {
                    "world": "earth",
                    "x": 1.0,
                    "y": 64.0,
                    "z": 2.0,
                    "pitch": 0.0,
                    "yaw": 90.0
                },
                "homeBlock": [10, 20],
                "townBlocks": [[10, 20], [11, 20]]
            })
        );
    }
}