speedrun_api/api/
levels.rs

1//! # Levels
2//!
3//! Endpoints available for levels.
4use std::{borrow::Cow, collections::BTreeSet, fmt::Display};
5
6use serde::{Deserialize, Serialize};
7
8use super::{
9    endpoint::Endpoint, error::BodyError, leaderboards::LeaderboardEmbeds,
10    query_params::QueryParams, CategoriesSorting, Direction, Pageable, VariablesSorting,
11};
12
13/// Embeds available for levels.
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
15pub enum LevelEmbeds {
16    /// Embed per-level categories applicable to the requested level.
17    Categories,
18    /// Embed the variables applicable to the requested level.
19    Variables,
20}
21
22/// Represents a level ID.
23#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
24pub struct LevelId<'a>(Cow<'a, str>);
25
26impl<'a> LevelId<'a> {
27    /// Create a new [`LevelId`].
28    pub fn new<T>(id: T) -> Self
29    where
30        T: Into<Cow<'a, str>>,
31    {
32        Self(id.into())
33    }
34}
35
36impl<'a, T> From<T> for LevelId<'a>
37where
38    T: Into<Cow<'a, str>>,
39{
40    fn from(value: T) -> Self {
41        LevelId::new(value)
42    }
43}
44
45impl Display for LevelId<'_> {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        write!(f, "{}", &self.0)
48    }
49}
50
51/// Retrieve a single level, itentified by its ID.
52#[derive(Debug, Builder, Serialize, Clone)]
53#[builder(setter(into, strip_option))]
54pub struct Level<'a> {
55    #[doc = r"`ID` of the level."]
56    #[serde(skip)]
57    id: LevelId<'a>,
58    #[builder(setter(name = "_embed"), private, default)]
59    #[serde(serialize_with = "super::utils::serialize_as_csv")]
60    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
61    embed: BTreeSet<LevelEmbeds>,
62}
63
64/// Retrieves all categories for the given level.
65#[derive(Debug, Builder, Serialize, Clone)]
66#[builder(setter(into, strip_option))]
67#[serde(rename_all = "kebab-case")]
68pub struct LevelCategories<'a> {
69    #[doc = r"`ID` of the level."]
70    #[serde(skip)]
71    id: LevelId<'a>,
72    #[doc = r"When give, filters miscellaneous categories."]
73    #[builder(default)]
74    miscellaneous: Option<bool>,
75    #[doc = r"Sorting options for results."]
76    #[builder(default)]
77    orderby: Option<CategoriesSorting>,
78    #[doc = r"Sort direction"]
79    #[builder(default)]
80    direction: Option<Direction>,
81}
82
83/// Retrieves all applicable variables for the given level.
84#[derive(Debug, Builder, Serialize, Clone)]
85#[builder(setter(into, strip_option))]
86#[serde(rename_all = "kebab-case")]
87pub struct LevelVariables<'a> {
88    #[doc = r"`ID` of the level."]
89    #[serde(skip)]
90    id: LevelId<'a>,
91    #[doc = r"Sorting options for results."]
92    #[builder(default)]
93    orderby: Option<VariablesSorting>,
94    #[doc = r"Sort direction"]
95    #[builder(default)]
96    direction: Option<Direction>,
97}
98
99/// Retrieves the leaderboards of the given level for all available categories.
100#[derive(Debug, Builder, Serialize, Clone)]
101#[builder(setter(into, strip_option))]
102#[serde(rename_all = "kebab-case")]
103pub struct LevelRecords<'a> {
104    #[doc = r"`ID` of the level."]
105    #[serde(skip)]
106    id: LevelId<'a>,
107    #[doc = r"Return `top` number of places (default: 3)."]
108    #[builder(default)]
109    top: Option<i64>,
110    #[doc = r"Do not return empty leaderboards when `true`."]
111    #[builder(default)]
112    skip_empty: Option<bool>,
113    #[builder(setter(name = "_embed"), private, default)]
114    #[serde(serialize_with = "super::utils::serialize_as_csv")]
115    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
116    embed: BTreeSet<LeaderboardEmbeds>,
117}
118
119impl Level<'_> {
120    /// Create a builder for this endpoint.
121    pub fn builder<'a>() -> LevelBuilder<'a> {
122        LevelBuilder::default()
123    }
124}
125
126impl LevelBuilder<'_> {
127    /// Add an embedded resource to this result
128    pub fn embed(&mut self, embed: LevelEmbeds) -> &mut Self {
129        self.embed.get_or_insert_with(BTreeSet::new).insert(embed);
130        self
131    }
132
133    /// Add multiple embedded resources to this result
134    pub fn embeds<I>(&mut self, iter: I) -> &mut Self
135    where
136        I: Iterator<Item = LevelEmbeds>,
137    {
138        self.embed.get_or_insert_with(BTreeSet::new).extend(iter);
139        self
140    }
141}
142
143impl LevelCategories<'_> {
144    /// Create a builder for this endpoint.
145    pub fn builder<'a>() -> LevelCategoriesBuilder<'a> {
146        LevelCategoriesBuilder::default()
147    }
148}
149
150impl LevelVariables<'_> {
151    /// Create a builder for this endpoint.
152    pub fn builder<'a>() -> LevelVariablesBuilder<'a> {
153        LevelVariablesBuilder::default()
154    }
155}
156
157impl LevelRecords<'_> {
158    /// Create a builder for this endpoint.
159    pub fn builder<'a>() -> LevelRecordsBuilder<'a> {
160        LevelRecordsBuilder::default()
161    }
162}
163
164impl LevelRecordsBuilder<'_> {
165    /// Add an embedded resource to this result
166    pub fn embed(&mut self, embed: LeaderboardEmbeds) -> &mut Self {
167        self.embed.get_or_insert_with(BTreeSet::new).insert(embed);
168        self
169    }
170
171    /// Add multiple embedded resources to this result
172    pub fn embeds<I>(&mut self, iter: I) -> &mut Self
173    where
174        I: Iterator<Item = LeaderboardEmbeds>,
175    {
176        self.embed.get_or_insert_with(BTreeSet::new).extend(iter);
177        self
178    }
179}
180
181impl LevelEmbeds {
182    fn as_str(&self) -> &'static str {
183        match self {
184            LevelEmbeds::Categories => "categories",
185            LevelEmbeds::Variables => "variables",
186        }
187    }
188}
189
190impl Endpoint for Level<'_> {
191    fn endpoint(&self) -> Cow<'static, str> {
192        format!("/levels/{}", self.id).into()
193    }
194
195    fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
196        QueryParams::with(self)
197    }
198}
199
200impl Endpoint for LevelCategories<'_> {
201    fn endpoint(&self) -> Cow<'static, str> {
202        format!("/levels/{}/categories", self.id).into()
203    }
204
205    fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
206        QueryParams::with(self)
207    }
208}
209
210impl Endpoint for LevelVariables<'_> {
211    fn endpoint(&self) -> Cow<'static, str> {
212        format!("/levels/{}/variables", self.id).into()
213    }
214
215    fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
216        QueryParams::with(self)
217    }
218}
219
220impl Endpoint for LevelRecords<'_> {
221    fn endpoint(&self) -> Cow<'static, str> {
222        format!("/levels/{}/records", self.id).into()
223    }
224
225    fn query_parameters(&self) -> Result<QueryParams<'_>, BodyError> {
226        QueryParams::with(self)
227    }
228}
229
230impl From<&LevelEmbeds> for &'static str {
231    fn from(value: &LevelEmbeds) -> Self {
232        value.as_str()
233    }
234}
235
236impl Pageable for LevelRecords<'_> {}