use futures_util::stream::BoxStream;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use crate::api::gameplay::games::LichessGame;
use crate::client::LichessClient;
use crate::config::Host;
use crate::error::Result;
use crate::http;
use crate::model::{GameExportOptions, LichessVariantKey};
#[derive(Debug)]
pub struct BulkPairingApi<'a> {
client: &'a LichessClient,
}
impl<'a> BulkPairingApi<'a> {
pub(crate) fn new(client: &'a LichessClient) -> Self {
Self { client }
}
pub async fn list(&self) -> Result<Vec<LichessBulkPairing>> {
let request = self
.client
.request(Method::GET, Host::Default, "/api/bulk-pairing");
http::json(request, "Vec<LichessBulkPairing>").await
}
pub async fn get(&self, id: &str) -> Result<LichessBulkPairing> {
let path = format!("/api/bulk-pairing/{}", http::segment(id));
let request = self.client.request(Method::GET, Host::Default, &path);
http::json(request, "LichessBulkPairing").await
}
#[must_use]
pub fn create(&self, players: &'a str) -> BulkPairingRequest<'a> {
BulkPairingRequest::new(self.client, players)
}
pub async fn start_clocks(&self, id: &str) -> Result<()> {
let path = format!("/api/bulk-pairing/{}/start-clocks", http::segment(id));
http::ok(self.client.request(Method::POST, Host::Default, &path)).await
}
pub async fn delete(&self, id: &str) -> Result<()> {
let path = format!("/api/bulk-pairing/{}", http::segment(id));
http::ok(self.client.request(Method::DELETE, Host::Default, &path)).await
}
#[must_use]
pub fn games(&self, id: &'a str) -> BulkGamesRequest<'a> {
BulkGamesRequest::new(self.client, id)
}
}
#[derive(Debug)]
pub struct BulkGamesRequest<'a> {
client: &'a LichessClient,
id: &'a str,
export: GameExportOptions,
}
impl<'a> BulkGamesRequest<'a> {
pub(crate) fn new(client: &'a LichessClient, id: &'a str) -> Self {
Self {
client,
id,
export: GameExportOptions::default(),
}
}
#[must_use]
pub fn export(mut self, options: GameExportOptions) -> Self {
self.export = options;
self
}
pub async fn stream(self) -> Result<BoxStream<'static, Result<LichessGame>>> {
let request = self.request(http::ACCEPT_NDJSON);
http::stream(request, self.client.max_line_bytes()).await
}
pub async fn pgn(self) -> Result<String> {
http::text(self.request(http::ACCEPT_PGN)).await
}
fn request(&self, accept: &'static str) -> http::ApiRequest {
let path = format!("/api/bulk-pairing/{}/games", http::segment(self.id));
self.client
.request(Method::GET, Host::Default, &path)
.header(reqwest::header::ACCEPT, accept)
.query(&self.export)
}
}
#[derive(Debug, Default, Serialize)]
struct BulkPairingForm<'a> {
players: &'a str,
#[serde(rename = "clock.limit", skip_serializing_if = "Option::is_none")]
clock_limit: Option<u32>,
#[serde(rename = "clock.increment", skip_serializing_if = "Option::is_none")]
clock_increment: Option<u32>,
#[serde(rename = "pairAt", skip_serializing_if = "Option::is_none")]
pair_at: Option<i64>,
#[serde(rename = "startClocksAt", skip_serializing_if = "Option::is_none")]
start_clocks_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
rated: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
variant: Option<LichessVariantKey>,
#[serde(skip_serializing_if = "Option::is_none")]
days: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
fen: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
rules: Option<&'a str>,
}
#[derive(Debug)]
pub struct BulkPairingRequest<'a> {
client: &'a LichessClient,
form: BulkPairingForm<'a>,
}
impl<'a> BulkPairingRequest<'a> {
fn new(client: &'a LichessClient, players: &'a str) -> Self {
Self {
client,
form: BulkPairingForm {
players,
..Default::default()
},
}
}
#[must_use]
pub fn clock(mut self, limit_secs: u32, increment_secs: u32) -> Self {
self.form.clock_limit = Some(limit_secs);
self.form.clock_increment = Some(increment_secs);
self
}
#[must_use]
pub fn rated(mut self, rated: bool) -> Self {
self.form.rated = Some(rated);
self
}
#[must_use]
pub fn pair_at(mut self, timestamp: i64) -> Self {
self.form.pair_at = Some(timestamp);
self
}
#[must_use]
pub fn start_clocks_at(mut self, timestamp: i64) -> Self {
self.form.start_clocks_at = Some(timestamp);
self
}
#[must_use]
pub fn variant(mut self, variant: LichessVariantKey) -> Self {
self.form.variant = Some(variant);
self
}
#[must_use]
pub fn days(mut self, days: u32) -> Self {
self.form.days = Some(days);
self
}
#[must_use]
pub fn fen(mut self, fen: &'a str) -> Self {
self.form.fen = Some(fen);
self
}
#[must_use]
pub fn message(mut self, message: &'a str) -> Self {
self.form.message = Some(message);
self
}
#[must_use]
pub fn rules(mut self, rules: &'a str) -> Self {
self.form.rules = Some(rules);
self
}
pub async fn send(self) -> Result<LichessBulkPairing> {
let request = self
.client
.request(Method::POST, Host::Default, "/api/bulk-pairing")
.form(&self.form);
http::json(request, "LichessBulkPairing").await
}
}
impl LichessClient {
#[must_use]
pub fn bulk_pairing(&self) -> BulkPairingApi<'_> {
BulkPairingApi::new(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessBulkClock {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub increment: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessBulkPairingGame {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub white: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub black: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessBulkPairing {
pub id: String,
#[serde(default)]
pub games: Vec<LichessBulkPairingGame>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant: Option<LichessVariantKey>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub clock: Option<LichessBulkClock>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pair_at: Option<i64>,
#[serde(default)]
pub paired_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rated: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub start_clocks_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scheduled_at: Option<i64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_bulk_pairing_with_null_paired_at() {
let json = r#"{"id":"RVAcwgg7",
"games":[{"id":"NKop9IyD","black":"lizen1","white":"thibault"}],
"variant":"standard","clock":{"increment":0,"limit":300},
"pairAt":1612289869919,"pairedAt":null,"rated":false,
"startClocksAt":1612200422971,"scheduledAt":1612203514628}"#;
let pairing: LichessBulkPairing = serde_json::from_str(json).unwrap();
assert_eq!(pairing.games.len(), 1);
assert_eq!(pairing.paired_at, None);
assert_eq!(pairing.clock.unwrap().limit, Some(300));
}
}