use std::collections::BTreeMap;
use serde::{Deserialize, Serialize, de::Error as _};
use crate::{
endpoints::location::position_xyz_object,
model::{location::PositionXYZ, query::UuidQuery},
};
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Shops {
shops: Vec<Shop>,
}
impl Shops {
pub(crate) fn into_vec(self) -> Vec<Shop> {
self.shops
}
}
impl<'de> Deserialize<'de> for Shops {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let shops_by_id = BTreeMap::<String, Shop>::deserialize(deserializer)?;
let mut shops_by_id = shops_by_id
.into_iter()
.map(|(id, shop)| {
Ok((id.parse::<u32>().map_err(D::Error::custom)?, shop))
})
.collect::<Result<Vec<_>, D::Error>>()?;
shops_by_id.sort_by_key(|(id, _)| *id);
Ok(Self {
shops: shops_by_id.into_iter().map(|(_, shop)| shop).collect(),
})
}
}
#[derive(Clone, Serialize)]
pub(crate) struct ShopQuery {
query: UuidQuery,
key: String,
}
impl ShopQuery {
pub(crate) fn new(query: UuidQuery, key: String) -> Self {
Self { query, key }
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct Shop {
pub item: String,
pub price: f64,
pub amount: u32,
#[serde(rename = "type")]
pub kind: ShopKind,
pub stock: u32,
#[serde(with = "position_xyz_object")]
pub location: PositionXYZ,
}
#[derive(
Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord,
)]
#[serde(rename_all = "lowercase")]
pub enum ShopKind {
Buying,
Selling,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_json::json;
use uuid::Uuid;
use crate::{
endpoints::shop::{Shop, ShopKind, ShopQuery, Shops},
model::{location::PositionXYZ, query::UuidQuery},
};
#[test]
fn shop_query_serializes_key_in_body() {
let uuid =
Uuid::from_str("5b8274bf-b162-4336-85a0-48f9d5380a78").unwrap();
let query = ShopQuery::new(UuidQuery::from(uuid), "API_KEY".into());
assert_eq!(
serde_json::to_value(query).unwrap(),
json!({
"query": ["5b8274bf-b162-4336-85a0-48f9d5380a78"],
"key": "API_KEY"
})
);
}
#[test]
fn deserializes_shop_response() {
let response: Vec<Shops> = serde_json::from_value(json!([
{
"0": {
"item": "TOTEM_OF_UNDYING",
"price": 150.0,
"amount": 1,
"type": "selling",
"stock": 3,
"location": {
"x": 0,
"y": 1,
"z": 2
}
},
"1": {
"item": "DIAMOND_CHESTPLATE",
"price": 2.0,
"amount": 1,
"type": "selling",
"stock": 15,
"location": {
"x": 3,
"y": 4,
"z": 5
}
}
}
]))
.unwrap();
assert_eq!(
response[0].shops.as_slice(),
[
Shop {
item: "TOTEM_OF_UNDYING".into(),
price: 150.0,
amount: 1,
kind: ShopKind::Selling,
stock: 3,
location: PositionXYZ { x: 0, y: 1, z: 2 },
},
Shop {
item: "DIAMOND_CHESTPLATE".into(),
price: 2.0,
amount: 1,
kind: ShopKind::Selling,
stock: 15,
location: PositionXYZ { x: 3, y: 4, z: 5 },
},
]
);
}
#[test]
fn deserializes_numeric_shop_keys_in_numeric_order() {
let shops: Shops = serde_json::from_value(json!({
"10": {
"item": "DIAMOND",
"price": 1.0,
"amount": 1,
"type": "buying",
"stock": 10,
"location": { "x": 0, "y": 0, "z": 0 }
},
"2": {
"item": "EMERALD",
"price": 2.0,
"amount": 1,
"type": "selling",
"stock": 20,
"location": { "x": 0, "y": 0, "z": 0 }
}
}))
.unwrap();
assert_eq!(shops.shops[0].item, "EMERALD");
assert_eq!(shops.shops[1].item, "DIAMOND");
}
}