//! Row structs for the five contact tables and their conversions to core models. //! //! Leaf module: it knows rusqlite and the core models, and nothing else in the crate. use goingson_core::{ Contact, ContactCustomField, ContactEmail, ContactPhone, CoreError, Result, SocialHandle, }; use crate::utils::{parse_datetime, parse_tags, parse_uuid}; // Row Structs #[derive(Debug, Clone)] pub(super) struct ContactRow { pub id: String, pub display_name: String, pub nickname: Option, pub company: Option, pub title: Option, pub notes: String, pub tags: String, pub birthday: Option, pub timezone: Option, pub external_source: Option, pub external_id: Option, pub is_implicit: i32, pub created_at: String, pub updated_at: String, } impl ContactRow { pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, display_name: row.get("display_name")?, nickname: row.get("nickname")?, company: row.get("company")?, title: row.get("title")?, notes: row.get("notes")?, tags: row.get("tags")?, birthday: row.get("birthday")?, timezone: row.get("timezone")?, external_source: row.get("external_source")?, external_id: row.get("external_id")?, is_implicit: row.get("is_implicit")?, created_at: row.get("created_at")?, updated_at: row.get("updated_at")?, }) } } #[derive(Debug, Clone)] pub(super) struct ContactEmailRow { pub id: String, pub contact_id: String, pub address: String, pub label: String, pub is_primary: i32, } impl ContactEmailRow { pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, contact_id: row.get("contact_id")?, address: row.get("address")?, label: row.get("label")?, is_primary: row.get("is_primary")?, }) } } #[derive(Debug, Clone)] pub(super) struct ContactPhoneRow { pub id: String, pub contact_id: String, pub number: String, pub label: String, pub is_primary: i32, } impl ContactPhoneRow { pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, contact_id: row.get("contact_id")?, number: row.get("number")?, label: row.get("label")?, is_primary: row.get("is_primary")?, }) } } #[derive(Debug, Clone)] pub(super) struct SocialHandleRow { pub id: String, pub contact_id: String, pub platform: String, pub handle: String, pub url: Option, } impl SocialHandleRow { pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, contact_id: row.get("contact_id")?, platform: row.get("platform")?, handle: row.get("handle")?, url: row.get("url")?, }) } } #[derive(Debug, Clone)] pub(super) struct CustomFieldRow { pub id: String, pub contact_id: String, pub label: String, pub value: String, pub url: Option, } impl CustomFieldRow { pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, contact_id: row.get("contact_id")?, label: row.get("label")?, value: row.get("value")?, url: row.get("url")?, }) } } // Row Conversions pub(super) fn contact_email_from_row(row: ContactEmailRow) -> Result { Ok(ContactEmail { id: parse_uuid(&row.id)?.into(), contact_id: parse_uuid(&row.contact_id)?.into(), address: row.address, label: row.label, is_primary: row.is_primary != 0, }) } pub(super) fn contact_phone_from_row(row: ContactPhoneRow) -> Result { Ok(ContactPhone { id: parse_uuid(&row.id)?.into(), contact_id: parse_uuid(&row.contact_id)?.into(), number: row.number, label: row.label, is_primary: row.is_primary != 0, }) } pub(super) fn social_handle_from_row(row: SocialHandleRow) -> Result { Ok(SocialHandle { id: parse_uuid(&row.id)?.into(), contact_id: parse_uuid(&row.contact_id)?.into(), platform: row.platform, handle: row.handle, url: row.url, }) } pub(super) fn custom_field_from_row(row: CustomFieldRow) -> Result { Ok(ContactCustomField { id: parse_uuid(&row.id)?.into(), contact_id: parse_uuid(&row.contact_id)?.into(), label: row.label, value: row.value, url: row.url, }) } pub(super) fn contact_from_row( row: ContactRow, emails: Vec, phones: Vec, social_handles: Vec, custom_fields: Vec, ) -> Result { let birthday = row .birthday .as_deref() .map(|s| { chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") .map_err(|e| CoreError::database_msg(format!("Invalid birthday: {e}"))) }) .transpose()?; Ok(Contact { id: parse_uuid(&row.id)?.into(), display_name: row.display_name, nickname: row.nickname, company: row.company, title: row.title, notes: row.notes, tags: parse_tags(&row.tags), birthday, timezone: row.timezone, external_source: row.external_source, external_id: row.external_id, is_implicit: row.is_implicit != 0, emails, phones, social_handles, custom_fields, created_at: parse_datetime(&row.created_at)?, updated_at: parse_datetime(&row.updated_at)?, }) }