Skip to main content

lichess_api/model/broadcasts/
create_tournament.rs

1use crate::model::broadcasts::BroadcastTiebreakExtendedCode;
2use crate::model::{Body, Request};
3use serde::Serialize;
4use serde::ser::SerializeMap;
5use serde_with::skip_serializing_none;
6
7#[skip_serializing_none]
8#[derive(Default, Clone, Debug, Serialize)]
9pub struct CreateBroadcastTournamentForm {
10    pub name: String,
11    #[serde(rename = "info.format")]
12    pub info_format: Option<String>,
13    #[serde(rename = "info.tc")]
14    pub info_tc: Option<String>,
15    #[serde(rename = "info.fideTC")]
16    pub info_fide_tc: Option<String>,
17    #[serde(rename = "info.timeZone")]
18    pub info_time_zone: Option<String>,
19    #[serde(rename = "info.location")]
20    pub info_location: Option<String>,
21    #[serde(rename = "info.players")]
22    pub info_players: Option<String>,
23    #[serde(rename = "info.website")]
24    pub info_website: Option<String>,
25    #[serde(rename = "info.standings")]
26    pub info_standings: Option<String>,
27    #[serde(rename = "info.regulations")]
28    pub info_regulations: Option<String>,
29    pub markdown: Option<String>,
30    #[serde(rename = "showScores")]
31    pub show_scores: Option<bool>,
32    #[serde(rename = "showRatingDiffs")]
33    pub show_rating_diffs: Option<bool>,
34    #[serde(rename = "teamTable")]
35    pub team_table: Option<bool>,
36    pub visibility: Option<String>,
37    pub players: Option<String>,
38    pub teams: Option<String>,
39    pub tier: Option<i32>,
40    pub tiebreaks: Option<Vec<BroadcastTiebreakExtendedCode>>,
41    #[serde(flatten)]
42    pub grouping: Option<BroadcastGrouping>,
43}
44
45/// Groups this broadcast tournament together with others.
46///
47/// `score_groups` serializes as indexed form keys (`grouping.scoreGroups[0]`,
48/// `grouping.scoreGroups[1]`, ...) per the Lichess API's non-standard array
49/// encoding for this field, which a single struct field cannot otherwise
50/// produce with `serde_urlencoded` - hence the manual `Serialize` impl below.
51#[derive(Default, Clone, Debug)]
52pub struct BroadcastGrouping {
53    pub info_name: Option<String>,
54    /// Linebreak separated list of tournament IDs to group together.
55    pub info_tours: Option<String>,
56    /// Each entry is a comma separated list of tournament IDs grouped for scoring.
57    pub score_groups: Option<Vec<String>>,
58}
59
60impl Serialize for BroadcastGrouping {
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: serde::Serializer,
64    {
65        let score_groups_len = self.score_groups.as_ref().map_or(0, Vec::len);
66        let len = self.info_name.is_some() as usize
67            + self.info_tours.is_some() as usize
68            + score_groups_len;
69        let mut map = serializer.serialize_map(Some(len))?;
70        if let Some(name) = &self.info_name {
71            map.serialize_entry("grouping.info.name", name)?;
72        }
73        if let Some(tours) = &self.info_tours {
74            map.serialize_entry("grouping.info.tours", tours)?;
75        }
76        if let Some(score_groups) = &self.score_groups {
77            for (i, group) in score_groups.iter().enumerate() {
78                map.serialize_entry(&format!("grouping.scoreGroups[{i}]"), group)?;
79            }
80        }
81        map.end()
82    }
83}
84
85#[derive(Default, Clone, Debug, Serialize)]
86pub struct PostQuery;
87
88pub type PostRequest = Request<PostQuery, CreateBroadcastTournamentForm>;
89
90impl PostRequest {
91    pub fn new(form: CreateBroadcastTournamentForm) -> Self {
92        Self::post("/broadcast/new", None, Body::Form(form), None)
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn grouping_serializes_score_groups_as_indexed_keys() {
102        let form = CreateBroadcastTournamentForm {
103            name: "Sinquefield Cup".to_string(),
104            grouping: Some(BroadcastGrouping {
105                info_name: Some("Chess Olympiad | Open".to_string()),
106                info_tours: Some("wYigbpXq\nM5YHvpOX".to_string()),
107                score_groups: Some(vec![
108                    "wYigbpXq,M5YHvpOX".to_string(),
109                    "q6ezoCXP".to_string(),
110                ]),
111            }),
112            ..Default::default()
113        };
114
115        let encoded = serde_urlencoded::to_string(&form).unwrap();
116
117        assert_eq!(
118            encoded,
119            "name=Sinquefield+Cup\
120             &grouping.info.name=Chess+Olympiad+%7C+Open\
121             &grouping.info.tours=wYigbpXq%0AM5YHvpOX\
122             &grouping.scoreGroups%5B0%5D=wYigbpXq%2CM5YHvpOX\
123             &grouping.scoreGroups%5B1%5D=q6ezoCXP"
124        );
125    }
126
127    #[test]
128    fn grouping_omitted_when_none() {
129        let form = CreateBroadcastTournamentForm {
130            name: "Sinquefield Cup".to_string(),
131            ..Default::default()
132        };
133
134        let encoded = serde_urlencoded::to_string(&form).unwrap();
135
136        assert_eq!(encoded, "name=Sinquefield+Cup");
137    }
138}