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