Skip to main content

wekan_cli/client/cards/
custom_fields.rs

1use 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}