1#![allow(missing_docs)]
59#![allow(clippy::needless_lifetimes)]
60#![cfg_attr(docsrs, feature(doc_cfg))]
61
62#[doc(hidden)]
63pub mod accounts;
64pub mod analytics;
65pub mod attachments;
66pub mod channels;
67pub mod comments;
68pub mod contact_groups;
69pub mod contact_handles;
70pub mod contact_notes;
71pub mod contacts;
72pub mod conversations;
73pub mod custom_fields;
74pub mod drafts;
75pub mod events;
76pub mod inboxes;
77pub mod links;
78pub mod message_template_folders;
79pub mod message_templates;
80pub mod messages;
81pub mod rules;
82pub mod shifts;
83pub mod signatures;
84pub mod tags;
85pub mod teammates;
86pub mod teams;
87#[cfg(test)]
88mod tests;
89pub mod token_identity;
90pub mod types;
91
92use std::env;
93
94static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
95
96#[derive(Clone, Debug)]
98pub struct Client {
99 token: String,
100 base_url: String,
101
102 client: reqwest_middleware::ClientWithMiddleware,
103}
104
105impl Client {
106 #[tracing::instrument]
110 pub fn new<T>(token: T) -> Self
111 where
112 T: ToString + std::fmt::Debug,
113 {
114 let retry_policy =
116 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
117 let client = reqwest::Client::builder()
118 .user_agent(APP_USER_AGENT)
119 .build();
120 match client {
121 Ok(c) => {
122 let client = reqwest_middleware::ClientBuilder::new(c)
123 .with(reqwest_tracing::TracingMiddleware::default())
125 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
127 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
128 |req: &reqwest::Request| req.try_clone().is_some(),
129 ))
130 .build();
131
132 Client {
133 token: token.to_string(),
134 base_url: "https://api2.frontapp.com".to_string(),
135
136 client,
137 }
138 }
139 Err(e) => panic!("creating reqwest client failed: {:?}", e),
140 }
141 }
142
143 #[tracing::instrument]
145 pub fn set_base_url<H>(&mut self, base_url: H)
146 where
147 H: Into<String> + std::fmt::Display + std::fmt::Debug,
148 {
149 self.base_url = base_url.to_string().trim_end_matches('/').to_string();
150 }
151
152 #[tracing::instrument]
154 pub fn new_from_env() -> Self {
155 let token = env::var("FRONT_API_TOKEN").expect("must set FRONT_API_TOKEN");
156
157 Client::new(token)
158 }
159
160 #[tracing::instrument]
162 pub async fn request_raw(
163 &self,
164 method: reqwest::Method,
165 uri: &str,
166 body: Option<reqwest::Body>,
167 ) -> anyhow::Result<reqwest_middleware::RequestBuilder> {
168 let u = if uri.starts_with("https://") || uri.starts_with("http://") {
169 uri.to_string()
170 } else {
171 format!("{}/{}", self.base_url, uri.trim_start_matches('/'))
172 };
173
174 let mut req = self.client.request(method, &u);
175
176 req = req.bearer_auth(&self.token);
178
179 req = req.header(
181 reqwest::header::ACCEPT,
182 reqwest::header::HeaderValue::from_static("application/json"),
183 );
184 req = req.header(
185 reqwest::header::CONTENT_TYPE,
186 reqwest::header::HeaderValue::from_static("application/json"),
187 );
188
189 if let Some(body) = body {
190 req = req.body(body);
191 }
192
193 Ok(req)
194 }
195
196 pub fn accounts(&self) -> accounts::Accounts {
198 accounts::Accounts::new(self.clone())
199 }
200
201 pub fn events(&self) -> events::Events {
203 events::Events::new(self.clone())
204 }
205
206 pub fn analytics(&self) -> analytics::Analytics {
208 analytics::Analytics::new(self.clone())
209 }
210
211 pub fn attachments(&self) -> attachments::Attachments {
213 attachments::Attachments::new(self.clone())
214 }
215
216 pub fn token_identity(&self) -> token_identity::TokenIdentity {
218 token_identity::TokenIdentity::new(self.clone())
219 }
220
221 pub fn message_template_folders(&self) -> message_template_folders::MessageTemplateFolders {
223 message_template_folders::MessageTemplateFolders::new(self.clone())
224 }
225
226 pub fn message_templates(&self) -> message_templates::MessageTemplates {
228 message_templates::MessageTemplates::new(self.clone())
229 }
230
231 pub fn contact_groups(&self) -> contact_groups::ContactGroups {
233 contact_groups::ContactGroups::new(self.clone())
234 }
235
236 pub fn contacts(&self) -> contacts::Contacts {
238 contacts::Contacts::new(self.clone())
239 }
240
241 pub fn contact_handles(&self) -> contact_handles::ContactHandles {
243 contact_handles::ContactHandles::new(self.clone())
244 }
245
246 pub fn contact_notes(&self) -> contact_notes::ContactNotes {
248 contact_notes::ContactNotes::new(self.clone())
249 }
250
251 pub fn channels(&self) -> channels::Channels {
253 channels::Channels::new(self.clone())
254 }
255
256 pub fn inboxes(&self) -> inboxes::Inboxes {
258 inboxes::Inboxes::new(self.clone())
259 }
260
261 pub fn comments(&self) -> comments::Comments {
263 comments::Comments::new(self.clone())
264 }
265
266 pub fn conversations(&self) -> conversations::Conversations {
268 conversations::Conversations::new(self.clone())
269 }
270
271 pub fn messages(&self) -> messages::Messages {
273 messages::Messages::new(self.clone())
274 }
275
276 pub fn custom_fields(&self) -> custom_fields::CustomFields {
278 custom_fields::CustomFields::new(self.clone())
279 }
280
281 pub fn drafts(&self) -> drafts::Drafts {
283 drafts::Drafts::new(self.clone())
284 }
285
286 pub fn rules(&self) -> rules::Rules {
288 rules::Rules::new(self.clone())
289 }
290
291 pub fn shifts(&self) -> shifts::Shifts {
293 shifts::Shifts::new(self.clone())
294 }
295
296 pub fn signatures(&self) -> signatures::Signatures {
298 signatures::Signatures::new(self.clone())
299 }
300
301 pub fn tags(&self) -> tags::Tags {
303 tags::Tags::new(self.clone())
304 }
305
306 pub fn teams(&self) -> teams::Teams {
308 teams::Teams::new(self.clone())
309 }
310
311 pub fn teammates(&self) -> teammates::Teammates {
313 teammates::Teammates::new(self.clone())
314 }
315
316 pub fn links(&self) -> links::Links {
318 links::Links::new(self.clone())
319 }
320}