gitlab_butler_lib/
issues.rs

1use url::percent_encoding::{utf8_percent_encode, PATH_SEGMENT_ENCODE_SET};
2
3use anyhow::Result;
4use log::{debug, error};
5
6use crate::client::Client;
7use crate::entities::{CreateIssue, Issue, IssueState, Project, ProjectMilestone};
8
9impl Issue {
10    pub fn create(client: &Client, issue: &CreateIssue) -> Result<Issue> {
11        //! `POST /projects/:id/issues`
12        let api_path = {
13            let project_name_or_id =
14                utf8_percent_encode(&issue.project_id, PATH_SEGMENT_ENCODE_SET).to_string();
15            format!("api/v4/projects/{}/issues", project_name_or_id)
16        };
17        let response = client.post_json(&api_path, &issue)?;
18        let de = &mut serde_json::Deserializer::from_reader(response);
19        let result: Result<Issue, _> = serde_path_to_error::deserialize(de);
20        match result {
21            Ok(issue) => Ok(issue),
22            Err(err) => {
23                dbg!(&api_path);
24                dbg!(err.path().to_string());
25                dbg!(client.get(&api_path)?.text()?);
26                Err(err.into_inner().into())
27            }
28        }
29    }
30    pub fn issue_from_branch(
31        client: &Client,
32        project: &str,
33        branch: &str,
34    ) -> Option<Result<Issue>> {
35        let prefix = branch.split('-').next();
36        let issue_iid = prefix.and_then(|iid| iid.parse::<usize>().ok());
37        issue_iid.map(|issue_iid| Issue::single(&client, &project, issue_iid))
38    }
39    pub fn single(client: &Client, project: &str, issue_iid: usize) -> Result<Issue> {
40        //! `GET /projects/:id/issues/:issue_iid`
41        let api_path = {
42            let project_name_or_id =
43                utf8_percent_encode(project, PATH_SEGMENT_ENCODE_SET).to_string();
44            format!(
45                "api/v4/projects/{}/issues/{}",
46                project_name_or_id, issue_iid
47            )
48        };
49        let response = client.get(&api_path)?;
50        let de = &mut serde_json::Deserializer::from_reader(response);
51        let result: Result<Issue, _> = serde_path_to_error::deserialize(de);
52        match result {
53            Ok(issue) => Ok(issue),
54            Err(err) => {
55                error!("Path: {}", err.path().to_string());
56                error!("Body: {:?}", client.get(&api_path)?.bytes()?);
57                Err(err.into_inner().into())
58            }
59        }
60    }
61    pub fn list(
62        client: &Client,
63        project: &Option<String>,
64        state: &IssueState,
65        labels: &Option<String>,
66        milestone: &Option<String>,
67        limit: usize,
68    ) -> Result<Vec<Issue>> {
69        let prefix = if let Some(aproject) = project {
70            let project_name_or_id =
71                utf8_percent_encode(aproject, PATH_SEGMENT_ENCODE_SET).to_string();
72            format!("api/v4/projects/{}", project_name_or_id)
73        } else {
74            "api/v4".to_string()
75        };
76        let scope = {
77            if project.is_some() {
78                "all"
79            } else {
80                "assigned_to_me" // avoid 500 error for too broad queries
81            }
82        };
83        let mut api_path = format!("{}/issues?state={}&scope={}", prefix, state.name(), scope);
84        if let Some(labels) = labels {
85            api_path = format!("{}&labels={}", api_path, labels);
86        }
87        if let Some(milestone) = milestone {
88            api_path = format!("{}&milestone={}", api_path, milestone);
89        }
90        debug!("Ready to query {:?}", api_path);
91        let mut all_issues: Vec<Issue> = vec![];
92        for issues in client.get_paginated(&api_path) {
93            let issues = issues?;
94            let de = &mut serde_json::Deserializer::from_reader(issues);
95            let result: Result<Vec<Issue>, _> = serde_path_to_error::deserialize(de);
96            let issues = match result {
97                Ok(issues) => issues,
98                Err(err) => {
99                    dbg!(&api_path);
100                    dbg!(err.path().to_string());
101                    dbg!(client.get(&api_path)?.text()?);
102                    return Err(err.into_inner().into());
103                }
104            };
105            all_issues.extend(issues);
106            if all_issues.len() >= limit {
107                break;
108            }
109        }
110        Ok(all_issues)
111    }
112}
113
114impl ProjectMilestone {
115    pub fn in_project_by_title(
116        client: &Client,
117        project: &str,
118        title: &str,
119    ) -> Result<Option<ProjectMilestone>> {
120        // The API returns only a single item or nothing.
121        ProjectMilestone::_in_project_by_title(client, project, Some(title)).map(|mut m| m.pop())
122    }
123    pub fn in_project(client: &Client, project: &str) -> Result<Vec<ProjectMilestone>> {
124        // The API returns only a single item or nothing.
125        ProjectMilestone::_in_project_by_title(client, project, None)
126    }
127    fn _in_project_by_title(
128        client: &Client,
129        project: &str,
130        title: Option<&str>,
131    ) -> Result<Vec<ProjectMilestone>> {
132        let prefix = {
133            let project_name_or_id =
134                utf8_percent_encode(project, PATH_SEGMENT_ENCODE_SET).to_string();
135            format!("api/v4/projects/{}", project_name_or_id)
136        };
137        let mut api_path = format!(
138            "{}/milestones?state=active&include_parent_milestones=true",
139            prefix
140        );
141        if let Some(title) = title {
142            api_path = format!("{}&title={}", api_path, title);
143        }
144        debug!("Ready to query {:?}", api_path);
145        let response = client.get(&api_path)?;
146        let de = &mut serde_json::Deserializer::from_reader(response);
147        let result: Result<Vec<ProjectMilestone>, _> = serde_path_to_error::deserialize(de);
148        let milestones = match result {
149            Ok(milestones) => milestones,
150            Err(err) => {
151                dbg!(&api_path);
152                dbg!(err.path().to_string());
153                dbg!(client.get(&api_path)?.text()?);
154                return Err(err.into_inner().into());
155            }
156        };
157        Ok(milestones)
158    }
159}
160
161impl Project {
162    pub fn list(client: &Client, search: &Option<String>, limit: usize) -> Result<Vec<Project>> {
163        let mut api_path = "api/v4/projects?simple=true&oder_by=id".to_string();
164        if let Some(search) = search {
165            api_path = format!("{}&search={}", api_path, search);
166        }
167        debug!("Ready to query {:?}", api_path);
168        let mut all_projects = vec![];
169        for projects in client.get_paginated(&api_path) {
170            let response = projects?;
171            let de = &mut serde_json::Deserializer::from_reader(response);
172            let result: Result<Vec<Project>, _> = serde_path_to_error::deserialize(de);
173            let projects = match result {
174                Ok(projects) => projects,
175                Err(err) => {
176                    dbg!(&api_path);
177                    dbg!(err.path().to_string());
178                    return Err(err.into_inner().into());
179                }
180            };
181            all_projects.extend(projects);
182            if all_projects.len() >= limit {
183                break;
184            }
185        }
186        Ok(all_projects)
187    }
188    pub fn single(client: &Client, name_or_id: &str) -> Result<Project> {
189        let api_path = {
190            let name_or_id = utf8_percent_encode(name_or_id, PATH_SEGMENT_ENCODE_SET).to_string();
191            format!("api/v4/projects/{}", name_or_id)
192        };
193        let response = client.get(&api_path)?;
194        let de = &mut serde_json::Deserializer::from_reader(response);
195        let result: Result<Project, _> = serde_path_to_error::deserialize(de);
196        match result {
197            Ok(project) => Ok(project),
198            Err(err) => {
199                dbg!(&api_path);
200                dbg!(err.path().to_string());
201                dbg!(client.get(&api_path)?.text()?);
202                Err(err.into_inner().into())
203            }
204        }
205    }
206}