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::{
    color::{RgbaColor, rgba_hex},
    named_uuid::NamedUuid,
    timestamp::Timestamp,
};

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Nation {
    pub name: String,
    pub uuid: Uuid,
    pub board: Option<String>,
    #[serde(with = "rgba_hex")]
    pub dynmap_colour: RgbaColor,
    #[serde(with = "rgba_hex")]
    pub dynmap_outline: RgbaColor,
    pub wiki: Option<String>,
    #[serde(rename = "king")]
    pub leader: NamedUuid,
    pub capital: NamedUuid,
    pub timestamps: NationTimestamps,
    pub status: NationStatus,
    pub stats: NationStats,
    pub coordinates: NationCoordinates,
    pub residents: Vec<NamedUuid>,
    pub towns: Vec<NamedUuid>,
    pub allies: Vec<NamedUuid>,
    pub enemies: Vec<NamedUuid>,
    pub sanctioned: Vec<NamedUuid>,
    pub ranks: BTreeMap<NationRankKind, Vec<NamedUuid>>,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct NationTimestamps {
    /// Creation time.
    #[cfg_attr(
        feature = "chrono",
        serde(with = "crate::model::timestamp::seconds")
    )]
    pub registered: Timestamp,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct NationStatus {
    pub is_public: bool,
    /// Invite-less joining is enabled.
    pub is_open: bool,
    pub is_neutral: bool,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct NationStats {
    pub num_town_blocks: i32,
    pub num_residents: i32,
    pub num_towns: i32,
    pub num_allies: i32,
    pub num_enemies: i32,
    /// Shown on `/n` in-game.
    pub balance: f64,
}

#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct NationCoordinates {
    pub spawn: NationSpawn,
}

/// The nation spawn may be unset, in which case its fields are `null` in the
/// API response. This differs from town spawns, whose coordinates are always
/// populated.
#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct NationSpawn {
    pub world: Option<String>,
    pub x: Option<f32>,
    pub y: Option<f32>,
    pub z: Option<f32>,
    pub pitch: Option<f32>,
    pub yaw: Option<f32>,
}

#[derive(
    Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord, Debug,
)]
#[serde(rename_all = "PascalCase")]
pub enum NationRankKind {
    Chancellor,
    Colonist,
    Diplomat,
    Treasurer,
    #[serde(untagged)]
    Other(String),
}

#[cfg(test)]
mod tests {
    use std::collections::BTreeMap;

    use serde_json::json;
    use uuid::Uuid;

    use super::Nation;
    use crate::model::{color::RgbaColor, named_uuid::NamedUuid};

    #[test]
    fn deserializes_dynmap_colours_from_rgba_hex_strings() {
        let nation: Nation = serde_json::from_value(json!({
            "name": "Test",
            "uuid": "00000000-0000-0000-0000-000000000000",
            "board": null,
            "dynmapColour": "11223344",
            "dynmapOutline": "aabbccdd",
            "wiki": null,
            "king": {
                "name": "Leader",
                "uuid": "00000000-0000-0000-0000-000000000000"
            },
            "capital": {
                "name": "Capital",
                "uuid": "00000000-0000-0000-0000-000000000000"
            },
            "timestamps": {
                "registered": 0
            },
            "status": {
                "isPublic": true,
                "isOpen": false,
                "isNeutral": true
            },
            "stats": {
                "numTownBlocks": 0,
                "numResidents": 0,
                "numTowns": 0,
                "numAllies": 0,
                "numEnemies": 0,
                "balance": 0.0
            },
            "coordinates": {
                "spawn": {
                    "world": "world",
                    "x": 0.0,
                    "y": 64.0,
                    "z": 0.0,
                    "pitch": 0.0,
                    "yaw": 0.0
                }
            },
            "residents": [],
            "towns": [],
            "allies": [],
            "enemies": [],
            "sanctioned": [],
            "ranks": {}
        }))
        .unwrap();

        assert_eq!(
            nation.dynmap_colour,
            RgbaColor::new(0x11, 0x22, 0x33, 0x44)
        );
        assert_eq!(
            nation.dynmap_outline,
            RgbaColor::new(0xaa, 0xbb, 0xcc, 0xdd)
        );
    }

    #[test]
    fn deserializes_nations_with_unset_spawn_coordinates() {
        let nation: Nation = serde_json::from_value(json!({
            "name": "Test",
            "uuid": "00000000-0000-0000-0000-000000000000",
            "board": null,
            "dynmapColour": "11223344",
            "dynmapOutline": "aabbccdd",
            "wiki": null,
            "king": { "name": "Leader", "uuid": "00000000-0000-0000-0000-000000000000" },
            "capital": { "name": "Capital", "uuid": "00000000-0000-0000-0000-000000000000" },
            "timestamps": { "registered": 0 },
            "status": { "isPublic": true, "isOpen": false, "isNeutral": true },
            "stats": {
                "numTownBlocks": 0, "numResidents": 0, "numTowns": 0,
                "numAllies": 0, "numEnemies": 0, "balance": 0.0
            },
            "coordinates": {
                "spawn": { "world": null, "x": null, "y": null, "z": null, "pitch": null, "yaw": null }
            },
            "residents": [], "towns": [], "allies": [], "enemies": [], "sanctioned": [], "ranks": {}
        }))
        .unwrap();

        assert_eq!(nation.coordinates.spawn.world, None);
        assert_eq!(nation.coordinates.spawn.x, None);
    }

    #[test]
    fn serializes_dynmap_colours_as_rgba_hex_strings() {
        let nation = Nation {
            name: "Test".to_string(),
            uuid: Uuid::nil(),
            board: None,
            dynmap_colour: RgbaColor::new(0x11, 0x22, 0x33, 0x44),
            dynmap_outline: RgbaColor::new(0xaa, 0xbb, 0xcc, 0xdd),
            wiki: None,
            leader: NamedUuid {
                name: "Leader".to_string(),
                uuid: Uuid::nil(),
            },
            capital: NamedUuid {
                name: "Capital".to_string(),
                uuid: Uuid::nil(),
            },
            timestamps: super::NationTimestamps { registered: 0 },
            status: super::NationStatus {
                is_public: true,
                is_open: false,
                is_neutral: true,
            },
            stats: super::NationStats {
                num_town_blocks: 0,
                num_residents: 0,
                num_towns: 0,
                num_allies: 0,
                num_enemies: 0,
                balance: 0.0,
            },
            coordinates: super::NationCoordinates {
                spawn: super::NationSpawn {
                    world: Some("world".to_string()),
                    x: Some(0.0),
                    y: Some(64.0),
                    z: Some(0.0),
                    pitch: Some(0.0),
                    yaw: Some(0.0),
                },
            },
            residents: vec![],
            towns: vec![],
            allies: vec![],
            enemies: vec![],
            sanctioned: vec![],
            ranks: BTreeMap::new(),
        };

        let value = serde_json::to_value(nation).unwrap();

        assert_eq!(value["dynmapColour"], "11223344");
        assert_eq!(value["dynmapOutline"], "aabbccdd");
    }
}