Skip to main content

wekan_cli/client/
checklist_items.rs

1use reqwest::header::ACCEPT;
2use secrecy::{ExposeSecret, SecretString};
3use serde::{Deserialize, Serialize};
4use serde_json::Number;
5
6use super::{
7    ClientError, WekanClient,
8    checklists::{protocol_error, require_date, require_id},
9    transport::decode_success,
10};
11
12#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
13#[serde(
14    deny_unknown_fields,
15    rename_all(deserialize = "camelCase", serialize = "snake_case")
16)]
17pub struct ChecklistItemDocument {
18    #[serde(default, deserialize_with = "super::decoding::optional_non_null")]
19    pub assignee_id: Option<String>,
20    #[serde(default, deserialize_with = "super::decoding::optional_non_null")]
21    pub linked_card_id: Option<String>,
22    pub due_at: Option<String>,
23    #[serde(rename(deserialize = "_id", serialize = "item_id"))]
24    pub item_id: String,
25    pub title: String,
26    pub sort: Number,
27    pub is_finished: bool,
28    pub checklist_id: String,
29    pub card_id: String,
30    /// Denormalized metadata; v12.57 REST card moves can leave the source board ID.
31    pub board_id: Option<String>,
32    pub user_id: Option<String>,
33    pub created_at: Option<String>,
34    pub modified_at: String,
35}
36
37#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
38#[serde(untagged)]
39pub enum ChecklistItemSortInput {
40    Number(Number),
41    String(String),
42    Null,
43}
44
45#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
46#[serde(untagged)]
47pub enum ChecklistItemFinishedInput {
48    Boolean(bool),
49    String(String),
50}
51
52#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
53pub struct CreateChecklistItemRequest {
54    #[serde(rename = "dueAt", skip_serializing_if = "Option::is_none")]
55    pub due_at: Option<String>,
56    pub title: String,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub sort: Option<ChecklistItemSortInput>,
59}
60
61#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
62#[serde(rename_all = "camelCase")]
63pub struct UpdateChecklistItemRequest {
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub due_at: Option<Option<String>>,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub title: Option<String>,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub is_finished: Option<ChecklistItemFinishedInput>,
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct CreateChecklistItemResult {
74    pub item_id: String,
75}
76
77#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct UpdateChecklistItemResult {
79    pub item_id: String,
80}
81
82#[derive(Clone, Debug, Eq, PartialEq)]
83pub struct DeleteChecklistItemResult {
84    pub item_id: String,
85}
86
87#[derive(Deserialize)]
88#[serde(deny_unknown_fields)]
89struct IdResponse {
90    #[serde(rename = "_id")]
91    id: String,
92}
93
94impl WekanClient {
95    pub async fn checklist_item(
96        &self,
97        board_id: &str,
98        card_id: &str,
99        checklist_id: &str,
100        item_id: &str,
101        token: &SecretString,
102    ) -> Result<ChecklistItemDocument, ClientError> {
103        let body = self
104            .execute_success_request(
105                self.http
106                    .get(self.checklist_item_endpoint(
107                        board_id,
108                        card_id,
109                        checklist_id,
110                        Some(item_id),
111                    )?)
112                    .header(ACCEPT, "application/json")
113                    .bearer_auth(token.expose_secret()),
114            )
115            .await?;
116        let item: ChecklistItemDocument = decode_success(&body, "checklist item", true)?;
117        for (value, field) in [
118            (&item.item_id, "item id"),
119            (&item.checklist_id, "checklist id"),
120            (&item.card_id, "card id"),
121        ] {
122            require_id(value, field)?;
123        }
124        if let Some(id) = &item.board_id {
125            require_id(id, "board id")?;
126        }
127        if let Some(id) = &item.user_id {
128            require_id(id, "creator user id")?;
129        }
130        if item.item_id != item_id || item.checklist_id != checklist_id || item.card_id != card_id {
131            return Err(protocol_error(
132                "the checklist item response identifiers did not match the requested scope",
133            ));
134        }
135        require_date(&item.modified_at)?;
136        if let Some(date) = &item.due_at {
137            require_date(date)?;
138        }
139        if let Some(date) = &item.created_at {
140            require_date(date)?;
141        }
142        Ok(item)
143    }
144
145    pub async fn create_checklist_item(
146        &self,
147        board_id: &str,
148        card_id: &str,
149        checklist_id: &str,
150        request: &CreateChecklistItemRequest,
151        token: &SecretString,
152    ) -> Result<CreateChecklistItemResult, ClientError> {
153        let body = self
154            .execute_success_request(
155                self.http
156                    .post(self.checklist_item_endpoint(board_id, card_id, checklist_id, None)?)
157                    .header(ACCEPT, "application/json")
158                    .bearer_auth(token.expose_secret())
159                    .json(request),
160            )
161            .await?;
162        let response: IdResponse = decode_success(&body, "checklist item creation", true)?;
163        require_id(&response.id, "created item id")?;
164        Ok(CreateChecklistItemResult {
165            item_id: response.id,
166        })
167    }
168
169    pub async fn update_checklist_item(
170        &self,
171        board_id: &str,
172        card_id: &str,
173        checklist_id: &str,
174        item_id: &str,
175        request: &UpdateChecklistItemRequest,
176        token: &SecretString,
177    ) -> Result<UpdateChecklistItemResult, ClientError> {
178        let body = self
179            .execute_success_request(
180                self.http
181                    .put(self.checklist_item_endpoint(
182                        board_id,
183                        card_id,
184                        checklist_id,
185                        Some(item_id),
186                    )?)
187                    .header(ACCEPT, "application/json")
188                    .bearer_auth(token.expose_secret())
189                    .json(request),
190            )
191            .await?;
192        let response: IdResponse = decode_success(&body, "checklist item update", true)?;
193        require_id(&response.id, "updated item id")?;
194        if response.id != item_id {
195            return Err(protocol_error(
196                "Wekan reported a different updated checklist item ID than requested",
197            ));
198        }
199        Ok(UpdateChecklistItemResult {
200            item_id: response.id,
201        })
202    }
203
204    pub async fn delete_checklist_item(
205        &self,
206        board_id: &str,
207        card_id: &str,
208        checklist_id: &str,
209        item_id: &str,
210        token: &SecretString,
211    ) -> Result<DeleteChecklistItemResult, ClientError> {
212        let body = self
213            .execute_success_request(
214                self.http
215                    .delete(self.checklist_item_endpoint(
216                        board_id,
217                        card_id,
218                        checklist_id,
219                        Some(item_id),
220                    )?)
221                    .header(ACCEPT, "application/json")
222                    .bearer_auth(token.expose_secret()),
223            )
224            .await?;
225        let response: IdResponse = decode_success(&body, "checklist item deletion", true)?;
226        require_id(&response.id, "deleted item id")?;
227        if response.id != item_id {
228            return Err(protocol_error(
229                "Wekan reported a different deleted checklist item ID than requested",
230            ));
231        }
232        Ok(DeleteChecklistItemResult {
233            item_id: response.id,
234        })
235    }
236
237    fn checklist_item_endpoint(
238        &self,
239        board_id: &str,
240        card_id: &str,
241        checklist_id: &str,
242        item_id: Option<&str>,
243    ) -> Result<reqwest::Url, ClientError> {
244        let mut endpoint = self.checklist_endpoint(board_id, card_id, Some(checklist_id))?;
245        let mut segments = endpoint
246            .path_segments_mut()
247            .map_err(|()| ClientError::Protocol {
248                diagnostic: None,
249                message: "could not add identifiers to the checklist item endpoint".to_owned(),
250                success_status_received: false,
251            })?;
252        segments.push("items");
253        if let Some(id) = item_id {
254            segments.push(id);
255        }
256        drop(segments);
257        Ok(endpoint)
258    }
259}