1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4use crate::client::Sendly;
5use crate::error::Result;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Contact {
9 pub id: String,
10 #[serde(alias = "phoneNumber")]
11 pub phone_number: String,
12 #[serde(default)]
13 pub name: Option<String>,
14 #[serde(default)]
15 pub email: Option<String>,
16 #[serde(default)]
17 pub metadata: Option<HashMap<String, serde_json::Value>>,
18 #[serde(default, alias = "createdAt")]
19 pub created_at: Option<String>,
20 #[serde(default, alias = "updatedAt")]
21 pub updated_at: Option<String>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ContactList {
26 pub id: String,
27 pub name: String,
28 #[serde(default)]
29 pub description: Option<String>,
30 #[serde(default, alias = "contactCount")]
31 pub contact_count: i32,
32 #[serde(default, alias = "createdAt")]
33 pub created_at: Option<String>,
34 #[serde(default, alias = "updatedAt")]
35 pub updated_at: Option<String>,
36}
37
38#[derive(Debug, Clone, Deserialize)]
39pub struct ContactListResponse {
40 pub contacts: Vec<Contact>,
41 #[serde(default)]
42 pub total: i32,
43 #[serde(default)]
44 pub limit: i32,
45 #[serde(default)]
46 pub offset: i32,
47}
48
49#[derive(Debug, Clone, Deserialize)]
50pub struct ContactListsResponse {
51 pub lists: Vec<ContactList>,
52 #[serde(default)]
53 pub total: i32,
54 #[serde(default)]
55 pub limit: i32,
56 #[serde(default)]
57 pub offset: i32,
58}
59
60#[derive(Debug, Clone, Serialize)]
61pub struct CreateContactRequest {
62 #[serde(rename = "phone_number")]
63 pub phone_number: String,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub name: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub email: Option<String>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub metadata: Option<HashMap<String, serde_json::Value>>,
70}
71
72impl CreateContactRequest {
73 pub fn new(phone_number: impl Into<String>) -> Self {
74 Self {
75 phone_number: phone_number.into(),
76 name: None,
77 email: None,
78 metadata: None,
79 }
80 }
81
82 pub fn name(mut self, name: impl Into<String>) -> Self {
83 self.name = Some(name.into());
84 self
85 }
86
87 pub fn email(mut self, email: impl Into<String>) -> Self {
88 self.email = Some(email.into());
89 self
90 }
91
92 pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
93 self.metadata = Some(metadata);
94 self
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Default)]
99pub struct UpdateContactRequest {
100 #[serde(skip_serializing_if = "Option::is_none", rename = "phone_number")]
101 pub phone_number: Option<String>,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub name: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub email: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub metadata: Option<HashMap<String, serde_json::Value>>,
108}
109
110impl UpdateContactRequest {
111 pub fn new() -> Self {
112 Self::default()
113 }
114
115 pub fn phone_number(mut self, phone_number: impl Into<String>) -> Self {
116 self.phone_number = Some(phone_number.into());
117 self
118 }
119
120 pub fn name(mut self, name: impl Into<String>) -> Self {
121 self.name = Some(name.into());
122 self
123 }
124
125 pub fn email(mut self, email: impl Into<String>) -> Self {
126 self.email = Some(email.into());
127 self
128 }
129
130 pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
131 self.metadata = Some(metadata);
132 self
133 }
134}
135
136#[derive(Debug, Clone, Default)]
137pub struct ListContactsOptions {
138 pub limit: Option<u32>,
139 pub offset: Option<u32>,
140 pub search: Option<String>,
141 pub list_id: Option<String>,
142}
143
144impl ListContactsOptions {
145 pub fn new() -> Self {
146 Self::default()
147 }
148
149 pub fn limit(mut self, limit: u32) -> Self {
150 self.limit = Some(limit.min(100));
151 self
152 }
153
154 pub fn offset(mut self, offset: u32) -> Self {
155 self.offset = Some(offset);
156 self
157 }
158
159 pub fn search(mut self, search: impl Into<String>) -> Self {
160 self.search = Some(search.into());
161 self
162 }
163
164 pub fn list_id(mut self, list_id: impl Into<String>) -> Self {
165 self.list_id = Some(list_id.into());
166 self
167 }
168
169 pub(crate) fn to_query_params(&self) -> Vec<(String, String)> {
170 let mut params = Vec::new();
171 if let Some(limit) = self.limit {
172 params.push(("limit".to_string(), limit.to_string()));
173 }
174 if let Some(offset) = self.offset {
175 params.push(("offset".to_string(), offset.to_string()));
176 }
177 if let Some(ref search) = self.search {
178 params.push(("search".to_string(), search.clone()));
179 }
180 if let Some(ref list_id) = self.list_id {
181 params.push(("list_id".to_string(), list_id.clone()));
182 }
183 params
184 }
185}
186
187#[derive(Debug, Clone, Serialize)]
188pub struct CreateContactListRequest {
189 pub name: String,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub description: Option<String>,
192}
193
194impl CreateContactListRequest {
195 pub fn new(name: impl Into<String>) -> Self {
196 Self {
197 name: name.into(),
198 description: None,
199 }
200 }
201
202 pub fn description(mut self, description: impl Into<String>) -> Self {
203 self.description = Some(description.into());
204 self
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Default)]
209pub struct UpdateContactListRequest {
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub name: Option<String>,
212 #[serde(skip_serializing_if = "Option::is_none")]
213 pub description: Option<String>,
214}
215
216impl UpdateContactListRequest {
217 pub fn new() -> Self {
218 Self::default()
219 }
220
221 pub fn name(mut self, name: impl Into<String>) -> Self {
222 self.name = Some(name.into());
223 self
224 }
225
226 pub fn description(mut self, description: impl Into<String>) -> Self {
227 self.description = Some(description.into());
228 self
229 }
230}
231
232#[derive(Debug, Clone, Serialize)]
233pub struct AddContactsRequest {
234 #[serde(rename = "contact_ids")]
235 pub contact_ids: Vec<String>,
236}
237
238#[derive(Debug, Clone, Serialize)]
239pub struct ImportContactItem {
240 pub phone: String,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub name: Option<String>,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub email: Option<String>,
245 #[serde(skip_serializing_if = "Option::is_none", rename = "optedInAt")]
246 pub opted_in_at: Option<String>,
247}
248
249impl ImportContactItem {
250 pub fn new(phone: impl Into<String>) -> Self {
251 Self {
252 phone: phone.into(),
253 name: None,
254 email: None,
255 opted_in_at: None,
256 }
257 }
258
259 pub fn name(mut self, name: impl Into<String>) -> Self {
260 self.name = Some(name.into());
261 self
262 }
263
264 pub fn email(mut self, email: impl Into<String>) -> Self {
265 self.email = Some(email.into());
266 self
267 }
268}
269
270#[derive(Debug, Clone, Serialize)]
271pub struct ImportContactsRequest {
272 pub contacts: Vec<ImportContactItem>,
273 #[serde(skip_serializing_if = "Option::is_none", rename = "listId")]
274 pub list_id: Option<String>,
275 #[serde(skip_serializing_if = "Option::is_none", rename = "optedInAt")]
276 pub opted_in_at: Option<String>,
277}
278
279#[derive(Debug, Clone, Deserialize)]
280pub struct ImportContactsError {
281 pub index: i32,
282 pub phone: String,
283 pub error: String,
284}
285
286#[derive(Debug, Clone, Deserialize)]
287pub struct ImportContactsResponse {
288 pub imported: i32,
289 #[serde(rename = "skippedDuplicates")]
290 pub skipped_duplicates: i32,
291 #[serde(default)]
292 pub errors: Vec<ImportContactsError>,
293 #[serde(default, rename = "totalErrors")]
294 pub total_errors: i32,
295}
296
297pub struct ContactsResource<'a> {
298 client: &'a Sendly,
299}
300
301impl<'a> ContactsResource<'a> {
302 pub fn new(client: &'a Sendly) -> Self {
303 Self { client }
304 }
305
306 pub fn lists(&self) -> ContactListsResource<'a> {
307 ContactListsResource::new(self.client)
308 }
309
310 pub async fn list(&self, options: ListContactsOptions) -> Result<ContactListResponse> {
311 let params = options.to_query_params();
312 let response = self.client.get("/contacts", ¶ms).await?;
313 Ok(response.json().await?)
314 }
315
316 pub async fn get(&self, id: &str) -> Result<Contact> {
317 let response = self.client.get(&format!("/contacts/{}", id), &[]).await?;
318 Ok(response.json().await?)
319 }
320
321 pub async fn create(&self, request: CreateContactRequest) -> Result<Contact> {
322 let response = self.client.post("/contacts", &request).await?;
323 Ok(response.json().await?)
324 }
325
326 pub async fn update(&self, id: &str, request: UpdateContactRequest) -> Result<Contact> {
327 let response = self
328 .client
329 .patch(&format!("/contacts/{}", id), &request)
330 .await?;
331 Ok(response.json().await?)
332 }
333
334 pub async fn delete(&self, id: &str) -> Result<()> {
335 self.client.delete(&format!("/contacts/{}", id)).await?;
336 Ok(())
337 }
338
339 pub async fn import(&self, request: ImportContactsRequest) -> Result<ImportContactsResponse> {
340 let response = self.client.post("/contacts/import", &request).await?;
341 Ok(response.json().await?)
342 }
343}
344
345pub struct ContactListsResource<'a> {
346 client: &'a Sendly,
347}
348
349impl<'a> ContactListsResource<'a> {
350 pub fn new(client: &'a Sendly) -> Self {
351 Self { client }
352 }
353
354 pub async fn list(&self) -> Result<ContactListsResponse> {
355 let response = self.client.get("/contact-lists", &[]).await?;
356 Ok(response.json().await?)
357 }
358
359 pub async fn get(&self, id: &str) -> Result<ContactList> {
360 let response = self
361 .client
362 .get(&format!("/contact-lists/{}", id), &[])
363 .await?;
364 Ok(response.json().await?)
365 }
366
367 pub async fn create(&self, request: CreateContactListRequest) -> Result<ContactList> {
368 let response = self.client.post("/contact-lists", &request).await?;
369 Ok(response.json().await?)
370 }
371
372 pub async fn update(&self, id: &str, request: UpdateContactListRequest) -> Result<ContactList> {
373 let response = self
374 .client
375 .patch(&format!("/contact-lists/{}", id), &request)
376 .await?;
377 Ok(response.json().await?)
378 }
379
380 pub async fn delete(&self, id: &str) -> Result<()> {
381 self.client
382 .delete(&format!("/contact-lists/{}", id))
383 .await?;
384 Ok(())
385 }
386
387 pub async fn add_contacts(&self, list_id: &str, contact_ids: Vec<String>) -> Result<()> {
388 let request = AddContactsRequest { contact_ids };
389 self.client
390 .post(&format!("/contact-lists/{}/contacts", list_id), &request)
391 .await?;
392 Ok(())
393 }
394
395 pub async fn remove_contact(&self, list_id: &str, contact_id: &str) -> Result<()> {
396 self.client
397 .delete(&format!(
398 "/contact-lists/{}/contacts/{}",
399 list_id, contact_id
400 ))
401 .await?;
402 Ok(())
403 }
404}