use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::model::{LichessColor, LichessLightUser, LichessSpeed, LichessVariantKey};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum LichessGameStatusName {
Created,
Started,
Aborted,
Mate,
Resign,
Stalemate,
Timeout,
Draw,
Outoftime,
Cheat,
NoStart,
UnknownFinish,
InsufficientMaterialClaim,
VariantEnd,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGameOpening {
pub eco: String,
pub name: String,
pub ply: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessMoveJudgment {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGameMoveAnalysis {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub eval: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mate: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub best: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variation: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub judgment: Option<LichessMoveJudgment>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessPlayerAnalysis {
pub inaccuracy: u32,
pub mistake: u32,
pub blunder: u32,
pub acpl: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accuracy: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessGamePlayer {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user: Option<LichessLightUser>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rating: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rating_diff: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provisional: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ai_level: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub analysis: Option<LichessPlayerAnalysis>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub team: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGamePlayers {
pub white: LichessGamePlayer,
pub black: LichessGamePlayer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessGameClock {
pub initial: u32,
pub increment: u32,
pub total_time: u32,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGameDivision {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub middle: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub end: Option<u32>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGameArenaTour {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGameSwissTour {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessGame {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rated: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant: Option<LichessVariantKey>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub speed: Option<LichessSpeed>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub perf: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_move_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<LichessGameStatusName>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub players: Option<LichessGamePlayers>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initial_fen: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub winner: Option<LichessColor>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub opening: Option<LichessGameOpening>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub moves: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pgn: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub days_per_turn: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub analysis: Option<Vec<LichessGameMoveAnalysis>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arena_tour: Option<LichessGameArenaTour>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub swiss_tour: Option<LichessGameSwissTour>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub clock: Option<LichessGameClock>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub clocks: Option<Vec<u32>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub division: Option<LichessGameDivision>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessImportedGame {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessNowPlayingOpponent {
pub id: String,
pub username: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rating: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rating_diff: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ai: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessNowPlayingGame {
pub game_id: String,
pub full_id: String,
pub color: LichessColor,
pub fen: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub has_moved: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_my_turn: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_move: Option<String>,
pub opponent: LichessNowPlayingOpponent,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub perf: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rated: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seconds_left: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub speed: Option<LichessSpeed>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant: Option<LichessVariantKey>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tournament_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub swiss_id: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessNowPlaying {
pub nb_my_turn: u32,
#[serde(default)]
pub now_playing: Vec<LichessNowPlayingGame>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGameChatMessage {
pub user: String,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessGameMoveUpdate {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fen: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lm: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wc: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bc: Option<i64>,
#[serde(flatten)]
pub other: HashMap<String, serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_full_game() {
let json = r#"{
"id":"q7ZvsdUF","rated":true,"variant":"standard","speed":"blitz",
"perf":"blitz","createdAt":1514505150384,"status":"mate",
"winner":"white",
"players":{
"white":{"user":{"id":"a","name":"A"},"rating":1600,"ratingDiff":8},
"black":{"user":{"id":"b","name":"B"},"rating":1590,"ratingDiff":-8}
},
"opening":{"eco":"B01","name":"Scandinavian","ply":2},
"moves":"e4 d5 exd5",
"clock":{"initial":300,"increment":3,"totalTime":420}
}"#;
let game: LichessGame = serde_json::from_str(json).unwrap();
assert_eq!(game.id, "q7ZvsdUF");
assert_eq!(game.status, Some(LichessGameStatusName::Mate));
assert_eq!(game.winner, Some(LichessColor::White));
assert_eq!(game.players.unwrap().white.rating_diff, Some(8));
assert_eq!(game.clock.unwrap().total_time, 420);
}
#[test]
fn parses_minimal_game() {
let game: LichessGame = serde_json::from_str(r#"{"id":"abcd1234"}"#).unwrap();
assert_eq!(game.id, "abcd1234");
assert!(game.players.is_none());
}
#[test]
fn status_uses_camel_case_names() {
let game: LichessGame =
serde_json::from_str(r#"{"id":"x","status":"variantEnd"}"#).unwrap();
assert_eq!(game.status, Some(LichessGameStatusName::VariantEnd));
}
#[test]
fn parses_arena_and_swiss_tour_objects() {
let json = r#"{"id":"g","arenaTour":{"id":"abc","name":"Hourly"},
"swissTour":{"id":"xyz"}}"#;
let game: LichessGame = serde_json::from_str(json).unwrap();
let arena = game.arena_tour.unwrap();
assert_eq!(arena.id.as_deref(), Some("abc"));
assert_eq!(arena.name.as_deref(), Some("Hourly"));
assert_eq!(game.swiss_tour.unwrap().id.as_deref(), Some("xyz"));
}
}