Skip to content

Commit a6c7621

Browse files
committed
jscontact: add {created,updated} fields
Signed-off-by: Manos Pitsidianakis <[email protected]>
1 parent 7dee32a commit a6c7621

File tree

3 files changed

+120
-12
lines changed

3 files changed

+120
-12
lines changed

melib/src/contacts/jscontact.rs

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
//
2121
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
2222

23-
use std::hash::Hasher;
24-
2523
use crate::{
2624
contacts::{Card, CardId},
2725
error::Result,
@@ -41,7 +39,7 @@ impl JSContactVersion for JSContactVersion1 {}
4139

4240
pub struct CardDeserializer;
4341

44-
#[derive(Debug)]
42+
#[derive(Clone, Debug, Eq, PartialEq)]
4543
pub struct JSContact<T: JSContactVersion>(
4644
json_types::JsonCardValue,
4745
std::marker::PhantomData<*const T>,
@@ -68,25 +66,34 @@ impl<V: JSContactVersion> TryInto<Card> for JSContact<V> {
6866
type Error = crate::error::Error;
6967

7068
fn try_into(self) -> Result<Card> {
69+
let json_types::JsonCardValue {
70+
uid, name, email, ..
71+
} = self.0;
7172
let mut card = Card::new();
72-
card.set_id(CardId::Hash({
73-
let mut hasher = std::collections::hash_map::DefaultHasher::new();
74-
hasher.write(self.0.uid.as_bytes());
75-
hasher.finish()
76-
}));
77-
card.set_name(self.0.name.full.clone().unwrap_or_default());
78-
if let Some(e) = self.0.email.get_index(0) {
73+
card.set_id(CardId::from(uid));
74+
if let Some(name) = name.full {
75+
card.set_name(name);
76+
}
77+
if let Some(e) = email.get_index(0) {
7978
card.set_email(e.1.address.to_string());
8079
}
8180

8281
Ok(card)
8382
}
8483
}
8584

85+
impl From<json_types::JsonCardValue> for JSContact<JSContactVersion1> {
86+
fn from(val: json_types::JsonCardValue) -> Self {
87+
Self(val, std::marker::PhantomData::<*const JSContactVersion1>)
88+
}
89+
}
90+
8691
pub mod json_types {
8792
use indexmap::IndexMap;
8893
use serde::{Deserialize, Deserializer, Serialize, Serializer};
8994

95+
use crate::utils::datetime::UnixTimestamp;
96+
9097
macro_rules! impl_json_type_struct_serde {
9198
($t:tt, $s:literal) => {
9299
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
@@ -155,6 +162,44 @@ pub mod json_types {
155162
}
156163
}
157164

165+
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
166+
pub struct UTCDateTime(pub UnixTimestamp);
167+
168+
impl Serialize for UTCDateTime {
169+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
170+
where
171+
S: Serializer,
172+
{
173+
use crate::utils::datetime::{formats::RFC3339_DATETIME_Z, timestamp_to_string_utc};
174+
175+
serializer.serialize_str(&timestamp_to_string_utc(
176+
self.0,
177+
Some(RFC3339_DATETIME_Z),
178+
true,
179+
))
180+
}
181+
}
182+
183+
impl<'de> Deserialize<'de> for UTCDateTime {
184+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
185+
where
186+
D: Deserializer<'de>,
187+
{
188+
use crate::utils::datetime::{
189+
formats::RFC3339_DATETIME_Z, parse_timestamp_from_string,
190+
};
191+
192+
let s = <&'de str>::deserialize(deserializer)?;
193+
let Ok((_, val)) = parse_timestamp_from_string(s, RFC3339_DATETIME_Z) else {
194+
return Err(serde::de::Error::custom(format!(
195+
r#"expected UTCDateTime value, found `{}`"#,
196+
s
197+
)));
198+
};
199+
Ok(Self(val))
200+
}
201+
}
202+
158203
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
159204
#[serde(rename_all = "lowercase")]
160205
pub enum JsonCardKind {
@@ -181,7 +226,14 @@ pub mod json_types {
181226
pub version: JsonCardVersion,
182227
pub uid: String,
183228
#[serde(default, skip_serializing_if = "Option::is_none")]
184-
pub created: Option<String>,
229+
pub created: Option<UTCDateTime>,
230+
#[serde(default, skip_serializing_if = "Option::is_none")]
231+
pub updated: Option<UTCDateTime>,
232+
#[serde(default, skip_serializing_if = "Option::is_none")]
233+
/// The language tag, as defined in `RFC5646`, that best describes the
234+
/// language used for text in the card, optionally including
235+
/// additional information such as the script.
236+
pub language: Option<String>,
185237
#[serde(default, skip_serializing_if = "Option::is_none")]
186238
pub kind: Option<JsonCardKind>,
187239
pub name: JsonCardName,
@@ -220,12 +272,17 @@ pub mod json_types {
220272

221273
#[test]
222274
fn test_addressbook_jscontact() {
275+
use super::JSContactVersion1;
276+
use crate::contacts::{jscontact::JSContact, Card, CardId};
277+
223278
assert_eq!(
224279
JsonCardValue {
225280
__type: JsonCardType,
226281
version: JsonCardVersion::_1_0,
227282
uid: "22B2C7DF-9120-4969-8460-05956FE6B065".to_string(),
228283
created: None,
284+
updated: None,
285+
language: None,
229286
kind: Some(JsonCardKind::Individual),
230287
name: JsonCardName {
231288
__type: None,
@@ -264,5 +321,52 @@ pub mod json_types {
264321
)
265322
.unwrap(),
266323
);
324+
assert_eq!(
325+
Card {
326+
last_edited: 1727155810,
327+
..<JSContact<JSContactVersion1> as std::convert::TryInto<Card>>::try_into(
328+
JSContact::<JSContactVersion1>::from(
329+
serde_json::from_str::<JsonCardValue>(
330+
r#"{
331+
"@type": "Card",
332+
"version": "1.0",
333+
"uid": "22B2C7DF-9120-4969-8460-05956FE6B065",
334+
"kind": "individual",
335+
"email": {
336+
"main": {
337+
"address": "[email protected]"
338+
}
339+
},
340+
"name": {
341+
"components": [],
342+
"full": "full_name",
343+
"isOrdered": true
344+
}
345+
}"#
346+
)
347+
.unwrap()
348+
)
349+
)
350+
.unwrap()
351+
},
352+
Card {
353+
id: CardId::Uuid(
354+
uuid::Uuid::try_parse("22B2C7DF-9120-4969-8460-05956FE6B065").unwrap()
355+
),
356+
title: "".into(),
357+
name: "full_name".into(),
358+
additionalname: "".into(),
359+
name_prefix: "".into(),
360+
name_suffix: "".into(),
361+
birthday: None,
362+
email: "[email protected]".into(),
363+
url: "".into(),
364+
key: "".into(),
365+
color: 0,
366+
last_edited: 1727155810,
367+
extra_properties: indexmap::indexmap! {},
368+
external_resource: false
369+
},
370+
);
267371
}
268372
}

melib/src/contacts/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl From<String> for CardId {
6767
fn from(s: String) -> Self {
6868
use std::{collections::hash_map::DefaultHasher, str::FromStr};
6969

70-
if let Ok(u) = Uuid::parse_str(s.as_str()) {
70+
if let Ok(u) = Uuid::try_parse(s.as_str()) {
7171
Self::Uuid(u)
7272
} else if let Ok(num) = u64::from_str(s.trim()) {
7373
Self::Hash(num)

melib/src/utils/datetime.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ use crate::error::{ErrorKind, Result, ResultIntoError};
4949
pub type UnixTimestamp = u64;
5050

5151
pub mod formats {
52+
//! Format string constants for use with `strftime`/`strptime`.
53+
5254
/// `<date>`T`<time>`
5355
pub const RFC3339_DATETIME: &str = "%Y-%m-%dT%H:%M:%S\0";
56+
/// `<date>`T`<time>Z`
57+
pub const RFC3339_DATETIME_Z: &str = "%Y-%m-%dT%H:%M:%SZ\0";
5458
/// `<date>`T`<time>`
5559
pub const RFC3339_DATETIME_AND_SPACE: &str = "%Y-%m-%d %H:%M:%S\0";
5660

0 commit comments

Comments
 (0)