leetcode_core/graphql/
client.rs

1use crate::errors::{AppResult, LcAppError};
2use crate::get_client;
3use async_trait::async_trait;
4use lru::LruCache;
5use serde::{de::DeserializeOwned, Serialize};
6use serde_json::{json, Value};
7use std::collections::hash_map::DefaultHasher;
8use std::hash::Hash;
9use std::hash::Hasher;
10use std::sync::RwLock;
11use std::{num::NonZeroUsize, sync::OnceLock};
12
13pub static CACHE: OnceLock<RwLock<LruCache<u64, String>>> = OnceLock::new();
14
15fn get_cache<'a>() -> &'a RwLock<LruCache<u64, String>> {
16    CACHE.get_or_init(|| RwLock::new(LruCache::new(NonZeroUsize::new(20).unwrap())))
17}
18
19fn hash_string(input: &str) -> u64 {
20    let mut hasher = DefaultHasher::new();
21    input.hash(&mut hasher);
22    hasher.finish()
23}
24
25#[async_trait]
26pub trait GQLLeetcodeRequest: Serialize + Sync {
27    type T: DeserializeOwned;
28
29    fn get_body(&self) -> Value {
30        json!(self)
31    }
32
33    fn is_post(&self) -> bool {
34        true
35    }
36
37    /// Default graphql endpoint
38    fn get_endpoint(&self) -> String {
39        "https://leetcode.com/graphql".to_string()
40    }
41
42    fn use_cache(&self) -> bool {
43        false
44    }
45
46    fn get_query_hash(&self) -> u64 {
47        hash_string(format!("{}{}", self.get_endpoint(), self.get_body()).as_str())
48    }
49
50    async fn send(&self) -> AppResult<Self::T> {
51        if self.use_cache() {
52            let mut c = get_cache().write().unwrap();
53            if let Some(value) = c.get(&self.get_query_hash()) {
54                return Ok(serde_json::from_str(value.as_str())?);
55            };
56        }
57
58        let request = if self.is_post() {
59            get_client()
60                .post(self.get_endpoint())
61                .json(&self.get_body())
62        } else {
63            get_client().get(self.get_endpoint())
64        };
65        let response = request
66            .header("Content-Type", "application/json")
67            .send()
68            .await?;
69
70        if response.status().as_u16() == 403 {
71            return Err(LcAppError::CookiesExpiredError);
72        } else if response.status().as_u16() != 200 {
73            return Err(LcAppError::StatusCodeError {
74                code: response.status().to_string(),
75                contents: response.text().await?,
76            });
77        }
78        let result = response.text().await?;
79
80        if self.use_cache() {
81            let mut c = get_cache().write().unwrap();
82            c.put(self.get_query_hash(), result.clone());
83        }
84        match serde_json::from_str(result.as_str()) {
85            Ok(parsed_message) => Ok(parsed_message),
86            Err(e) => {
87                log::debug!("{}\n{}", &e, result.as_str());
88                Err(LcAppError::DeserializeError(e))
89            }
90        }
91    }
92}