use serde::{Deserialize, Serialize};
use crate::common::{ContactId, GrantId};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Contact {
pub id: ContactId,
pub grant_id: GrantId,
#[serde(skip_serializing_if = "Option::is_none")]
pub given_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub middle_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub family_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub birthday: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub company_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manager_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub office_location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub picture_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub picture: Option<String>,
#[serde(default)]
pub emails: Vec<ContactEmail>,
#[serde(default)]
pub phone_numbers: Vec<ContactPhone>,
#[serde(default)]
pub physical_addresses: Vec<ContactAddress>,
#[serde(default)]
pub web_pages: Vec<ContactWebPage>,
#[serde(default)]
pub groups: Vec<ContactGroup>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContactEmail {
#[serde(skip_serializing_if = "Option::is_none", rename = "type")]
pub email_type: Option<String>,
pub email: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContactPhone {
#[serde(skip_serializing_if = "Option::is_none", rename = "type")]
pub phone_type: Option<String>,
pub number: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContactAddress {
#[serde(skip_serializing_if = "Option::is_none", rename = "type")]
pub address_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub street_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub postal_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContactWebPage {
#[serde(skip_serializing_if = "Option::is_none", rename = "type")]
pub page_type: Option<String>,
pub url: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContactGroup {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreateContactRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub given_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub middle_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub family_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub birthday: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub company_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manager_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub office_location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub picture_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub picture: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub emails: Option<Vec<ContactEmail>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone_numbers: Option<Vec<ContactPhone>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub physical_addresses: Option<Vec<ContactAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub web_pages: Option<Vec<ContactWebPage>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub groups: Option<Vec<ContactGroup>>,
}
impl CreateContactRequest {
pub fn builder() -> CreateContactRequestBuilder {
CreateContactRequestBuilder::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct CreateContactRequestBuilder {
given_name: Option<String>,
middle_name: Option<String>,
family_name: Option<String>,
suffix: Option<String>,
nickname: Option<String>,
birthday: Option<String>,
company_name: Option<String>,
job_title: Option<String>,
manager_name: Option<String>,
office_location: Option<String>,
notes: Option<String>,
picture_url: Option<String>,
picture: Option<String>,
emails: Option<Vec<ContactEmail>>,
phone_numbers: Option<Vec<ContactPhone>>,
physical_addresses: Option<Vec<ContactAddress>>,
web_pages: Option<Vec<ContactWebPage>>,
groups: Option<Vec<ContactGroup>>,
}
impl CreateContactRequestBuilder {
pub fn given_name(mut self, name: impl Into<String>) -> Self {
self.given_name = Some(name.into());
self
}
pub fn middle_name(mut self, name: impl Into<String>) -> Self {
self.middle_name = Some(name.into());
self
}
pub fn family_name(mut self, name: impl Into<String>) -> Self {
self.family_name = Some(name.into());
self
}
pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
self.suffix = Some(suffix.into());
self
}
pub fn nickname(mut self, nickname: impl Into<String>) -> Self {
self.nickname = Some(nickname.into());
self
}
pub fn birthday(mut self, birthday: impl Into<String>) -> Self {
self.birthday = Some(birthday.into());
self
}
pub fn company_name(mut self, name: impl Into<String>) -> Self {
self.company_name = Some(name.into());
self
}
pub fn job_title(mut self, title: impl Into<String>) -> Self {
self.job_title = Some(title.into());
self
}
pub fn manager_name(mut self, name: impl Into<String>) -> Self {
self.manager_name = Some(name.into());
self
}
pub fn office_location(mut self, location: impl Into<String>) -> Self {
self.office_location = Some(location.into());
self
}
pub fn notes(mut self, notes: impl Into<String>) -> Self {
self.notes = Some(notes.into());
self
}
pub fn picture_url(mut self, url: impl Into<String>) -> Self {
self.picture_url = Some(url.into());
self
}
pub fn picture(mut self, picture: impl Into<String>) -> Self {
self.picture = Some(picture.into());
self
}
pub fn emails(mut self, emails: Vec<ContactEmail>) -> Self {
self.emails = Some(emails);
self
}
pub fn phone_numbers(mut self, phones: Vec<ContactPhone>) -> Self {
self.phone_numbers = Some(phones);
self
}
pub fn physical_addresses(mut self, addresses: Vec<ContactAddress>) -> Self {
self.physical_addresses = Some(addresses);
self
}
pub fn web_pages(mut self, pages: Vec<ContactWebPage>) -> Self {
self.web_pages = Some(pages);
self
}
pub fn groups(mut self, groups: Vec<ContactGroup>) -> Self {
self.groups = Some(groups);
self
}
pub fn build(self) -> CreateContactRequest {
CreateContactRequest {
given_name: self.given_name,
middle_name: self.middle_name,
family_name: self.family_name,
suffix: self.suffix,
nickname: self.nickname,
birthday: self.birthday,
company_name: self.company_name,
job_title: self.job_title,
manager_name: self.manager_name,
office_location: self.office_location,
notes: self.notes,
picture_url: self.picture_url,
picture: self.picture,
emails: self.emails,
phone_numbers: self.phone_numbers,
physical_addresses: self.physical_addresses,
web_pages: self.web_pages,
groups: self.groups,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct UpdateContactRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub given_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub middle_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub family_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub birthday: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub company_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manager_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub office_location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub picture_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub picture: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub emails: Option<Vec<ContactEmail>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone_numbers: Option<Vec<ContactPhone>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub physical_addresses: Option<Vec<ContactAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub web_pages: Option<Vec<ContactWebPage>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub groups: Option<Vec<ContactGroup>>,
}
impl UpdateContactRequest {
pub fn builder() -> UpdateContactRequestBuilder {
UpdateContactRequestBuilder::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct UpdateContactRequestBuilder {
given_name: Option<String>,
middle_name: Option<String>,
family_name: Option<String>,
suffix: Option<String>,
nickname: Option<String>,
birthday: Option<String>,
company_name: Option<String>,
job_title: Option<String>,
manager_name: Option<String>,
office_location: Option<String>,
notes: Option<String>,
picture_url: Option<String>,
picture: Option<String>,
emails: Option<Vec<ContactEmail>>,
phone_numbers: Option<Vec<ContactPhone>>,
physical_addresses: Option<Vec<ContactAddress>>,
web_pages: Option<Vec<ContactWebPage>>,
groups: Option<Vec<ContactGroup>>,
}
impl UpdateContactRequestBuilder {
pub fn given_name(mut self, name: impl Into<String>) -> Self {
self.given_name = Some(name.into());
self
}
pub fn middle_name(mut self, name: impl Into<String>) -> Self {
self.middle_name = Some(name.into());
self
}
pub fn family_name(mut self, name: impl Into<String>) -> Self {
self.family_name = Some(name.into());
self
}
pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
self.suffix = Some(suffix.into());
self
}
pub fn nickname(mut self, nickname: impl Into<String>) -> Self {
self.nickname = Some(nickname.into());
self
}
pub fn birthday(mut self, birthday: impl Into<String>) -> Self {
self.birthday = Some(birthday.into());
self
}
pub fn company_name(mut self, name: impl Into<String>) -> Self {
self.company_name = Some(name.into());
self
}
pub fn job_title(mut self, title: impl Into<String>) -> Self {
self.job_title = Some(title.into());
self
}
pub fn manager_name(mut self, name: impl Into<String>) -> Self {
self.manager_name = Some(name.into());
self
}
pub fn office_location(mut self, location: impl Into<String>) -> Self {
self.office_location = Some(location.into());
self
}
pub fn notes(mut self, notes: impl Into<String>) -> Self {
self.notes = Some(notes.into());
self
}
pub fn picture_url(mut self, url: impl Into<String>) -> Self {
self.picture_url = Some(url.into());
self
}
pub fn picture(mut self, picture: impl Into<String>) -> Self {
self.picture = Some(picture.into());
self
}
pub fn emails(mut self, emails: Vec<ContactEmail>) -> Self {
self.emails = Some(emails);
self
}
pub fn phone_numbers(mut self, phones: Vec<ContactPhone>) -> Self {
self.phone_numbers = Some(phones);
self
}
pub fn physical_addresses(mut self, addresses: Vec<ContactAddress>) -> Self {
self.physical_addresses = Some(addresses);
self
}
pub fn web_pages(mut self, pages: Vec<ContactWebPage>) -> Self {
self.web_pages = Some(pages);
self
}
pub fn groups(mut self, groups: Vec<ContactGroup>) -> Self {
self.groups = Some(groups);
self
}
pub fn build(self) -> UpdateContactRequest {
UpdateContactRequest {
given_name: self.given_name,
middle_name: self.middle_name,
family_name: self.family_name,
suffix: self.suffix,
nickname: self.nickname,
birthday: self.birthday,
company_name: self.company_name,
job_title: self.job_title,
manager_name: self.manager_name,
office_location: self.office_location,
notes: self.notes,
picture_url: self.picture_url,
picture: self.picture,
emails: self.emails,
phone_numbers: self.phone_numbers,
physical_addresses: self.physical_addresses,
web_pages: self.web_pages,
groups: self.groups,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contact_creation() {
let contact = Contact {
id: ContactId::new("contact_123"),
grant_id: GrantId::new("grant_456"),
given_name: Some("John".to_string()),
family_name: Some("Doe".to_string()),
emails: vec![ContactEmail {
email_type: Some("work".to_string()),
email: "john.doe@example.com".to_string(),
}],
phone_numbers: vec![],
physical_addresses: vec![],
web_pages: vec![],
groups: vec![],
middle_name: None,
suffix: None,
nickname: None,
birthday: None,
company_name: None,
job_title: None,
manager_name: None,
office_location: None,
notes: None,
picture_url: None,
picture: None,
};
assert_eq!(contact.id.as_str(), "contact_123");
assert_eq!(contact.given_name.as_ref().unwrap(), "John");
assert_eq!(contact.family_name.as_ref().unwrap(), "Doe");
assert_eq!(contact.emails.len(), 1);
}
#[test]
fn test_contact_serialization() {
let contact = Contact {
id: ContactId::new("contact_123"),
grant_id: GrantId::new("grant_456"),
given_name: Some("Jane".to_string()),
family_name: Some("Smith".to_string()),
emails: vec![],
phone_numbers: vec![],
physical_addresses: vec![],
web_pages: vec![],
groups: vec![],
middle_name: None,
suffix: None,
nickname: None,
birthday: None,
company_name: Some("Acme Corp".to_string()),
job_title: Some("Engineer".to_string()),
manager_name: None,
office_location: None,
notes: None,
picture_url: None,
picture: None,
};
let json = serde_json::to_string(&contact).unwrap();
assert!(json.contains("contact_123"));
assert!(json.contains("Jane"));
assert!(json.contains("Acme Corp"));
}
#[test]
fn test_contact_deserialization() {
let json = r#"{
"id": "contact_123",
"grant_id": "grant_456",
"given_name": "Bob",
"family_name": "Johnson",
"company_name": "Tech Inc",
"emails": [],
"phone_numbers": [],
"physical_addresses": [],
"web_pages": [],
"groups": []
}"#;
let contact: Contact = serde_json::from_str(json).unwrap();
assert_eq!(contact.id.as_str(), "contact_123");
assert_eq!(contact.given_name.as_ref().unwrap(), "Bob");
assert_eq!(contact.company_name.as_ref().unwrap(), "Tech Inc");
}
#[test]
fn test_create_contact_request_builder() {
let request = CreateContactRequest::builder()
.given_name("Alice")
.family_name("Williams")
.company_name("Example LLC")
.job_title("Manager")
.build();
assert_eq!(request.given_name.as_ref().unwrap(), "Alice");
assert_eq!(request.family_name.as_ref().unwrap(), "Williams");
assert_eq!(request.company_name.as_ref().unwrap(), "Example LLC");
assert_eq!(request.job_title.as_ref().unwrap(), "Manager");
}
#[test]
fn test_update_contact_request_builder() {
let update = UpdateContactRequest::builder()
.given_name("Updated Name")
.job_title("Senior Engineer")
.build();
assert_eq!(update.given_name.as_ref().unwrap(), "Updated Name");
assert_eq!(update.job_title.as_ref().unwrap(), "Senior Engineer");
assert!(update.company_name.is_none());
}
#[test]
fn test_contact_email() {
let email = ContactEmail {
email_type: Some("work".to_string()),
email: "test@example.com".to_string(),
};
assert_eq!(email.email_type.as_ref().unwrap(), "work");
assert_eq!(email.email, "test@example.com");
}
#[test]
fn test_contact_phone() {
let phone = ContactPhone {
phone_type: Some("mobile".to_string()),
number: "+1234567890".to_string(),
};
assert_eq!(phone.phone_type.as_ref().unwrap(), "mobile");
assert_eq!(phone.number, "+1234567890");
}
#[test]
fn test_contact_address() {
let address = ContactAddress {
address_type: Some("home".to_string()),
street_address: Some("123 Main St".to_string()),
city: Some("Springfield".to_string()),
state: Some("IL".to_string()),
postal_code: Some("62701".to_string()),
country: Some("USA".to_string()),
};
assert_eq!(address.address_type.as_ref().unwrap(), "home");
assert_eq!(address.city.as_ref().unwrap(), "Springfield");
}
}