1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use crate::client::NoBody;
use crate::client::scope::{Scoped, Threads};
use crate::{Error, types::*, util::urlish};
impl<S: Threads> Scoped<'_, S> {
/// GET `{scope}/threads`, one page. Pass [`ThreadListFilters::default`] for
/// the first unfiltered page; carry `next_page_token` back in for more, or
/// use [`Scoped::list_all_threads`] to drain every page.
pub async fn list_threads(&self, filters: ThreadListFilters) -> Result<ThreadList, Error> {
self.client
.request(
reqwest::Method::GET,
&format!("{}/threads", self.base()),
&filters.query(),
None::<&NoBody>,
)
.await
}
/// Every thread matching `filters`, draining pagination.
pub async fn list_all_threads(
&self,
mut filters: ThreadListFilters,
) -> Result<Vec<Thread>, Error> {
let mut out = Vec::new();
loop {
let page = self.list_threads(filters.clone()).await?;
let next = page.next_page_token;
out.extend(page.threads);
match next {
Some(token) => filters.page_token = Some(token),
None => return Ok(out),
}
}
}
/// GET `{scope}/threads/search`, full-text search (`q` required); matches
/// come back in [`Thread::highlights`].
pub async fn search_threads(
&self,
query: &str,
filters: ThreadListFilters,
) -> Result<ThreadList, Error> {
let mut q = filters.query();
q.push(("q", query.to_string()));
self.client
.request(
reqwest::Method::GET,
&format!("{}/threads/search", self.base()),
&q,
None::<&NoBody>,
)
.await
}
/// Every thread matching a search, draining pagination.
pub async fn search_all_threads(
&self,
query: &str,
mut filters: ThreadListFilters,
) -> Result<Vec<Thread>, Error> {
let mut out = Vec::new();
loop {
let page = self.search_threads(query, filters.clone()).await?;
let next = page.next_page_token;
out.extend(page.threads);
match next {
Some(token) => filters.page_token = Some(token),
None => return Ok(out),
}
}
}
/// GET `{scope}/threads/{thread_id}`, the full thread with its messages.
pub async fn get_thread(&self, thread_id: &str) -> Result<Thread, Error> {
self.client
.request(
reqwest::Method::GET,
&format!("{}/threads/{}", self.base(), urlish(thread_id)),
&[],
None::<&NoBody>,
)
.await
}
/// PATCH `{scope}/threads/{thread_id}`, add and/or remove labels across the
/// thread. Returns the thread id and its labels after the change.
pub async fn update_thread(
&self,
thread_id: &str,
update: UpdateThread,
) -> Result<UpdatedThread, Error> {
self.client
.request(
reqwest::Method::PATCH,
&format!("{}/threads/{}", self.base(), urlish(thread_id)),
&[],
Some(&update),
)
.await
}
/// DELETE `{scope}/threads/{thread_id}`.
pub async fn delete_thread(&self, thread_id: &str) -> Result<(), Error> {
self.client
.request(
reqwest::Method::DELETE,
&format!("{}/threads/{}", self.base(), urlish(thread_id)),
&[],
None::<&NoBody>,
)
.await
}
/// GET `{scope}/threads/{thread_id}/attachments/{attachment_id}`, the
/// attachment metadata with a short-lived `download_url`; fetch the bytes
/// with [`Client::download_attachment`](crate::Client::download_attachment).
pub async fn get_thread_attachment(
&self,
thread_id: &str,
attachment_id: &str,
) -> Result<Attachment, Error> {
self.client
.request(
reqwest::Method::GET,
&format!(
"{}/threads/{}/attachments/{}",
self.base(),
urlish(thread_id),
urlish(attachment_id),
),
&[],
None::<&NoBody>,
)
.await
}
}