1use bytes::Bytes;
2
3use futures::AsyncBufReadExt;
4use futures::TryStreamExt;
5use futures::stream::StreamExt;
6
7use serde::de::DeserializeOwned;
8use tracing::debug;
9
10use crate::error::{Error, Result};
11
12#[derive(Debug, Clone)]
13pub struct LichessApi<HttpClient> {
14 pub client: HttpClient,
15 bearer_auth: Option<String>,
16}
17
18impl<HttpClient> LichessApi<HttpClient> {
19 pub fn new(client: HttpClient, auth_token: Option<String>) -> Self {
20 let bearer_auth = auth_token.map(|token| format!("Bearer {}", token));
21 Self {
22 client,
23 bearer_auth,
24 }
25 }
26
27 pub(crate) async fn expect_one_model<Model, G>(&self, stream: &mut G) -> Result<Model>
28 where
29 G: StreamExt<Item = Result<Model>> + std::marker::Unpin,
30 {
31 stream
32 .next()
33 .await
34 .ok_or(Error::Response("empty response stream".to_string()))?
35 }
36
37 pub(crate) async fn expect_empty<G>(&self, stream: &mut G) -> Result<()>
38 where
39 G: StreamExt<Item = Result<()>> + std::marker::Unpin,
40 {
41 if stream.next().await.is_some() {
42 Err(Error::Response(
43 "expected empty response stream".to_string(),
44 ))
45 } else {
46 Ok(())
47 }
48 }
49}
50
51impl LichessApi<reqwest::Client> {
52 pub(crate) async fn make_request<Model: DeserializeOwned>(
53 &self,
54 http_request: http::Request<Bytes>,
55 ) -> Result<impl StreamExt<Item = Result<Model>>> {
56 let stream =
57 self.make_request_as_raw_lines(http_request)
58 .await?
59 .map(|l| -> Result<Model> {
60 serde_json::from_str(&l?).map_err(crate::error::Error::Json)
61 });
62
63 Ok(stream)
64 }
65
66 pub(crate) async fn make_request_as_raw_lines(
67 &self,
68 mut http_request: http::Request<Bytes>,
69 ) -> Result<impl StreamExt<Item = Result<String>>> {
70 if let Some(auth) = &self.bearer_auth {
71 let mut auth_header = http::HeaderValue::from_str(auth)
72 .map_err(|e| Error::HttpRequestBuilder(http::Error::from(e)))?;
73 auth_header.set_sensitive(true);
75 http_request
76 .headers_mut()
77 .insert(http::header::AUTHORIZATION, auth_header);
78 };
79
80 let convert_err = |e: reqwest::Error| Error::Request(e.to_string());
81 let request = reqwest::Request::try_from(http_request).map_err(convert_err)?;
82 let body_text = if let Some(body) = request.body() {
83 match body.as_bytes() {
84 Some(bytes) => String::from_utf8_lossy(bytes).to_string(),
85 None => "<streaming body>".to_string(),
86 }
87 } else {
88 "<empty body>".to_string()
89 };
90 debug!(?request, body = %body_text, "sending");
91 let response = self.client.execute(request).await;
92 debug!(?response, "received");
93 let stream = response
94 .map_err(convert_err)?
95 .bytes_stream()
96 .map_err(futures::io::Error::other)
97 .into_async_read()
98 .lines()
99 .filter(|l| {
100 futures::future::ready(match l {
102 Ok(line) => !line.is_empty(),
103 Err(_) => true,
104 })
105 })
106 .map(|l| -> Result<String> {
107 let line = l?;
108 debug!(line, "model line");
109 if line.starts_with("<!DOCTYPE html>") {
110 return Err(crate::error::Error::PageNotFound());
111 }
112 if let Ok(error_value) = serde_json::from_str::<serde_json::Value>(&line)
115 && let Some(error_msg) = error_value.get("error").and_then(|e| e.as_str())
116 {
117 return Err(crate::error::Error::Response(error_msg.to_string()));
118 }
119 Ok(line)
120 });
121
122 Ok(stream)
123 }
124}