Skip to content

Commit 56bb4ee

Browse files
committed
add jsonptr as features
1 parent f66dd3e commit 56bb4ee

File tree

6 files changed

+168
-36
lines changed

6 files changed

+168
-36
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ jobs:
2222
run: cargo test --verbose -- --test-threads=1
2323
- name: Run tests untyped
2424
run: cargo test --verbose --no-default-features -- --test-threads=1
25+
- uses: taiki-e/install-action@cargo-hack
26+
- name: Run hack tests
27+
run: cargo hack test --feature-powerset --all-targets

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ exclude = [
1919

2020
[features]
2121
# The "typed" feature is enabled by default
22-
default = ["typed"]
22+
default = ["typed", "jsonptr"]
2323

2424
# The typed feature enables the usage of the @type attributes in jscontact structs
2525
# This can be disabled with { default-features = false } in your Cargo.toml
@@ -30,10 +30,13 @@ default = ["typed"]
3030
# see tests/test_localizations.rs for an example
3131
typed = []
3232

33+
jsonptr = ["dep:jsonptr"]
34+
3335

3436
[dependencies]
3537
serde = { version = "1", features = ["derive"] }
3638
serde_json = "1"
39+
jsonptr = { version = "0.6.3", optional = true }
3740

3841
[dev-dependencies]
3942
serde_json = { version = "1", features = ["preserve_order"] }

src/card.rs

Lines changed: 86 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use serde::{Deserialize, Serialize};
66
use serde_json::Value;
77

88
use crate::{
9-
Address, AddressComponent, AddressComponentKind, Anniversary, Calendar, CardKind, CardVersion,
10-
CryptoKey, Directory, EmailAddress, LanguagePref, Link, Media, Name, NameComponent, Nickname,
11-
Note, OnlineService, Organization, PersonalInfo, Phone, Relation, SchedulingAddress, SpeakToAs,
12-
Title,
9+
Address, Anniversary, Calendar, CardKind, CardVersion, CryptoKey, Directory, EmailAddress,
10+
LanguagePref, Link, Media, Name, Nickname, Note, OnlineService, Organization, PersonalInfo,
11+
Phone, Relation, SchedulingAddress, SpeakToAs, Title,
1312
};
13+
#[cfg(not(feature = "jsonptr"))]
14+
use crate::{AddressComponent, AddressComponentKind, NameComponent};
1415

1516
/// Represents the primary Card object as defined in RFC 9553, storing metadata and contact properties.
1617
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -35,7 +36,7 @@ pub struct Card {
3536
#[serde(skip_serializing_if = "Option::is_none")]
3637
pub kind: Option<CardKind>,
3738
/// The language used in the Card (e.g., en, fr).
38-
/// Not localized.
39+
/// Localized when using [`crate::Card::get_localized`] method.
3940
#[serde(skip_serializing_if = "Option::is_none")]
4041
pub language: Option<String>,
4142
/// Members of a group Card, if applicable.
@@ -266,50 +267,88 @@ impl Card {
266267
};
267268
// iter on localized_lang and set the values
268269
let mut localized_card = self.clone();
269-
for (key, value) in localized_lang.iter() {
270-
// Deliberately not using jsonptr here
271-
if key.starts_with("name") {
272-
localize_name(&mut localized_card, key, value)?;
273-
} else if key.starts_with("titles") {
274-
localize_titles(&mut localized_card, key, value)?;
275-
} else if key.starts_with("addresses") {
276-
localize_addresses(&mut localized_card, key, value)?;
277-
} else if key.starts_with("nicknames") {
278-
localize_nicknames(&mut localized_card, key, value)?;
279-
} else if key.starts_with("personalInfo") {
280-
localize_personal_info(&mut localized_card, key, value)?;
281-
} else if key.starts_with("notes") {
282-
localize_notes(&mut localized_card, key, value)?;
283-
} else if key.starts_with("keywords") {
284-
localize_keywords(&mut localized_card, key, value)?;
285-
} else if key.starts_with("media") {
286-
localize_media(&mut localized_card, key, value)?;
287-
} else if key.starts_with("links") {
288-
localize_links(&mut localized_card, key, value)?;
289-
} else if key.starts_with("directories") {
290-
localize_directories(&mut localized_card, key, value)?;
291-
} else if key.starts_with("calendars") {
292-
localize_calendars(&mut localized_card, key, value)?;
293-
} else if key.starts_with("schedulingAddresses") {
294-
localize_scheduling_addresses(&mut localized_card, key, value)?;
295-
}
296-
}
297270
// remove localizations of the localized card
298271
localized_card.localizations = None;
299272
// set the language of the localized card
300273
localized_card.language = Some(lang);
274+
localize_card(&mut localized_card, localized_lang)?;
301275
Ok(localized_card)
302276
}
303277
}
304278

279+
/// Localize the Card object with jsonptr
280+
#[cfg(feature = "jsonptr")]
281+
fn localize_card(
282+
localized_card: &mut Card,
283+
localized_lang: &HashMap<String, Value>,
284+
) -> Result<(), String> {
285+
use jsonptr::Pointer;
286+
let Ok(mut card_value) = serde_json::to_value(&localized_card) else {
287+
return Err("Failed to convert card to value".into());
288+
};
289+
for (key, value) in localized_lang.iter() {
290+
let key = format!("/{}", key);
291+
let ptr = match Pointer::parse(&key) {
292+
Ok(ptr) => ptr,
293+
Err(e) => return Err(format!("Failed to parse pointer: {}", e)),
294+
};
295+
match ptr.assign(&mut card_value, value.clone()) {
296+
Ok(_) => (),
297+
Err(e) => return Err(format!("Failed to assign value: {}", e)),
298+
}
299+
}
300+
*localized_card = serde_json::from_value(card_value).unwrap();
301+
Ok(())
302+
}
303+
304+
/// Localize the Card object
305+
#[cfg(not(feature = "jsonptr"))]
306+
fn localize_card(
307+
localized_card: &mut Card,
308+
localized_lang: &HashMap<String, Value>,
309+
) -> Result<(), String> {
310+
for (key, value) in localized_lang.iter() {
311+
// Deliberately not using jsonptr here
312+
if key.starts_with("name") {
313+
localize_name(localized_card, key, value)?;
314+
} else if key.starts_with("titles") {
315+
localize_titles(localized_card, key, value)?;
316+
} else if key.starts_with("addresses") {
317+
localize_addresses(localized_card, key, value)?;
318+
} else if key.starts_with("nicknames") {
319+
localize_nicknames(localized_card, key, value)?;
320+
} else if key.starts_with("personalInfo") {
321+
localize_personal_info(localized_card, key, value)?;
322+
} else if key.starts_with("notes") {
323+
localize_notes(localized_card, key, value)?;
324+
} else if key.starts_with("keywords") {
325+
localize_keywords(localized_card, key, value)?;
326+
} else if key.starts_with("media") {
327+
localize_media(localized_card, key, value)?;
328+
} else if key.starts_with("links") {
329+
localize_links(localized_card, key, value)?;
330+
} else if key.starts_with("directories") {
331+
localize_directories(localized_card, key, value)?;
332+
} else if key.starts_with("calendars") {
333+
localize_calendars(localized_card, key, value)?;
334+
} else if key.starts_with("schedulingAddresses") {
335+
localize_scheduling_addresses(localized_card, key, value)?;
336+
}
337+
}
338+
Ok(())
339+
}
340+
305341
/// remove the first character of a string
342+
#[cfg(not(feature = "jsonptr"))]
343+
#[inline]
306344
fn remove_first(s: &str) -> &str {
307345
let mut chars = s.chars();
308346
chars.next();
309347
chars.as_str()
310348
}
311349

312350
/// Localize the [`crate::Name`]
351+
#[cfg(not(feature = "jsonptr"))]
313352
fn localize_name(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
314353
if key == "name" {
315354
card.name = serde_json::from_value(value.clone()).ok();
@@ -323,6 +362,7 @@ fn localize_name(card: &mut Card, key: &str, value: &Value) -> Result<(), String
323362
if key.starts_with("components") {
324363
if key == "components" {
325364
curr_name.components = serde_json::from_value(value.clone()).ok();
365+
card.name = Some(curr_name.clone());
326366
return Ok(());
327367
}
328368
let components = match &mut curr_name.components {
@@ -363,6 +403,7 @@ fn localize_name(card: &mut Card, key: &str, value: &Value) -> Result<(), String
363403
}
364404

365405
/// Localize the [`crate::Titles`]
406+
#[cfg(not(feature = "jsonptr"))]
366407
fn localize_titles(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
367408
if key == "titles" {
368409
card.titles = serde_json::from_value(value.clone()).ok();
@@ -410,6 +451,7 @@ fn localize_titles(card: &mut Card, key: &str, value: &Value) -> Result<(), Stri
410451
}
411452

412453
/// Localize the [`crate::Addresses`]
454+
#[cfg(not(feature = "jsonptr"))]
413455
fn localize_addresses(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
414456
let full_key = key;
415457
if key == "addresses" {
@@ -508,6 +550,7 @@ fn localize_addresses(card: &mut Card, key: &str, value: &Value) -> Result<(), S
508550
}
509551

510552
/// Localize the [`crate::Nicknames`]
553+
#[cfg(not(feature = "jsonptr"))]
511554
fn localize_nicknames(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
512555
if key == "nicknames" {
513556
card.nicknames = serde_json::from_value(value.clone()).ok();
@@ -558,6 +601,7 @@ fn localize_nicknames(card: &mut Card, key: &str, value: &Value) -> Result<(), S
558601
}
559602

560603
/// Localize the [`crate::PersonalInfos`]
604+
#[cfg(not(feature = "jsonptr"))]
561605
fn localize_personal_info(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
562606
if key == "personalInfo" {
563607
card.personal_info = serde_json::from_value(value.clone()).ok();
@@ -613,6 +657,7 @@ fn localize_personal_info(card: &mut Card, key: &str, value: &Value) -> Result<(
613657
}
614658

615659
/// Localize the [`crate::Notes`]
660+
#[cfg(not(feature = "jsonptr"))]
616661
fn localize_notes(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
617662
if key == "notes" {
618663
card.notes = serde_json::from_value(value.clone()).ok();
@@ -667,7 +712,8 @@ fn localize_notes(card: &mut Card, key: &str, value: &Value) -> Result<(), Strin
667712
Ok(())
668713
}
669714

670-
/// Localize the [`crate::Keywords`]
715+
/// Localize the Keywords
716+
#[cfg(not(feature = "jsonptr"))]
671717
fn localize_keywords(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
672718
if key == "keywords" {
673719
card.keywords = serde_json::from_value(value.clone()).ok();
@@ -677,6 +723,7 @@ fn localize_keywords(card: &mut Card, key: &str, value: &Value) -> Result<(), St
677723
}
678724

679725
/// Localize the [`crate::Media`]
726+
#[cfg(not(feature = "jsonptr"))]
680727
fn localize_media(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
681728
if key == "media" {
682729
card.media = serde_json::from_value(value.clone()).ok();
@@ -744,7 +791,8 @@ fn localize_media(card: &mut Card, key: &str, value: &Value) -> Result<(), Strin
744791
Ok(())
745792
}
746793

747-
/// Localize the [`crate::Links`]
794+
/// Localize the Links
795+
#[cfg(not(feature = "jsonptr"))]
748796
fn localize_links(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
749797
if key == "links" {
750798
card.links = serde_json::from_value(value.clone()).ok();
@@ -807,6 +855,7 @@ fn localize_links(card: &mut Card, key: &str, value: &Value) -> Result<(), Strin
807855
}
808856

809857
/// Localize the [`crate::Directory`]
858+
#[cfg(not(feature = "jsonptr"))]
810859
fn localize_directories(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
811860
if key == "directories" {
812861
card.directories = serde_json::from_value(value.clone()).ok();
@@ -876,6 +925,7 @@ fn localize_directories(card: &mut Card, key: &str, value: &Value) -> Result<(),
876925
}
877926

878927
/// Localize the [`crate::Calendar`]
928+
#[cfg(not(feature = "jsonptr"))]
879929
fn localize_calendars(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
880930
if key == "calendars" {
881931
card.calendars = serde_json::from_value(value.clone()).ok();
@@ -940,6 +990,7 @@ fn localize_calendars(card: &mut Card, key: &str, value: &Value) -> Result<(), S
940990
}
941991

942992
/// Localize the [`crate::SchedulingAddress`]
993+
#[cfg(not(feature = "jsonptr"))]
943994
fn localize_scheduling_addresses(card: &mut Card, key: &str, value: &Value) -> Result<(), String> {
944995
if key == "schedulingAddresses" {
945996
card.scheduling_addresses = serde_json::from_value(value.clone()).ok();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"@type": "Card",
3+
"version": "1.0",
4+
"uid": "1234",
5+
"name": {
6+
"full": "Okubo Masahito Motohiro"
7+
},
8+
"localizations": {
9+
"en": {
10+
"name": {
11+
"components": [
12+
{
13+
"kind": "given",
14+
"value": "Masahito"
15+
},
16+
{
17+
"kind": "given2",
18+
"value": "Okubo"
19+
}
20+
]
21+
}
22+
}
23+
}
24+
}

tests/test_localizations.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,44 @@ mod test {
237237
Ok(())
238238
}
239239

240+
#[test]
241+
fn test_localizations_name_components_2() -> Result<(), Box<dyn std::error::Error>> {
242+
let json = serde_json::json!({
243+
"@type": "Card",
244+
"version": "1.0",
245+
"uid": "1234",
246+
"name": {
247+
"full": "Okubo Masahito Motohiro"
248+
},
249+
"localizations": {
250+
"en": {
251+
"name": {
252+
"components": [
253+
{ "kind": "given", "value": "Masahito" },
254+
{ "kind": "given2", "value": "Okubo" }
255+
],
256+
}
257+
}
258+
}
259+
});
260+
std::fs::write(
261+
"tests/localizations/test_localizations_name_components_2.json",
262+
serde_json::to_string_pretty(&json)?,
263+
)?;
264+
let card: Card = serde_json::from_value(json)?;
265+
266+
let localizations = card.get_localized("en")?;
267+
let name = localizations.name.unwrap();
268+
let components = name.components.as_ref().unwrap();
269+
assert_eq!(components.len(), 2);
270+
assert_eq!(components[0].kind, NameComponentKind::Given);
271+
assert_eq!(components[0].value, "Masahito");
272+
assert_eq!(components[1].kind, NameComponentKind::Given2);
273+
assert_eq!(components[1].value, "Okubo");
274+
assert_eq!(name.full, None);
275+
Ok(())
276+
}
277+
240278
#[test]
241279
fn test_localizations_name_components_path_object_1() -> Result<(), Box<dyn std::error::Error>>
242280
{
@@ -672,6 +710,7 @@ mod test {
672710
/// The card in the test is not valid as the RFC states
673711
/// Because we cannot replace inexistent components
674712
/// But we still handle it
713+
#[cfg(not(feature = "jsonptr"))]
675714
#[test]
676715
fn test_localizations_addresses_path_object_3_invalid() -> Result<(), Box<dyn std::error::Error>>
677716
{
@@ -756,6 +795,7 @@ mod test {
756795
/// The card in the test is not valid as the RFC states
757796
/// Because we cannot replace inexistent components
758797
/// But we still handle it
798+
#[cfg(not(feature = "jsonptr"))]
759799
#[test]
760800
fn test_localizations_addresses_path_object_4_invalid() -> Result<(), Box<dyn std::error::Error>>
761801
{

0 commit comments

Comments
 (0)