algorithm-problem-client 0.4.0

HTTP Client for algorithm problems
Documentation
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);
    }
}