diff --git a/melib/src/addressbook.rs b/melib/src/addressbook.rs index 08b5db05..c2c1a647 100644 --- a/melib/src/addressbook.rs +++ b/melib/src/addressbook.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +pub mod jscontact; pub mod mutt; #[cfg(feature = "vcard")] pub mod vcard; diff --git a/melib/src/addressbook/jscontact.rs b/melib/src/addressbook/jscontact.rs new file mode 100644 index 00000000..b682a581 --- /dev/null +++ b/melib/src/addressbook/jscontact.rs @@ -0,0 +1,268 @@ +// +// meli +// +// Copyright 2024 Emmanouil Pitsidianakis +// +// This file is part of meli. +// +// meli is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// meli is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with meli. If not, see . +// +// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later + +use std::hash::Hasher; + +use crate::{ + addressbook::{Card, CardId}, + error::Result, +}; + +/// Supported `JSContact` versions +pub trait JSContactVersion: std::fmt::Debug {} + +#[derive(Debug)] +pub struct JSContactVersionUnknown; +impl JSContactVersion for JSContactVersionUnknown {} + +/// Version 1 +#[derive(Debug)] +pub struct JSContactVersion1; +impl JSContactVersion for JSContactVersion1 {} + +pub struct CardDeserializer; + +#[derive(Debug)] +pub struct JSContact( + json_types::JsonCardValue, + std::marker::PhantomData<*const T>, +); + +impl JSContact { + pub fn new_v1() -> JSContact { + JSContact( + json_types::JsonCardValue::default(), + std::marker::PhantomData::<*const JSContactVersion1>, + ) + } +} + +impl CardDeserializer { + pub fn try_from_str(input: &str) -> Result> { + let inner: json_types::JsonCardValue = serde_json::from_str(input)?; + + Ok(JSContact(inner, std::marker::PhantomData)) + } +} + +impl TryInto for JSContact { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + let mut card = Card::new(); + card.set_id(CardId::Hash({ + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + hasher.write(self.0.uid.as_bytes()); + hasher.finish() + })); + card.set_name(self.0.name.full.clone().unwrap_or_default()); + if let Some(e) = self.0.email.get_index(0) { + card.set_email(e.1.address.to_string()); + } + + Ok(card) + } +} + +pub mod json_types { + use indexmap::IndexMap; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + 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(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str($s) + } + } + + impl<'de> Deserialize<'de> for $t { + fn deserialize(deserializer: D) -> std::result::Result + 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(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str("1.0") + } + } + + impl<'de> Deserialize<'de> for JsonCardVersion { + fn deserialize(deserializer: D) -> std::result::Result + 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, Deserialize, Eq, PartialEq, Serialize)] + #[serde(rename_all = "lowercase")] + pub enum JsonCardKind { + #[default] + /// A single person + Individual, + /// A group of people or entities + Group, + /// an organization + Org, + /// A named location + Location, + /// A device such as an appliance, a computer, or a network element + Device, + /// A software application + 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, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, + pub name: JsonCardName, + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub email: IndexMap, + } + + #[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, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub components: Vec<()>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_ordered: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_separator: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub full: Option, + } + + #[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, + pub address: String, + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub contexts: IndexMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pref: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + } + + #[test] + fn test_addressbook_jscontact() { + assert_eq!( + JsonCardValue { + __type: JsonCardType, + version: JsonCardVersion::_1_0, + uid: "22B2C7DF-9120-4969-8460-05956FE6B065".to_string(), + created: 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(), + ); + } +} diff --git a/melib/src/addressbook/vcard.rs b/melib/src/addressbook/vcard.rs index 7bd04a2c..89fe7b1c 100644 --- a/melib/src/addressbook/vcard.rs +++ b/melib/src/addressbook/vcard.rs @@ -185,7 +185,7 @@ impl CardDeserializer { impl TryInto for VCard { type Error = crate::error::Error; - fn try_into(mut self) -> crate::error::Result { + fn try_into(mut self) -> Result { let mut card = Card::new(); card.set_id(CardId::Hash({ let mut hasher = std::collections::hash_map::DefaultHasher::new(); diff --git a/melib/src/error.rs b/melib/src/error.rs index 4c852674..29814645 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -523,7 +523,6 @@ impl From for Error { } } -#[cfg(feature = "jmap")] impl From for Error { #[inline] fn from(err: serde_json::error::Error) -> Self {