1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use super::*;

use crate::util::HttpClient;
use crate::Result;
use serde::Deserialize;

const BASE_URL: &str = "https://codeforces.com/api";

pub struct CodeforcesClient {
    http_client: HttpClient,
}

impl Default for CodeforcesClient {
    fn default() -> Self {
        Self {
            http_client: HttpClient::default(),
        }
    }
}

impl CodeforcesClient {
    pub fn fetch_problems(&self) -> Result<Vec<CodeforcesProblem>> {
        let url = format!("{}/problemset.problems", BASE_URL);
        self.http_client
            .get_json::<CodeforcesProblemResponse>(&url)
            .map(|response| response.result.problems)
    }

    pub fn fetch_submissions(
        &self,
        user_id: &str,
        from: u32,
        count: u32,
    ) -> Result<Vec<CodeforcesSubmission>> {
        let url = format!(
            "{base_url}/user.status?handle={user_id}&from={from}&count={count}",
            base_url = BASE_URL,
            user_id = user_id,
            from = from,
            count = count,
        );
        self.http_client
            .get_json::<SubmissionResult>(&url)
            .map(|response| {
                response
                    .result
                    .into_iter()
                    .flat_map(|submission| submission.convert())
                    .collect()
            })
    }
}

#[derive(Deserialize)]
struct CodeforcesProblemResponse {
    result: CodeforcesProblemResponseResult,
}

#[derive(Deserialize)]
struct CodeforcesProblemResponseResult {
    problems: Vec<CodeforcesProblem>,
}

#[derive(Deserialize)]
struct SubmissionResult {
    result: Vec<Submission>,
}
#[derive(Deserialize)]
struct Submission {
    id: u64,
    problem: CodeforcesProblem,
    author: Author,

    #[serde(rename = "programmingLanguage")]
    language: String,

    verdict: String,
}
#[derive(Deserialize)]
struct Author {
    members: Vec<Member>,
}
#[derive(Deserialize)]
struct Member {
    handle: String,
}

impl Submission {
    fn convert(self) -> Option<CodeforcesSubmission> {
        let id = self.id;
        let contest_id = self.problem.contest_id;
        let problem_index = self.problem.index;
        let verdict = self.verdict;
        let language = self.language;
        let user_id = self.author.members.into_iter().next()?.handle;
        Some(CodeforcesSubmission {
            id,
            contest_id,
            problem_index,
            user_id,
            verdict,
            language,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fetch_problems() {
        let client = CodeforcesClient::default();
        assert!(client.fetch_problems().unwrap().len() > 0);
    }

    #[test]
    fn test_fetch_submissions() {
        let client = CodeforcesClient::default();
        let submissions = client.fetch_submissions("kenkoooo", 1, 10).unwrap();
        assert_eq!(submissions.len(), 10);
    }
}