gitlab_time_report/fetch_api/
fetch_options.rs

1//! Data structure with settings for accessing the GitLab API.
2
3use thiserror::Error;
4
5/// Contains all information needed for a call to the GitLab API.
6/// Create a new instance with [`FetchOptions::new()`].
7#[derive(Default, Debug, PartialEq, Clone)]
8pub struct FetchOptions {
9    /// The protocol the GitLab server should be contacted with.
10    pub(super) protocol: String,
11    /// The host part of the project URL, i.e. `gitlab.ost.ch`
12    pub(super) host: String,
13    /// The path part of the project URL, i.e. `gitlab-time-report/gitlab-time-report`
14    pub(super) path: String,
15    /// A GitLab access token. Required if the project visibility is set to "internal" or "private".
16    pub(super) token: Option<String>,
17}
18
19impl FetchOptions {
20    /// Creates a new `FetchOption` instance. Takes the full URL to a GitLab repository and a
21    /// access token, if needed.
22    /// ```
23    /// # use gitlab_time_report::FetchOptions;
24    /// let access_token = "MyAccessToken".to_string();
25    /// let options_result = FetchOptions::new("https://gitlab.com/gitlab-org/gitlab", Some(access_token));
26    /// // Check for errors
27    /// let Ok(option) = options_result else {
28    ///     panic!("Error happened when creating FetchOption: {:?}", options_result.unwrap_err());
29    /// };
30    ///  println!("{:?}", option);
31    /// ```
32    ///
33    /// # Errors
34    /// The function returns an `Err` if the URL path component does not contain two `/`, i.e. `https://gitlab.com/gitlab-org`
35    pub fn new(url: &str, token: Option<String>) -> Result<Self, FetchOptionsError> {
36        let (protocol, host, path) =
37            Self::split_url(url).ok_or(FetchOptionsError::InvalidUrl(url.into()))?;
38        Ok(Self {
39            protocol,
40            host,
41            path,
42            token,
43        })
44    }
45
46    /// Takes a URL and splits it into protocol, host and path component.
47    fn split_url(full_url: &str) -> Option<(String, String, String)> {
48        // Get the protocol of the URL if specified. If not, use https.
49        let (protocol, rest) = full_url.split_once("://").unwrap_or(("https", full_url));
50
51        let url_parts = rest.splitn(2, '/').collect::<Vec<_>>();
52
53        // Check if the URL contains a host and a path and
54        // if the path contains both the user/group name and the project name.
55        if url_parts.len() != 2 || !url_parts[1].contains('/') {
56            return None;
57        }
58        Some((protocol.into(), url_parts[0].into(), url_parts[1].into()))
59    }
60}
61
62/// Possible errors when creating a [`FetchOptions`].
63#[derive(Debug, Error)]
64pub enum FetchOptionsError {
65    #[error("Invalid URL: {0}")]
66    InvalidUrl(String),
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn create_new_fetch_options() {
75        let url = "https://gitlab.ost.ch/gitlab-time-report/gitlab-time-report";
76        let token = "MyAccessToken".to_string();
77        let result = FetchOptions {
78            protocol: "https".into(),
79            host: "gitlab.ost.ch".into(),
80            path: "gitlab-time-report/gitlab-time-report".into(),
81            token: Some(token.clone()),
82        };
83
84        let output = FetchOptions::new(url, Some(token)).unwrap();
85        assert_eq!(output, result);
86    }
87
88    #[test]
89    fn create_new_fetch_options_without_protocol() {
90        let url = "gitlab.ost.ch/gitlab-time-report/gitlab-time-report";
91        let token = "MyAccessToken".to_string();
92        let result = FetchOptions {
93            protocol: "https".into(),
94            host: "gitlab.ost.ch".into(),
95            path: "gitlab-time-report/gitlab-time-report".into(),
96            token: Some(token.clone()),
97        };
98
99        let output = FetchOptions::new(url, Some(token)).unwrap();
100        assert_eq!(output, result);
101    }
102
103    #[test]
104    fn split_url_correctly() {
105        let input = "https://gitlab.ost.ch/gitlab-time-report/gitlab-time-report";
106        let result = (
107            "https".into(),
108            "gitlab.ost.ch".into(),
109            "gitlab-time-report/gitlab-time-report".into(),
110        );
111        let output = FetchOptions::split_url(input).unwrap();
112        assert_eq!(output, result);
113    }
114
115    #[test]
116    fn split_url_one_slash() {
117        let input = "https://gitlab.com/gitlab-org";
118        let output = FetchOptions::split_url(input);
119        assert!(output.is_none());
120    }
121}