1use std::marker::PhantomData;
2
3use async_stream::try_stream;
4use futures_core::stream::BoxStream;
5use reqwest::Method;
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use serde_json::{Map, Value};
9
10use crate::client::Client;
11use crate::error::{Error, Result};
12use crate::models::{NewArticle, Ticket, TicketExpanded};
13
14const PATH: &str = "api/v1/tickets";
15
16pub trait TicketShape: DeserializeOwned + Send + 'static {
22 const EXPAND: bool;
24}
25
26impl TicketShape for Ticket {
27 const EXPAND: bool = false;
28}
29
30impl TicketShape for TicketExpanded {
31 const EXPAND: bool = true;
32}
33
34#[derive(Debug)]
36pub struct TicketsApi<'a> {
37 client: &'a Client,
38}
39
40impl<'a> TicketsApi<'a> {
41 pub(crate) fn new(client: &'a Client) -> Self {
42 Self { client }
43 }
44
45 pub async fn get(&self, id: u64) -> Result<Ticket> {
47 let req = self.client.request(Method::GET, &format!("{PATH}/{id}"));
48 self.client.send_json(req).await
49 }
50
51 pub async fn get_expanded(&self, id: u64) -> Result<TicketExpanded> {
54 let req = self
55 .client
56 .request(Method::GET, &format!("{PATH}/{id}"))
57 .query(&[("expand", "true")]);
58 self.client.send_json(req).await
59 }
60
61 pub fn list(&self) -> ListTickets<'a, Ticket> {
63 ListTickets::new(self.client)
64 }
65
66 pub fn list_expanded(&self) -> ListTickets<'a, TicketExpanded> {
68 ListTickets::new(self.client)
69 }
70
71 pub fn search(&self, query: impl Into<String>) -> SearchTickets<'a, Ticket> {
73 SearchTickets::new(self.client, query.into())
74 }
75
76 pub fn search_expanded(&self, query: impl Into<String>) -> SearchTickets<'a, TicketExpanded> {
78 SearchTickets::new(self.client, query.into())
79 }
80
81 pub fn create(&self) -> CreateTicket<'a> {
83 CreateTicket {
84 client: self.client,
85 body: Map::new(),
86 }
87 }
88
89 pub async fn update<P: Serialize + ?Sized>(&self, id: u64, patch: &P) -> Result<Ticket> {
95 let req = self
96 .client
97 .request(Method::PUT, &format!("{PATH}/{id}"))
98 .json(patch);
99 self.client.send_json(req).await
100 }
101
102 pub async fn set_field(
113 &self,
114 id: u64,
115 field: &str,
116 value: impl Into<Value>,
117 ) -> Result<Ticket> {
118 let value: Value = value.into();
119 let mut body = Map::new();
120 body.insert(field.to_string(), value.clone());
121 let updated = self.update(id, &Value::Object(body)).await?;
122 let actual = updated.extra.get(field).cloned();
123 if actual.as_ref() != Some(&value) {
124 return Err(Error::WriteVerificationFailed {
125 field: field.into(),
126 actual,
127 });
128 }
129 Ok(updated)
130 }
131
132 pub async fn delete(&self, id: u64) -> Result<()> {
134 let req = self.client.request(Method::DELETE, &format!("{PATH}/{id}"));
135 self.client.send_unit(req).await
136 }
137}
138
139#[derive(Debug)]
145pub struct ListTickets<'a, T: TicketShape = Ticket> {
146 client: &'a Client,
147 per_page: u32,
148 _t: PhantomData<fn() -> T>,
149}
150
151impl<'a, T: TicketShape> ListTickets<'a, T> {
152 fn new(client: &'a Client) -> Self {
153 Self {
154 client,
155 per_page: 100,
156 _t: PhantomData,
157 }
158 }
159
160 pub fn per_page(mut self, n: u32) -> Self {
162 self.per_page = n;
163 self
164 }
165
166 pub async fn page(&self, page: u32) -> Result<Vec<T>> {
168 let req = self.client.request(Method::GET, PATH).query(&[
169 ("page", page.to_string()),
170 ("per_page", self.per_page.to_string()),
171 ("expand", T::EXPAND.to_string()),
172 ]);
173 self.client.send_json(req).await
174 }
175
176 pub fn stream(self) -> BoxStream<'a, Result<T>> {
178 let client = self.client;
179 let per_page = self.per_page;
180 Box::pin(try_stream! {
181 let mut page = 1u32;
182 loop {
183 let req = client.request(Method::GET, PATH).query(&[
184 ("page", page.to_string()),
185 ("per_page", per_page.to_string()),
186 ("expand", T::EXPAND.to_string()),
187 ]);
188 let chunk: Vec<T> = client.send_json(req).await?;
189 let len = chunk.len() as u32;
190 for t in chunk { yield t; }
191 if len < per_page { break; }
192 page += 1;
193 }
194 })
195 }
196}
197
198#[derive(Debug)]
200pub struct SearchTickets<'a, T: TicketShape = Ticket> {
201 client: &'a Client,
202 query: String,
203 per_page: u32,
204 sort_by: Option<String>,
205 order_by: Option<String>,
206 _t: PhantomData<fn() -> T>,
207}
208
209impl<'a, T: TicketShape> SearchTickets<'a, T> {
210 fn new(client: &'a Client, query: String) -> Self {
211 Self {
212 client,
213 query,
214 per_page: 100,
215 sort_by: None,
216 order_by: None,
217 _t: PhantomData,
218 }
219 }
220
221 pub fn per_page(mut self, n: u32) -> Self {
223 self.per_page = n;
224 self
225 }
226
227 pub fn sort_by(mut self, s: impl Into<String>) -> Self {
229 self.sort_by = Some(s.into());
230 self
231 }
232
233 pub fn order_by(mut self, s: impl Into<String>) -> Self {
235 self.order_by = Some(s.into());
236 self
237 }
238
239 fn build_query(&self, page: u32) -> Vec<(&'static str, String)> {
240 let mut q = vec![
241 ("query", self.query.clone()),
242 ("page", page.to_string()),
243 ("per_page", self.per_page.to_string()),
244 ("expand", T::EXPAND.to_string()),
245 ];
246 if let Some(s) = &self.sort_by {
247 q.push(("sort_by", s.clone()));
248 }
249 if let Some(s) = &self.order_by {
250 q.push(("order_by", s.clone()));
251 }
252 q
253 }
254
255 pub async fn page(&self, page: u32) -> Result<Vec<T>> {
257 let req = self
258 .client
259 .request(Method::GET, &format!("{PATH}/search"))
260 .query(&self.build_query(page));
261 self.client.send_json(req).await
262 }
263
264 pub fn stream(self) -> BoxStream<'a, Result<T>> {
266 let client = self.client;
267 let per_page = self.per_page;
268 Box::pin(try_stream! {
269 let mut page = 1u32;
270 loop {
271 let req = client
272 .request(Method::GET, &format!("{PATH}/search"))
273 .query(&self.build_query(page));
274 let chunk: Vec<T> = client.send_json(req).await?;
275 let len = chunk.len() as u32;
276 for t in chunk { yield t; }
277 if len < per_page { break; }
278 page += 1;
279 }
280 })
281 }
282}
283
284#[derive(Debug)]
286pub struct CreateTicket<'a> {
287 client: &'a Client,
288 body: Map<String, Value>,
289}
290
291impl<'a> CreateTicket<'a> {
292 pub fn title(mut self, s: impl Into<String>) -> Self {
294 self.body.insert("title".into(), s.into().into());
295 self
296 }
297 pub fn group(mut self, s: impl Into<String>) -> Self {
299 self.body.insert("group".into(), s.into().into());
300 self
301 }
302 pub fn customer(mut self, s: impl Into<String>) -> Self {
304 self.body.insert("customer".into(), s.into().into());
305 self
306 }
307 pub fn state(mut self, s: impl Into<String>) -> Self {
309 self.body.insert("state".into(), s.into().into());
310 self
311 }
312 pub fn priority(mut self, s: impl Into<String>) -> Self {
314 self.body.insert("priority".into(), s.into().into());
315 self
316 }
317 pub fn owner(mut self, s: impl Into<String>) -> Self {
319 self.body.insert("owner".into(), s.into().into());
320 self
321 }
322 pub fn article(mut self, a: NewArticle) -> Self {
324 self.body
325 .insert("article".into(), serde_json::to_value(a).expect("serialize article"));
326 self
327 }
328 pub fn field(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
331 self.body.insert(key.into(), value.into());
332 self
333 }
334
335 pub async fn send(self) -> Result<Ticket> {
337 let req = self
338 .client
339 .request(Method::POST, PATH)
340 .json(&Value::Object(self.body));
341 self.client.send_json(req).await
342 }
343}