1pub mod account;
2pub mod analysis;
3pub mod board;
4pub mod bot;
5pub mod challenges;
6pub mod external_engine;
7pub mod fide;
8pub mod games;
9pub mod messaging;
10pub mod oauth;
11pub mod openings;
12pub mod puzzles;
13pub mod relations;
14pub mod simuls;
15pub mod studies;
16pub mod tablebase;
17pub mod tv;
18pub mod users;
19
20use crate::error;
21use serde::{Deserialize, Serialize, de::DeserializeOwned};
22use serde_with::skip_serializing_none;
23
24pub trait BodyBounds: Serialize {}
25impl<B: Serialize> BodyBounds for B {}
26
27pub trait QueryBounds: Serialize + Default {}
28impl<Q: Serialize + Default> QueryBounds for Q {}
29
30pub trait ModelBounds: DeserializeOwned {}
31impl<M: DeserializeOwned> ModelBounds for M {}
32
33#[derive(Default, Clone, Debug)]
34pub enum Body<B: BodyBounds> {
35 Form(B),
36 Json(B),
37 PlainText(String),
38 #[default]
39 Empty,
40}
41
42impl<B: BodyBounds> Body<B> {
43 fn as_mime(&self) -> Option<mime::Mime> {
44 match &self {
45 Body::Form(_) => Some(mime::APPLICATION_WWW_FORM_URLENCODED),
46 Body::Json(_) => Some(mime::APPLICATION_JSON),
47 Body::PlainText(_) => Some(mime::TEXT_PLAIN),
48 Body::Empty => None,
49 }
50 }
51
52 fn as_encoded_string(&self) -> error::Result<String> {
53 let body = match &self {
54 Body::Form(form) => to_form_string(&form)?,
55 Body::Json(json) => to_json_string(&json)?,
56 Body::PlainText(text) => text.to_string(),
57 Body::Empty => "".to_string(),
58 };
59 Ok(body)
60 }
61}
62
63#[derive(Clone, Copy, Debug, Default)]
64pub enum Domain {
65 #[default]
66 Lichess,
67 Tablebase,
68 Engine,
69 Explorer,
70}
71
72impl AsRef<str> for Domain {
73 fn as_ref(&self) -> &str {
74 match self {
75 Domain::Lichess => "lichess.org",
76 Domain::Tablebase => "tablebase.lichess.ovh",
77 Domain::Engine => "engine.lichess.ovh",
78 Domain::Explorer => "explorer.lichess.ovh",
79 }
80 }
81}
82
83#[derive(Clone, Debug)]
84pub struct Request<Q, B = ()>
85where
86 Q: QueryBounds,
87 B: BodyBounds,
88{
89 pub(crate) domain: Domain,
90 pub(crate) method: http::Method,
91 pub(crate) path: String,
92 pub(crate) query: Option<Q>,
93 pub(crate) body: Body<B>,
94}
95
96impl<Q, B> Request<Q, B>
97where
98 Q: QueryBounds + Default,
99 B: BodyBounds,
100{
101 pub(crate) fn create(
102 path: impl Into<String>,
103 query: impl Into<Option<Q>>,
104 body: impl Into<Option<Body<B>>>,
105 domain: impl Into<Option<Domain>>,
106 method: http::Method,
107 ) -> Self {
108 Self {
109 domain: domain.into().unwrap_or_default(),
110 method,
111 path: path.into(),
112 query: query.into(),
113 body: body.into().unwrap_or_default(),
114 }
115 }
116
117 pub(crate) fn get(
118 path: impl Into<String>,
119 query: impl Into<Option<Q>>,
120 domain: impl Into<Option<Domain>>,
121 ) -> Self {
122 Self::create(path, query, None, domain, http::Method::GET)
123 }
124
125 pub(crate) fn post(
126 path: impl Into<String>,
127 query: impl Into<Option<Q>>,
128 body: impl Into<Option<Body<B>>>,
129 domain: impl Into<Option<Domain>>,
130 ) -> Self {
131 Self::create(path, query, body, domain, http::Method::POST)
132 }
133
134 pub(crate) fn put(
135 path: impl Into<String>,
136 query: impl Into<Option<Q>>,
137 body: impl Into<Option<Body<B>>>,
138 domain: impl Into<Option<Domain>>,
139 ) -> Self {
140 Self::create(path, query, body, domain, http::Method::PUT)
141 }
142
143 pub(crate) fn delete(
144 path: impl Into<String>,
145 query: impl Into<Option<Q>>,
146 body: impl Into<Option<Body<B>>>,
147 domain: Option<Domain>,
148 ) -> Self {
149 Self::create(path, query, body, domain, http::Method::DELETE)
150 }
151}
152
153impl<Q, B> Request<Q, B>
154where
155 Q: QueryBounds,
156 B: BodyBounds,
157{
158 pub(crate) fn as_http_request(
159 self,
160 accept: &str,
161 ) -> error::Result<http::Request<bytes::Bytes>> {
162 make_request(
163 self.domain,
164 self.method,
165 self.path,
166 self.query,
167 self.body,
168 accept,
169 )
170 }
171}
172
173fn make_request<Q, B>(
174 domain: Domain,
175 method: http::Method,
176 path: String,
177 query: Option<Q>,
178 body: Body<B>,
179 accept: &str,
180) -> error::Result<http::Request<bytes::Bytes>>
181where
182 Q: QueryBounds,
183 B: BodyBounds,
184{
185 let mut builder = http::Request::builder();
186
187 if let Some(mime) = body.as_mime() {
188 builder = builder.header(http::header::CONTENT_TYPE, mime.to_string());
189 }
190 let accept_header = http::HeaderValue::from_str(accept)
191 .map_err(|e| error::Error::HttpRequestBuilder(http::Error::from(e)))?;
192 builder = builder.header(http::header::ACCEPT, accept_header);
193
194 let url = make_url(domain, path, query)?;
195 let body = bytes::Bytes::from(body.as_encoded_string()?);
196
197 let request = builder
198 .method(method)
199 .uri(url.as_str())
200 .body(body)
201 .map_err(|e| error::Error::HttpRequestBuilder(e))?;
202
203 Ok(request)
204}
205
206fn make_url<Q>(domain: Domain, path: String, query: Option<Q>) -> error::Result<url::Url>
207where
208 Q: QueryBounds,
209{
210 let base_url = format!("https://{}", domain.as_ref());
211 let mut url = url::Url::parse(&base_url).expect("invalid base url");
212
213 if let Some(query) = query {
214 let mut query_pairs = url.query_pairs_mut();
215 let query_serializer = serde_urlencoded::Serializer::new(&mut query_pairs);
216 query.serialize(query_serializer)?;
217 }
218
219 url.set_path(&path.to_string());
220
221 Ok(url)
222}
223
224fn to_json_string<B: BodyBounds>(body: &B) -> error::Result<String> {
225 serde_json::to_string(&body).map_err(|e| error::Error::Json(e))
226}
227
228fn to_form_string<B: BodyBounds>(body: &B) -> error::Result<String> {
229 serde_urlencoded::to_string(&body).map_err(|e| error::Error::UrlEncoded(e))
230}
231
232#[derive(Clone, Debug, Serialize, Deserialize)]
233pub struct Ok {
234 pub ok: bool,
235}
236
237#[derive(Clone, Debug, Serialize, Deserialize)]
238#[serde(untagged)]
239pub enum Response<M> {
240 Model(M),
241 Error { error: String },
242}
243
244#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
245#[serde(rename_all = "lowercase")]
246pub enum Color {
247 #[default]
248 White,
249 Black,
250 Random,
251}
252
253#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
254#[serde(rename_all = "lowercase")]
255pub enum PlayerColor {
256 #[default]
257 White,
258 Black,
259}
260
261#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
262#[serde(rename_all = "camelCase")]
263pub enum Speed {
264 UltraBullet,
265 Bullet,
266 Blitz,
267 Rapid,
268 Classical,
269 Correspondence,
270}
271
272#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)]
273#[serde(rename_all = "camelCase")]
274pub enum PerfType {
275 UltraBullet,
276 Bullet,
277 Blitz,
278 Rapid,
279 Classical,
280 Chess960,
281 Crazyhouse,
282 Antichess,
283 Atomic,
284 Horde,
285 KingOfTheHill,
286 RacingKings,
287 ThreeCheck,
288}
289
290impl PerfType {
291 pub fn as_str(&self) -> &'static str {
292 match self {
293 Self::UltraBullet => "ultraBullet",
294 Self::Bullet => "bullet",
295 Self::Blitz => "blitz",
296 Self::Rapid => "rapid",
297 Self::Classical => "classical",
298 Self::Chess960 => "chess960",
299 Self::Crazyhouse => "crazyhouse",
300 Self::Antichess => "antichess",
301 Self::Atomic => "atomic",
302 Self::Horde => "horde",
303 Self::KingOfTheHill => "kingOfTheHill",
304 Self::RacingKings => "racingKings",
305 Self::ThreeCheck => "threeCheck",
306 }
307 }
308}
309
310impl std::fmt::Display for PerfType {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 f.write_str(self.as_str())
313 }
314}
315
316#[skip_serializing_none]
317#[derive(Clone, Debug, Deserialize, Serialize)]
318pub struct LightUser {
319 pub id: String,
320 pub name: String,
321 pub title: Option<Title>,
322 pub flair: Option<String>,
323 pub patron: Option<bool>,
324 pub online: Option<bool>,
325}
326
327#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
328#[serde(rename_all = "UPPERCASE")]
329pub enum Title {
330 Gm,
331 Wgm,
332 Im,
333 Wim,
334 Fm,
335 Wfm,
336 Nm,
337 Cm,
338 Wcm,
339 Wnm,
340 Lm,
341 Bot,
342}
343
344#[skip_serializing_none]
345#[derive(Clone, Debug, Deserialize, Serialize)]
346pub struct Variant {
347 pub key: VariantKey,
348 pub name: String,
349 pub short: Option<String>,
350 pub icon: Option<String>,
351}
352
353#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
354#[serde(rename_all = "camelCase")]
355pub enum VariantKey {
356 #[default]
357 Standard,
358 Chess960,
359 Crazyhouse,
360 Antichess,
361 Atomic,
362 Horde,
363 KingOfTheHill,
364 RacingKings,
365 ThreeCheck,
366 FromPosition,
367}
368
369#[derive(Default, Clone, Debug, Serialize, PartialEq, Eq, Deserialize)]
370#[serde(rename_all = "camelCase")]
371pub enum Room {
372 #[default]
373 Player,
374 Spectator,
375}
376
377#[derive(Clone, Debug, Deserialize, Serialize)]
378pub struct GameCompat {
379 pub bot: Option<bool>,
380 pub board: Option<bool>,
381}
382
383#[serde_with::skip_serializing_none]
384#[derive(Clone, Debug, Serialize, Deserialize)]
385#[serde(rename_all = "camelCase")]
386pub struct Clock {
387 pub initial: u32,
388 pub increment: u32,
389 pub total_time: Option<u32>,
390}
391
392#[derive(Clone, Copy, Debug, PartialEq, Eq)]
393pub enum Days {
394 One,
395 Two,
396 Three,
397 Five,
398 Seven,
399 Ten,
400 Fourteen,
401}
402
403impl Serialize for Days {
404 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
405 where
406 S: serde::Serializer,
407 {
408 let value: u32 = (*self).into();
409 value.serialize(serializer)
410 }
411}
412
413impl<'de> Deserialize<'de> for Days {
414 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
415 where
416 D: serde::Deserializer<'de>,
417 {
418 let value = u32::deserialize(deserializer)?;
419 Ok(Days::from(value))
420 }
421}
422
423impl From<u32> for Days {
424 fn from(value: u32) -> Self {
425 match value {
426 1 => Days::One,
427 2 => Days::Two,
428 3 => Days::Three,
429 5 => Days::Five,
430 7 => Days::Seven,
431 10 => Days::Ten,
432 14 => Days::Fourteen,
433 _ => panic!("Invalid days {}", value),
434 }
435 }
436}
437
438impl Into<u32> for Days {
439 fn into(self) -> u32 {
440 match self {
441 Days::One => 1,
442 Days::Two => 2,
443 Days::Three => 3,
444 Days::Five => 5,
445 Days::Seven => 7,
446 Days::Ten => 10,
447 Days::Fourteen => 14,
448 }
449 }
450}