tfc_toolset/
tag.rs

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        &params,
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    // Need to check pagination
104    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                    // Get the next page and merge the result
120                    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}