use std::marker::PhantomData;
use async_stream::try_stream;
use futures_core::stream::BoxStream;
use reqwest::Method;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::client::Client;
use crate::error::{Error, Result};
use crate::models::{NewArticle, Ticket, TicketExpanded};
const PATH: &str = "api/v1/tickets";
pub trait TicketShape: DeserializeOwned + Send + 'static {
const EXPAND: bool;
}
impl TicketShape for Ticket {
const EXPAND: bool = false;
}
impl TicketShape for TicketExpanded {
const EXPAND: bool = true;
}
#[derive(Debug)]
pub struct TicketsApi<'a> {
client: &'a Client,
}
impl<'a> TicketsApi<'a> {
pub(crate) fn new(client: &'a Client) -> Self {
Self { client }
}
pub async fn get(&self, id: u64) -> Result<Ticket> {
let req = self.client.request(Method::GET, &format!("{PATH}/{id}"));
self.client.send_json(req).await
}
pub async fn get_expanded(&self, id: u64) -> Result<TicketExpanded> {
let req = self
.client
.request(Method::GET, &format!("{PATH}/{id}"))
.query(&[("expand", "true")]);
self.client.send_json(req).await
}
pub fn list(&self) -> ListTickets<'a, Ticket> {
ListTickets::new(self.client)
}
pub fn list_expanded(&self) -> ListTickets<'a, TicketExpanded> {
ListTickets::new(self.client)
}
pub fn search(&self, query: impl Into<String>) -> SearchTickets<'a, Ticket> {
SearchTickets::new(self.client, query.into())
}
pub fn search_expanded(&self, query: impl Into<String>) -> SearchTickets<'a, TicketExpanded> {
SearchTickets::new(self.client, query.into())
}
pub fn create(&self) -> CreateTicket<'a> {
CreateTicket {
client: self.client,
body: Map::new(),
}
}
pub async fn update<P: Serialize + ?Sized>(&self, id: u64, patch: &P) -> Result<Ticket> {
let req = self
.client
.request(Method::PUT, &format!("{PATH}/{id}"))
.json(patch);
self.client.send_json(req).await
}
pub async fn set_field(
&self,
id: u64,
field: &str,
value: impl Into<Value>,
) -> Result<Ticket> {
let value: Value = value.into();
let mut body = Map::new();
body.insert(field.to_string(), value.clone());
let updated = self.update(id, &Value::Object(body)).await?;
let actual = updated.extra.get(field).cloned();
if actual.as_ref() != Some(&value) {
return Err(Error::WriteVerificationFailed {
field: field.into(),
actual,
});
}
Ok(updated)
}
pub async fn delete(&self, id: u64) -> Result<()> {
let req = self.client.request(Method::DELETE, &format!("{PATH}/{id}"));
self.client.send_unit(req).await
}
}
#[derive(Debug)]
pub struct ListTickets<'a, T: TicketShape = Ticket> {
client: &'a Client,
per_page: u32,
_t: PhantomData<fn() -> T>,
}
impl<'a, T: TicketShape> ListTickets<'a, T> {
fn new(client: &'a Client) -> Self {
Self {
client,
per_page: 100,
_t: PhantomData,
}
}
pub fn per_page(mut self, n: u32) -> Self {
self.per_page = n;
self
}
pub async fn page(&self, page: u32) -> Result<Vec<T>> {
let req = self.client.request(Method::GET, PATH).query(&[
("page", page.to_string()),
("per_page", self.per_page.to_string()),
("expand", T::EXPAND.to_string()),
]);
self.client.send_json(req).await
}
pub fn stream(self) -> BoxStream<'a, Result<T>> {
let client = self.client;
let per_page = self.per_page;
Box::pin(try_stream! {
let mut page = 1u32;
loop {
let req = client.request(Method::GET, PATH).query(&[
("page", page.to_string()),
("per_page", per_page.to_string()),
("expand", T::EXPAND.to_string()),
]);
let chunk: Vec<T> = client.send_json(req).await?;
let len = chunk.len() as u32;
for t in chunk { yield t; }
if len < per_page { break; }
page += 1;
}
})
}
}
#[derive(Debug)]
pub struct SearchTickets<'a, T: TicketShape = Ticket> {
client: &'a Client,
query: String,
per_page: u32,
sort_by: Option<String>,
order_by: Option<String>,
_t: PhantomData<fn() -> T>,
}
impl<'a, T: TicketShape> SearchTickets<'a, T> {
fn new(client: &'a Client, query: String) -> Self {
Self {
client,
query,
per_page: 100,
sort_by: None,
order_by: None,
_t: PhantomData,
}
}
pub fn per_page(mut self, n: u32) -> Self {
self.per_page = n;
self
}
pub fn sort_by(mut self, s: impl Into<String>) -> Self {
self.sort_by = Some(s.into());
self
}
pub fn order_by(mut self, s: impl Into<String>) -> Self {
self.order_by = Some(s.into());
self
}
fn build_query(&self, page: u32) -> Vec<(&'static str, String)> {
let mut q = vec![
("query", self.query.clone()),
("page", page.to_string()),
("per_page", self.per_page.to_string()),
("expand", T::EXPAND.to_string()),
];
if let Some(s) = &self.sort_by {
q.push(("sort_by", s.clone()));
}
if let Some(s) = &self.order_by {
q.push(("order_by", s.clone()));
}
q
}
pub async fn page(&self, page: u32) -> Result<Vec<T>> {
let req = self
.client
.request(Method::GET, &format!("{PATH}/search"))
.query(&self.build_query(page));
self.client.send_json(req).await
}
pub fn stream(self) -> BoxStream<'a, Result<T>> {
let client = self.client;
let per_page = self.per_page;
Box::pin(try_stream! {
let mut page = 1u32;
loop {
let req = client
.request(Method::GET, &format!("{PATH}/search"))
.query(&self.build_query(page));
let chunk: Vec<T> = client.send_json(req).await?;
let len = chunk.len() as u32;
for t in chunk { yield t; }
if len < per_page { break; }
page += 1;
}
})
}
}
#[derive(Debug)]
pub struct CreateTicket<'a> {
client: &'a Client,
body: Map<String, Value>,
}
impl<'a> CreateTicket<'a> {
pub fn title(mut self, s: impl Into<String>) -> Self {
self.body.insert("title".into(), s.into().into());
self
}
pub fn group(mut self, s: impl Into<String>) -> Self {
self.body.insert("group".into(), s.into().into());
self
}
pub fn customer(mut self, s: impl Into<String>) -> Self {
self.body.insert("customer".into(), s.into().into());
self
}
pub fn state(mut self, s: impl Into<String>) -> Self {
self.body.insert("state".into(), s.into().into());
self
}
pub fn priority(mut self, s: impl Into<String>) -> Self {
self.body.insert("priority".into(), s.into().into());
self
}
pub fn owner(mut self, s: impl Into<String>) -> Self {
self.body.insert("owner".into(), s.into().into());
self
}
pub fn article(mut self, a: NewArticle) -> Self {
self.body
.insert("article".into(), serde_json::to_value(a).expect("serialize article"));
self
}
pub fn field(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
self.body.insert(key.into(), value.into());
self
}
pub async fn send(self) -> Result<Ticket> {
let req = self
.client
.request(Method::POST, PATH)
.json(&Value::Object(self.body));
self.client.send_json(req).await
}
}