1use std::vec;
2
3use crate::{
4 build_request,
5 error::{surf_to_tool_error, ToolError},
6 set_page_number,
7 settings::Core,
8 workspace, Meta, BASE_URL,
9};
10
11use log::{error, info};
12use serde::{Deserialize, Serialize};
13use serde_json::json;
14use surf::{http::Method, Client};
15use url::Url;
16
17#[derive(Clone, Debug, Deserialize, Serialize)]
18pub struct Attributes {
19 pub name: String,
20}
21
22#[derive(Clone, Debug, Deserialize, Serialize)]
23pub struct Tag {
24 #[serde(rename = "type")]
25 pub relationship_type: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub id: Option<String>,
28 pub attributes: Attributes,
29}
30
31impl Tag {
32 pub fn new(name: String) -> Self {
33 Tag {
34 relationship_type: "tags".to_string(),
35 id: None,
36 attributes: Attributes { name },
37 }
38 }
39}
40
41impl From<String> for Tag {
42 fn from(name: String) -> Self {
43 Tag::new(name)
44 }
45}
46
47#[derive(Clone, Debug, Serialize, Deserialize)]
48pub struct Tags {
49 pub data: Vec<Tag>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub meta: Option<Meta>,
52}
53
54impl Tags {
55 pub fn new(tags: Vec<Tag>) -> Self {
56 Tags { data: tags, meta: None }
57 }
58}
59
60pub async fn list(
61 workspace_id: &str,
62 config: &Core,
63 client: Client,
64) -> Result<Tags, ToolError> {
65 info!(
66 "Retrieving the initial list of tags for workspace {}.",
67 workspace_id
68 );
69 let params = vec![
70 ("page[size]", config.pagination.page_size.clone()),
71 ("page[number]", config.pagination.start_page.clone()),
72 ];
73 let url = Url::parse_with_params(
74 &format!("{}/workspaces/{}/relationships/tags", BASE_URL, workspace_id),
75 ¶ms,
76 )?;
77 let req = build_request(Method::Get, url.clone(), config, None);
78 let mut tag_list: Tags = match client.send(req).await {
79 Ok(mut res) => {
80 if res.status().is_success() {
81 info!("Tags for workspace {} retrieved.", workspace_id);
82 match res.body_json().await {
83 Ok(t) => t,
84 Err(e) => {
85 error!("{:#?}", e);
86 return Err(ToolError::General(anyhow::anyhow!(e)));
87 }
88 }
89 } else {
90 error!(
91 "Failed to retrieve tags for workspace {}.",
92 workspace_id
93 );
94 let error =
95 res.body_string().await.map_err(|e| e.into_inner())?;
96 return Err(ToolError::General(anyhow::anyhow!(error)));
97 }
98 }
99 Err(e) => {
100 return Err(ToolError::General(e.into_inner()));
101 }
102 };
103 if let Some(meta) = tag_list.meta.clone() {
105 let max_depth = config.pagination.max_depth.parse::<u32>()?;
106 if max_depth > 1 || max_depth == 0 {
107 let current_depth: u32 = 1;
108 if let Some(next_page) = meta.pagination.next_page {
109 if max_depth == 0 || current_depth < max_depth {
110 let num_pages: u32 = if max_depth
111 >= meta.pagination.total_pages
112 || max_depth == 0
113 {
114 meta.pagination.total_pages
115 } else {
116 max_depth
117 };
118
119 for n in next_page..=num_pages {
121 let u = url.clone();
122 info!("Retrieving tags page {}.", &n);
123 let u = match set_page_number(n, u) {
124 Some(u) => u,
125 None => {
126 error!("Failed to set page number.");
127 return Err(ToolError::Pagination(
128 "Failed to set page number.".to_string(),
129 ));
130 }
131 };
132 let req =
133 build_request(Method::Get, u.clone(), config, None);
134 let mut response = client
135 .send(req)
136 .await
137 .map_err(surf_to_tool_error)?;
138 if response.status().is_success() {
139 let tag_pages: Result<Tags, ToolError> = response
140 .body_json()
141 .await
142 .map_err(surf_to_tool_error);
143 match tag_pages {
144 Ok(mut t) => {
145 tag_list.data.append(&mut t.data);
146 }
147 Err(e) => {
148 error!("{:#?}", e);
149 return Err(e);
150 }
151 }
152 }
153 }
154 }
155 }
156 }
157 }
158 info!("Finished retrieving tags.");
159 Ok(tag_list)
160}
161
162pub async fn list_by_name(
163 workspace_name: &str,
164 config: &Core,
165 client: Client,
166) -> Result<Tags, ToolError> {
167 let workspace =
168 workspace::show_by_name(workspace_name, config, client.clone()).await?;
169 list(&workspace.id, config, client).await
170}
171
172pub async fn add(
173 workspace_id: &str,
174 tags: Vec<String>,
175 config: &Core,
176 client: Client,
177) -> Result<(), ToolError> {
178 info!("Tagging workspace {}.", workspace_id);
179 let url = Url::parse(&format!(
180 "{}/workspaces/{}/relationships/tags",
181 BASE_URL, workspace_id
182 ))?;
183 let tags = tags.into_iter().map(Tag::from).collect::<Vec<Tag>>();
184 let req =
185 build_request(Method::Post, url, config, Some(json!(Tags::new(tags))));
186 match client.send(req).await {
187 Ok(mut r) => {
188 if r.status().is_success() {
189 info!("Workspace {} tagged.", workspace_id);
190 Ok(())
191 } else {
192 error!("Failed to tag workspace {}.", workspace_id);
193 let error =
194 r.body_string().await.map_err(|e| e.into_inner())?;
195 Err(ToolError::General(anyhow::anyhow!(error)))
196 }
197 }
198 Err(e) => Err(ToolError::General(e.into_inner())),
199 }
200}
201
202pub async fn add_by_name(
203 workspace_name: &str,
204 tags: Vec<String>,
205 config: &Core,
206 client: Client,
207) -> Result<(), ToolError> {
208 let workspace =
209 workspace::show_by_name(workspace_name, config, client.clone()).await?;
210 add(&workspace.id, tags, config, client).await
211}
212
213pub async fn remove(
214 workspace_id: &str,
215 tags: Vec<String>,
216 config: &Core,
217 client: Client,
218) -> Result<(), ToolError> {
219 info!("Removing tags from workspace {}.", workspace_id);
220 let url = Url::parse(&format!(
221 "{}/workspaces/{}/relationships/tags",
222 BASE_URL, workspace_id
223 ))?;
224 let tags = tags.into_iter().map(Tag::from).collect::<Vec<Tag>>();
225 let req = build_request(
226 Method::Delete,
227 url,
228 config,
229 Some(json!(Tags::new(tags))),
230 );
231 match client.send(req).await {
232 Ok(mut r) => {
233 if r.status().is_success() {
234 info!("Tags removed from workspace {}.", workspace_id);
235 Ok(())
236 } else {
237 error!(
238 "Failed to remove tags from workspace {}.",
239 workspace_id
240 );
241 let error =
242 r.body_string().await.map_err(|e| e.into_inner())?;
243 Err(ToolError::General(anyhow::anyhow!(error)))
244 }
245 }
246 Err(e) => Err(ToolError::General(e.into_inner())),
247 }
248}
249
250pub async fn remove_by_name(
251 workspace_name: &str,
252 tags: Vec<String>,
253 config: &Core,
254 client: Client,
255) -> Result<(), ToolError> {
256 let workspace =
257 workspace::show_by_name(workspace_name, config, client.clone()).await?;
258 remove(&workspace.id, tags, config, client).await
259}