use crate::{
contacts::{Card, CardId},
error::Result,
};
pub trait JSContactVersion: std::fmt::Debug {}
#[derive(Debug)]
pub struct JSContactVersionUnknown;
impl JSContactVersion for JSContactVersionUnknown {}
#[derive(Debug)]
pub struct JSContactVersion1;
impl JSContactVersion for JSContactVersion1 {}
pub struct CardDeserializer;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct JSContact<T: JSContactVersion>(
json_types::JsonCardValue,
std::marker::PhantomData<*const T>,
);
impl<V: JSContactVersion> JSContact<V> {
pub fn new_v1() -> JSContact<impl JSContactVersion> {
JSContact(
json_types::JsonCardValue::default(),
std::marker::PhantomData::<*const JSContactVersion1>,
)
}
}
impl CardDeserializer {
pub fn try_from_str(input: &str) -> Result<JSContact<JSContactVersion1>> {
let inner: json_types::JsonCardValue = serde_json::from_str(input)?;
Ok(JSContact(inner, std::marker::PhantomData))
}
}
impl<V: JSContactVersion> TryInto<Card> for JSContact<V> {
type Error = crate::error::Error;
fn try_into(self) -> Result<Card> {
let json_types::JsonCardValue {
uid, name, email, ..
} = self.0;
let mut card = Card::new();
card.set_id(CardId::from(uid));
if let Some(name) = name.full {
card.set_name(name);
}
if let Some(e) = email.get_index(0) {
card.set_email(e.1.address.to_string());
}
Ok(card)
}
}
impl From<json_types::JsonCardValue> for JSContact<JSContactVersion1> {
fn from(val: json_types::JsonCardValue) -> Self {
Self(val, std::marker::PhantomData::<*const JSContactVersion1>)
}
}
pub mod json_types {
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::utils::datetime::UnixTimestamp;
macro_rules! impl_json_type_struct_serde {
($t:tt, $s:literal) => {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
struct $t;
impl Serialize for $t {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str($s)
}
}
impl<'de> Deserialize<'de> for $t {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <&'de str>::deserialize(deserializer)?;
if s != $s {
return Err(serde::de::Error::custom(format!(
concat!(r#"expected @type value ""#, $s, ", found `{}`"),
s
)));
}
Ok(Self)
}
}
};
}
impl_json_type_struct_serde! {JsonCardType, "Card"}
impl_json_type_struct_serde! {JsonNameType, "Name"}
impl_json_type_struct_serde! {JsonEmailAddressType, "EmailAddress"}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum JsonCardVersion {
#[default]
_1_0,
}
impl Serialize for JsonCardVersion {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str("1.0")
}
}
impl<'de> Deserialize<'de> for JsonCardVersion {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <&'de str>::deserialize(deserializer)?;
if s != "1.0" {
return Err(serde::de::Error::custom(format!(
r#"expected version value "1.0", found `{s}`"#
)));
}
Ok(Self::_1_0)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct UTCDateTime(pub UnixTimestamp);
impl Serialize for UTCDateTime {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
use crate::utils::datetime::{formats::RFC3339_DATETIME_Z, timestamp_to_string_utc};
serializer.serialize_str(×tamp_to_string_utc(
self.0,
Some(RFC3339_DATETIME_Z),
true,
))
}
}
impl<'de> Deserialize<'de> for UTCDateTime {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use crate::utils::datetime::{
formats::RFC3339_DATETIME_Z, parse_timestamp_from_string,
};
let s = <&'de str>::deserialize(deserializer)?;
let Ok((_, val)) = parse_timestamp_from_string(s, RFC3339_DATETIME_Z) else {
return Err(serde::de::Error::custom(format!(
r#"expected UTCDateTime value, found `{s}`"#
)));
};
Ok(Self(val))
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum JsonCardKind {
#[default]
Individual,
Group,
Org,
Location,
Device,
Application,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonCardValue {
#[serde(rename = "@type")]
__type: JsonCardType,
pub version: JsonCardVersion,
pub uid: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created: Option<UTCDateTime>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub updated: Option<UTCDateTime>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<JsonCardKind>,
pub name: JsonCardName,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub email: IndexMap<String, JsonCardEmailAddress>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonCardName {
#[serde(rename = "@type", default, skip_serializing_if = "Option::is_none")]
__type: Option<JsonNameType>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub components: Vec<()>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_ordered: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_separator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub full: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonCardEmailAddress {
#[serde(rename = "@type", default, skip_serializing_if = "Option::is_none")]
__type: Option<JsonEmailAddressType>,
pub address: String,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub contexts: IndexMap<String, bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pref: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[test]
fn test_addressbook_jscontact() {
use super::JSContactVersion1;
use crate::contacts::{jscontact::JSContact, Card, CardId};
assert_eq!(
JsonCardValue {
__type: JsonCardType,
version: JsonCardVersion::_1_0,
uid: "22B2C7DF-9120-4969-8460-05956FE6B065".to_string(),
created: None,
updated: None,
language: None,
kind: Some(JsonCardKind::Individual),
name: JsonCardName {
__type: None,
components: vec![],
full: Some("full_name".to_string()),
is_ordered: Some(true),
default_separator: None,
},
email: indexmap! {
"main".to_string() => JsonCardEmailAddress {
__type: None,
address: "user@example.com".to_string(),
contexts: IndexMap::new(),
pref:None,
label: None,
}
}
},
serde_json::from_str(
r#"{
"@type": "Card",
"version": "1.0",
"uid": "22B2C7DF-9120-4969-8460-05956FE6B065",
"kind": "individual",
"email": {
"main": {
"address": "user@example.com"
}
},
"name": {
"components": [],
"full": "full_name",
"isOrdered": true
}
}"#
)
.unwrap(),
);
assert_eq!(
Card {
last_edited: 1727155810,
..<JSContact<JSContactVersion1> as std::convert::TryInto<Card>>::try_into(
JSContact::<JSContactVersion1>::from(
serde_json::from_str::<JsonCardValue>(
r#"{
"@type": "Card",
"version": "1.0",
"uid": "22B2C7DF-9120-4969-8460-05956FE6B065",
"kind": "individual",
"email": {
"main": {
"address": "user@example.com"
}
},
"name": {
"components": [],
"full": "full_name",
"isOrdered": true
}
}"#
)
.unwrap()
)
)
.unwrap()
},
Card {
id: CardId::Uuid(
uuid::Uuid::try_parse("22B2C7DF-9120-4969-8460-05956FE6B065").unwrap()
),
title: "".into(),
name: "full_name".into(),
additionalname: "".into(),
name_prefix: "".into(),
name_suffix: "".into(),
birthday: None,
email: "user@example.com".into(),
url: "".into(),
key: "".into(),
color: 0,
last_edited: 1727155810,
extra_properties: indexmap::indexmap! {},
external_resource: false
},
);
}
}