Skip to main content

wekan_cli/client/cards/
bot_runs.rs

1use super::require_non_empty;
2use crate::client::{ClientError, WekanClient, transport::decode_success};
3use reqwest::header::ACCEPT;
4use secrecy::{ExposeSecret, SecretString};
5use serde::{Deserialize, Serialize};
6use serde_json::Number;
7
8#[derive(Debug, Deserialize, Serialize)]
9#[serde(deny_unknown_fields, rename_all = "camelCase")]
10pub struct BotRunUpdate {
11    #[serde(
12        default,
13        deserialize_with = "crate::client::decoding::optional_non_null",
14        skip_serializing_if = "Option::is_none"
15    )]
16    pub status: Option<BotRunStatus>,
17    #[serde(
18        default,
19        deserialize_with = "present_nullable",
20        skip_serializing_if = "Option::is_none"
21    )]
22    pub text: Option<Option<String>>,
23    #[serde(
24        default,
25        deserialize_with = "present_nullable",
26        skip_serializing_if = "Option::is_none"
27    )]
28    pub tool: Option<Option<String>>,
29    #[serde(
30        default,
31        deserialize_with = "present_nullable",
32        skip_serializing_if = "Option::is_none"
33    )]
34    pub question: Option<Option<String>>,
35    #[serde(
36        default,
37        deserialize_with = "crate::client::decoding::optional_non_null",
38        skip_serializing_if = "Option::is_none"
39    )]
40    pub options: Option<Vec<BotRunOption>>,
41    #[serde(
42        default,
43        deserialize_with = "present_nullable",
44        skip_serializing_if = "Option::is_none"
45    )]
46    pub tokens: Option<Option<Number>>,
47    #[serde(
48        default,
49        deserialize_with = "present_nullable",
50        skip_serializing_if = "Option::is_none"
51    )]
52    pub cost_usd: Option<Option<Number>>,
53}
54#[derive(Debug, Deserialize, Serialize)]
55#[serde(deny_unknown_fields)]
56pub struct BotRunOption {
57    pub value: String,
58    #[serde(
59        default,
60        deserialize_with = "present_nullable",
61        skip_serializing_if = "Option::is_none"
62    )]
63    pub label: Option<Option<String>>,
64}
65
66fn present_nullable<'de, D: serde::Deserializer<'de>, T: Deserialize<'de>>(
67    deserializer: D,
68) -> Result<Option<Option<T>>, D::Error> {
69    Option::<T>::deserialize(deserializer).map(Some)
70}
71#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
72#[serde(rename_all = "snake_case")]
73pub enum BotRunStatus {
74    Queued,
75    Running,
76    AwaitingInput,
77    Review,
78    Done,
79    Failed,
80    Stopped,
81}
82#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
83#[serde(deny_unknown_fields)]
84pub struct BotRunReport {
85    #[serde(rename(deserialize = "_id", serialize = "run_id"))]
86    pub run_id: String,
87    pub status: BotRunStatus,
88}
89impl WekanClient {
90    pub async fn report_bot_run(
91        &self,
92        board: &str,
93        card: &str,
94        run: &str,
95        update: &BotRunUpdate,
96        token: &SecretString,
97    ) -> Result<BotRunReport, ClientError> {
98        for (value, field) in [(board, "board ID"), (card, "card ID"), (run, "run ID")] {
99            require_non_empty(value, "bot run report", field)?;
100        }
101        let mut url = self
102            .server()
103            .join("api/boards/")
104            .map_err(|_| super::protocol_error("could not build bot report endpoint".into()))?;
105        url.path_segments_mut()
106            .map_err(|_| super::protocol_error("could not build bot report endpoint".into()))?
107            .pop_if_empty()
108            .extend([board, "cards", card, "bot-runs", run]);
109        let body = self
110            .execute_success_request(
111                self.http
112                    .post(url)
113                    .header(ACCEPT, "application/json")
114                    .bearer_auth(token.expose_secret())
115                    .json(update),
116            )
117            .await?;
118        let response: BotRunReport = decode_success(&body, "bot run report", true)?;
119        if response.run_id != run {
120            return Err(super::protocol_error(
121                "the reported run ID did not match the requested run".into(),
122            ));
123        }
124        Ok(response)
125    }
126}