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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
pub mod cache;
pub mod errors;

use crate::errors::Error;
use codecov::{
    author::Author, branch_detail::BranchDetailAPIResponse, branches::BranchesAPIResponse,
    commits::CommitsAPIResponse, owner::Owner, repos::Repo, Client as CodecovClient,
};

/**
 * Client is a struct wrapping CodecovClient.
 */
pub struct Client {
    codecov_client: CodecovClient,
    cache_client: cache::Client,
}

/**
 * Client is a struct wrapping CodecovClient.
 * pub methods are same as CodecovClient.
 * https://docs.rs/codecov/latest/codecov/struct.Client.html
 */
impl Client {
    pub fn new_from_env() -> Result<Client, Error> {
        let cache_dir = Client::resolve_cache_dir_root();
        Ok(Client {
            codecov_client: CodecovClient::new_from_env()?,
            cache_client: cache::Client::new(cache_dir, "data.json".to_string()),
        })
    }

    fn resolve_cache_dir_root() -> std::path::PathBuf {
        match std::env::var("CODECOV_CACHE_DIR") {
            Ok(path) => std::path::PathBuf::from(path),
            Err(_) => Client::default_cache_dir_root(),
        }
    }

    fn default_cache_dir_root() -> std::path::PathBuf {
        let Some(mut path) = dirs::cache_dir() else {
            panic!("Unsupported platform");
        };
        path.push("rust-codecov-cache");
        path
    }

    pub fn new(token: String, cache_dir: std::path::PathBuf) -> Client {
        Client {
            codecov_client: CodecovClient::new(token),
            cache_client: cache::Client::new(cache_dir, "data.json".to_string()),
        }
    }

    /**
     * get_all_repos returns a list of all repos for a given owner.
     * /repos endpoint returns a list of repos for a given owner with pagination.
     * This function will make multiple requests to get all repos.
     */
    pub fn get_all_repos(&self, owner: &Owner) -> Result<Vec<Repo>, Error> {
        Ok(self.codecov_client.get_all_repos(owner)?)
    }

    /**
     * get_commits returns a list of commits for a given author.
     * https://docs.codecov.com/reference/repos_commits_list
     */
    pub fn get_commits(&self, author: &Author) -> Result<CommitsAPIResponse, Error> {
        Ok(self.codecov_client.get_commits(author)?)
    }

    /**
     * get_branches returns a list of branches for a given author.
     * https://docs.codecov.com/reference/repos_branches_list
     */
    pub fn get_branches(&self, author: &Author) -> Result<BranchesAPIResponse, Error> {
        Ok(self.codecov_client.get_branches(author)?)
    }

    /**
     * get_branch_detail returns a branch detail for a given author and branch name.
     * https://docs.codecov.com/reference/repos_branches_retrieve
     */
    pub fn get_branch_detail(
        &self,
        author: &Author,
        branch_name: &str,
    ) -> Result<BranchDetailAPIResponse, Error> {
        Ok(self.codecov_client.get_branch_detail(author, branch_name)?)
    }

    /**
     * get_branch_detail returns a branch detail for a given author and branch name.
     * https://docs.codecov.com/reference/repos_branches_retrieve
     */
    pub fn get_branch_detail_with_commit_id(
        &self,
        author: &Author,
        branch_name: &str,
        commit_id: &str,
    ) -> Result<BranchDetailAPIResponse, Error> {
        let cache_key = &[
            &author.service,
            &author.username,
            &author.name,
            branch_name,
            commit_id,
        ];
        // Use cache if exists
        if let Ok(data) = self.cache_client.load(cache_key) {
            if let Ok(value) = serde_json::from_slice(&data) {
                if let Ok(branch_detail) = serde_json::from_value(value) {
                    return Ok(branch_detail);
                }
            }
        }
        // If cache does not exist, fetch from Codecov API
        let retrieved = self.codecov_client.get_branch_detail(author, branch_name)?;
        // Save to cache
        if let BranchDetailAPIResponse::Success(detail) = &retrieved {
            if let Ok(data) = serde_json::to_vec(&detail) {
                let cache_key = &[
                    &author.service,
                    &author.username,
                    &author.name,
                    branch_name,
                    &detail.head_commit.commitid,
                ];
                if let Err(err) = self.cache_client.save(cache_key, &data) {
                    println!("Failed to save cache: {:?}", err);
                }
            }
            return Ok(retrieved);
        }
        Ok(retrieved)
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fetch_and_cache() {
        let client = Client::new_from_env().unwrap(); // Read CODECOV_OWNER_TOKEN from environment variable
        let owner = Owner::new("github", "kitsuyui");
        let author = owner.new_author("rust-codecov");

        let detail = client.get_branch_detail(&author, "main").unwrap();

        let BranchDetailAPIResponse::Success(detail) = detail else {
            panic!("Unexpected response");
        };
        let commit_id = detail.head_commit.commitid.to_string();

        client
            .get_branch_detail_with_commit_id(&author, "main", &commit_id)
            .unwrap();
        assert!(client.cache_client.has(&[
            "github",
            "kitsuyui",
            "rust-codecov",
            "main",
            &commit_id
        ]));

        // Check cache dir exists
        let mut cache_dir = dirs::cache_dir().unwrap();
        cache_dir.extend(&[
            "rust-codecov-cache",
            "github",
            "kitsuyui",
            "rust-codecov",
            "main",
            &commit_id,
        ]);
        assert!(cache_dir.exists());
    }
}