gitlab_time_report/fetch_api/
http_requests.rs

1//! This module is an abstraction of the `reqwest` API so dependency injection can be used for
2//! unit and integration tests.
3
4// Exclude file from testing, as we would be testing the library implementation.
5#![cfg(not(tarpaulin_include))]
6
7use super::fetch_options::FetchOptions;
8#[cfg(test)]
9use mockall::automock;
10use reqwest::StatusCode;
11use reqwest::blocking::Client;
12use thiserror::Error;
13
14/// Contains methods to abstract HTTP calls.
15#[cfg_attr(test, automock)]
16pub(super) trait HttpFetcher {
17    /// Send a HTTP POST request with JSON as payload.
18    fn http_post_request(
19        &self,
20        url: &str,
21        payload: serde_json::Value,
22        options: &FetchOptions,
23    ) -> Result<String, NetworkError>;
24}
25
26impl HttpFetcher for Client {
27    fn http_post_request(
28        &self,
29        url: &str,
30        payload: serde_json::Value,
31        options: &FetchOptions,
32    ) -> Result<String, NetworkError> {
33        let mut request = self.post(url).json(&payload);
34
35        // Add the access token to the query if specified
36        if let Some(token) = &options.token {
37            request = request.bearer_auth(token);
38        }
39
40        Ok(request.send()?.error_for_status()?.text()?)
41    }
42}
43
44/// A generic error that failures in the library can be transformed into.
45/// Does still use [`StatusCode`] as return type for error handling.
46#[derive(Debug, Clone, Error)]
47pub enum NetworkError {
48    #[error("HTTP error: {0}")]
49    StatusCode(StatusCode),
50    #[error("Connection to {0} failed")]
51    Connection(String),
52}
53
54/// Implements the conversion from [`reqwest::Error`] into [`NetworkError`].
55impl From<reqwest::Error> for NetworkError {
56    fn from(e: reqwest::Error) -> Self {
57        if e.is_connect() {
58            return Self::Connection(
59                e.url()
60                    .expect("Should have a connection URL here")
61                    .to_string(),
62            );
63        }
64        let status = e.status().expect("Should have a status code here");
65        Self::StatusCode(status)
66    }
67}