wekan_cli/client/cards/
custom_fields.rs1use reqwest::header::ACCEPT;
2use secrecy::{ExposeSecret, SecretString};
3use serde::{Deserialize, Serialize};
4
5use super::{
6 CardCustomField, CardDocument, protocol_error, require_non_empty, validate_card_document,
7};
8use crate::client::{
9 ClientError, WekanClient,
10 custom_fields::{custom_field_input_error, validate_custom_field_identifier},
11 transport::decode_success,
12};
13
14#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
15pub struct SetCardCustomFieldRequest {
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub value: Option<serde_json::Value>,
18}
19
20#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
21#[serde(deny_unknown_fields, rename_all = "camelCase")]
22pub struct SetCardCustomFieldResponse {
23 #[serde(rename = "_id")]
24 pub card_id: String,
25 pub custom_fields: Vec<CardCustomField>,
26}
27
28pub(crate) fn validate_custom_field_query_value(value: &str) -> Result<(), &'static str> {
29 match value {
30 "" => Err("the custom-field lookup value must not be empty"),
31 "." | ".." => Err(
32 "dot-only custom-field lookup values cannot be sent by the HTTP client; no request was sent",
33 ),
34 _ => Ok(()),
35 }
36}
37
38impl WekanClient {
39 pub async fn set_card_custom_field(
40 &self,
41 board: &str,
42 list: &str,
43 card: &str,
44 field: &str,
45 request: &SetCardCustomFieldRequest,
46 token: &SecretString,
47 ) -> Result<SetCardCustomFieldResponse, ClientError> {
48 for id in [board, list, card, field] {
49 validate_custom_field_identifier(id).map_err(custom_field_input_error)?;
50 }
51 let mut endpoint = self.card_endpoint(board, list, Some(card))?;
52 endpoint
53 .path_segments_mut()
54 .map_err(|()| protocol_error("could not add the custom-field selector".to_owned()))?
55 .extend(["customFields", field]);
56 let body = self
57 .execute_success_request(
58 self.http
59 .post(endpoint)
60 .header(ACCEPT, "application/json")
61 .bearer_auth(token.expose_secret())
62 .json(request),
63 )
64 .await?;
65 let response: SetCardCustomFieldResponse =
66 decode_success(&body, "card custom-field assignment", true)?;
67 if response.card_id != card {
68 return Err(protocol_error(
69 "Wekan acknowledged a different card than requested".to_owned(),
70 ));
71 }
72 require_non_empty(&response.card_id, "card custom-field assignment", "card id")?;
73 Ok(response)
74 }
75
76 pub async fn cards_by_custom_field(
77 &self,
78 board: &str,
79 field: &str,
80 value: &str,
81 token: &SecretString,
82 ) -> Result<Vec<CardDocument>, ClientError> {
83 for id in [board, field] {
84 validate_custom_field_identifier(id).map_err(custom_field_input_error)?;
85 }
86 validate_custom_field_query_value(value).map_err(|message| ClientError::Protocol {
87 diagnostic: None,
88 message: message.to_owned(),
89 success_status_received: false,
90 })?;
91 let mut endpoint = self.server().join("api/boards").map_err(|_| {
92 protocol_error("could not build the custom-field card query".to_owned())
93 })?;
94 endpoint
95 .path_segments_mut()
96 .map_err(|()| protocol_error("could not add custom-field query selectors".to_owned()))?
97 .extend([board, "cardsByCustomField", field, value]);
98 let body = self
99 .execute_success_request(
100 self.http
101 .get(endpoint)
102 .header(ACCEPT, "application/json")
103 .bearer_auth(token.expose_secret()),
104 )
105 .await?;
106 let cards: Vec<CardDocument> = decode_success(&body, "custom-field card collection", true)?;
107 cards.into_iter().map(validate_card_document).collect()
108 }
109}