wekan_cli/client/cards/
bot_runs.rs1use 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}