leetcode_cli/cache/
parser.rs

1//! Sub-Module for parsing resp data
2use super::models::*;
3use serde_json::Value;
4
5/// problem parser
6pub fn problem(problems: &mut Vec<Problem>, v: Value) -> Option<()> {
7    let pairs = v.get("stat_status_pairs")?.as_array()?;
8    for p in pairs {
9        let stat = p.get("stat")?.as_object()?;
10        let total_acs = stat.get("total_acs")?.as_f64()? as f32;
11        let total_submitted = stat.get("total_submitted")?.as_f64()? as f32;
12
13        let fid_obj = stat.get("frontend_question_id")?;
14        let fid = match fid_obj.as_i64() {
15            // Handle on leetcode-com
16            Some(s) => s as i32,
17            // Handle on leetcode-cn
18            None => fid_obj
19                .as_str()?
20                .split(' ')
21                .next_back()?
22                .parse::<i32>()
23                .ok()?,
24        };
25
26        problems.push(Problem {
27            category: v.get("category_slug")?.as_str()?.to_string(),
28            fid,
29            id: stat.get("question_id")?.as_i64()? as i32,
30            level: p.get("difficulty")?.as_object()?.get("level")?.as_i64()? as i32,
31            locked: p.get("paid_only")?.as_bool()?,
32            name: stat.get("question__title")?.as_str()?.to_string(),
33            percent: total_acs / total_submitted * 100.0,
34            slug: stat.get("question__title_slug")?.as_str()?.to_string(),
35            starred: p.get("is_favor")?.as_bool()?,
36            status: p.get("status")?.as_str().unwrap_or("Null").to_string(),
37            desc: String::new(),
38        });
39    }
40
41    Some(())
42}
43
44/// desc parser
45pub fn desc(q: &mut Question, v: Value) -> Option<bool> {
46    /* None - parsing failed
47     * Some(false) - content was null (premium?)
48     * Some(true) - content was parsed
49     */
50    let o = &v
51        .as_object()?
52        .get("data")?
53        .as_object()?
54        .get("question")?
55        .as_object()?;
56
57    if *o.get("content")? == Value::Null {
58        return Some(false);
59    }
60
61    *q = Question {
62        content: o.get("content")?.as_str().unwrap_or("").to_string(),
63        stats: serde_json::from_str(o.get("stats")?.as_str()?).ok()?,
64        defs: serde_json::from_str(o.get("codeDefinition")?.as_str()?).ok()?,
65        case: o.get("sampleTestCase")?.as_str()?.to_string(),
66        all_cases: o
67            .get("exampleTestcases")
68            .unwrap_or(o.get("sampleTestCase")?) // soft fail to the sampleTestCase
69            .as_str()?
70            .to_string(),
71        metadata: serde_json::from_str(o.get("metaData")?.as_str()?).ok()?,
72        test: o.get("enableRunCode")?.as_bool()?,
73        t_content: o
74            .get("translatedContent")?
75            .as_str()
76            .unwrap_or("")
77            .to_string(),
78    };
79
80    Some(true)
81}
82
83/// tag parser
84pub fn tags(v: Value) -> Option<Vec<String>> {
85    trace!("Parse tags...");
86    let tag = v.as_object()?.get("data")?.as_object()?.get("topicTag")?;
87
88    if tag.is_null() {
89        return Some(vec![]);
90    }
91
92    let arr = tag.as_object()?.get("questions")?.as_array()?;
93
94    let mut res: Vec<String> = vec![];
95    for q in arr.iter() {
96        res.push(q.as_object()?.get("questionId")?.as_str()?.to_string())
97    }
98
99    Some(res)
100}
101
102/// daily parser
103pub fn daily(v: Value) -> Option<i32> {
104    trace!("Parse daily...");
105    let v_obj = v.as_object()?.get("data")?.as_object()?;
106    match v_obj.get("activeDailyCodingChallengeQuestion") {
107        // Handle on leetcode-com
108        Some(v) => v,
109        // Handle on leetcode-cn
110        None => v_obj.get("todayRecord")?.as_array()?.first()?,
111    }
112    .as_object()?
113    .get("question")?
114    .as_object()?
115    .get("questionFrontendId")?
116    .as_str()?
117    .parse()
118    .ok()
119}
120
121/// user parser
122pub fn user(v: Value) -> Option<Option<(String, bool)>> {
123    // None => error while parsing
124    // Some(None) => User not found
125    // Some("...") => username
126    let user = v.as_object()?.get("data")?.as_object()?.get("user")?;
127    if *user == Value::Null {
128        return Some(None);
129    }
130    let user = user.as_object()?;
131    Some(Some((
132        user.get("username")?.as_str()?.to_owned(),
133        user.get("isCurrentUserPremium")?.as_bool()?,
134    )))
135}
136
137pub use ss::ssr;
138/// string or squence
139mod ss {
140    use serde::{Deserialize, Deserializer, de};
141    use std::fmt;
142    use std::marker::PhantomData;
143
144    /// de Vec<String> from string or sequence
145    pub fn ssr<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
146    where
147        D: Deserializer<'de>,
148    {
149        struct StringOrVec(PhantomData<Vec<String>>);
150
151        impl<'de> de::Visitor<'de> for StringOrVec {
152            type Value = Vec<String>;
153
154            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
155                formatter.write_str("string or list of strings")
156            }
157
158            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
159            where
160                E: de::Error,
161            {
162                Ok(vec![value.to_owned()])
163            }
164
165            fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
166            where
167                S: de::SeqAccess<'de>,
168            {
169                Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))
170            }
171        }
172
173        deserializer.deserialize_any(StringOrVec(PhantomData))
174    }
175}