Skip to content

Commit ab363fb

Browse files
authored
Implement Serialize and Deserialize for Locale (#6829)
Fixes #6824
1 parent b4a9f8a commit ab363fb

File tree

3 files changed

+104
-25
lines changed

3 files changed

+104
-25
lines changed

components/locale_core/src/langid.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ use alloc::borrow::Cow;
4040
/// This operation normalizes syntax to be well-formed. No legacy subtag replacements is performed.
4141
/// For validation and canonicalization, see `LocaleCanonicalizer`.
4242
///
43+
/// # Serde
44+
///
45+
/// This type implements `serde::Serialize` and `serde::Deserialize` if the
46+
/// `"serde"` Cargo feature is enabled on the crate.
47+
///
48+
/// The value will be serialized as a string and parsed when deserialized.
49+
/// For tips on efficient storage and retrieval of locales, see [`crate::zerovec`].
50+
///
4351
/// # Examples
4452
///
4553
/// Simple example:

components/locale_core/src/locale.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ use core::str::FromStr;
4646
/// ICU4X's Locale parsing does not allow for non-BCP-47-compatible locales [allowed by UTS 35 for backwards compatability][tr35-bcp].
4747
/// Furthermore, it currently does not allow for language tags to have more than three characters.
4848
///
49+
/// # Serde
50+
///
51+
/// This type implements `serde::Serialize` and `serde::Deserialize` if the
52+
/// `"serde"` Cargo feature is enabled on the crate.
53+
///
54+
/// The value will be serialized as a string and parsed when deserialized.
55+
/// For tips on efficient storage and retrieval of locales, see [`crate::zerovec`].
56+
///
4957
/// # Examples
5058
///
5159
/// Simple example:

components/locale_core/src/serde.rs

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,72 @@
22
// called LICENSE at the top level of the ICU4X source tree
33
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
44

5-
use crate::LanguageIdentifier;
5+
use crate::{LanguageIdentifier, Locale};
6+
use core::{fmt::Display, marker::PhantomData, str::FromStr};
67
use serde::{Deserialize, Deserializer, Serialize, Serializer};
8+
use writeable::Writeable;
79

810
impl Serialize for LanguageIdentifier {
911
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1012
where
1113
S: Serializer,
1214
{
13-
serializer.serialize_str(&self.to_string())
15+
serializer.serialize_str(&self.write_to_string())
1416
}
1517
}
1618

17-
impl<'de> Deserialize<'de> for LanguageIdentifier {
18-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
19+
impl Serialize for Locale {
20+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1921
where
20-
D: Deserializer<'de>,
22+
S: Serializer,
2123
{
22-
struct LanguageIdentifierVisitor;
24+
serializer.serialize_str(&self.write_to_string())
25+
}
26+
}
2327

24-
impl serde::de::Visitor<'_> for LanguageIdentifierVisitor {
25-
type Value = LanguageIdentifier;
28+
struct ParseVisitor<T>(PhantomData<T>);
2629

27-
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28-
write!(formatter, "a valid Unicode Language Identifier")
29-
}
30+
impl<T> serde::de::Visitor<'_> for ParseVisitor<T>
31+
where
32+
T: FromStr,
33+
<T as FromStr>::Err: Display,
34+
{
35+
type Value = T;
3036

31-
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
32-
where
33-
E: serde::de::Error,
34-
{
35-
s.parse::<LanguageIdentifier>()
36-
.map_err(serde::de::Error::custom)
37-
}
38-
}
37+
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38+
write!(formatter, "a valid Unicode Language or Locale Identifier")
39+
}
40+
41+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
42+
where
43+
E: serde::de::Error,
44+
{
45+
s.parse::<T>().map_err(serde::de::Error::custom)
46+
}
47+
}
3948

40-
deserializer.deserialize_string(LanguageIdentifierVisitor)
49+
impl<'de> Deserialize<'de> for LanguageIdentifier {
50+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51+
where
52+
D: Deserializer<'de>,
53+
{
54+
deserializer.deserialize_str(ParseVisitor(PhantomData))
55+
}
56+
}
57+
58+
impl<'de> Deserialize<'de> for Locale {
59+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60+
where
61+
D: Deserializer<'de>,
62+
{
63+
deserializer.deserialize_str(ParseVisitor(PhantomData))
4164
}
4265
}
4366

4467
#[test]
4568
fn json() {
46-
use crate::langid;
4769
use crate::subtags::{Language, Region, Script};
70+
use crate::{langid, locale};
4871

4972
assert_eq!(
5073
serde_json::to_string(&langid!("en-US")).unwrap(),
@@ -54,8 +77,26 @@ fn json() {
5477
serde_json::from_str::<LanguageIdentifier>(r#""en-US""#).unwrap(),
5578
langid!("en-US")
5679
);
80+
assert_eq!(
81+
serde_json::from_reader::<_, LanguageIdentifier>(&br#""en-US""#[..]).unwrap(),
82+
langid!("en-US")
83+
);
5784
assert!(serde_json::from_str::<LanguageIdentifier>(r#""2Xs""#).is_err());
5885

86+
assert_eq!(
87+
serde_json::to_string(&locale!("en-US-u-hc-h12")).unwrap(),
88+
r#""en-US-u-hc-h12""#
89+
);
90+
assert_eq!(
91+
serde_json::from_str::<Locale>(r#""en-US-u-hc-h12""#).unwrap(),
92+
locale!("en-US-u-hc-h12")
93+
);
94+
assert_eq!(
95+
serde_json::from_reader::<_, Locale>(&br#""en-US-u-hc-h12""#[..]).unwrap(),
96+
locale!("en-US-u-hc-h12")
97+
);
98+
assert!(serde_json::from_str::<Locale>(r#""2Xs""#).is_err());
99+
59100
assert_eq!(
60101
serde_json::to_string(&"fr".parse::<Language>().unwrap()).unwrap(),
61102
r#""fr""#
@@ -64,6 +105,10 @@ fn json() {
64105
serde_json::from_str::<Language>(r#""fr""#).unwrap(),
65106
"fr".parse::<Language>().unwrap()
66107
);
108+
assert_eq!(
109+
serde_json::from_reader::<_, Language>(&br#""fr""#[..]).unwrap(),
110+
"fr".parse::<Language>().unwrap()
111+
);
67112
assert!(serde_json::from_str::<Language>(r#""2Xs""#).is_err());
68113

69114
assert_eq!(
@@ -74,6 +119,10 @@ fn json() {
74119
serde_json::from_str::<Script>(r#""Latn""#).unwrap(),
75120
"Latn".parse::<Script>().unwrap()
76121
);
122+
assert_eq!(
123+
serde_json::from_reader::<_, Script>(&br#""Latn""#[..]).unwrap(),
124+
"Latn".parse::<Script>().unwrap()
125+
);
77126
assert!(serde_json::from_str::<Script>(r#""2Xs""#).is_err());
78127

79128
assert_eq!(
@@ -84,23 +133,37 @@ fn json() {
84133
serde_json::from_str::<Region>(r#""US""#).unwrap(),
85134
"US".parse::<Region>().unwrap()
86135
);
136+
assert_eq!(
137+
serde_json::from_reader::<_, Region>(&br#""US""#[..]).unwrap(),
138+
"US".parse::<Region>().unwrap()
139+
);
87140
assert!(serde_json::from_str::<Region>(r#""2Xs""#).is_err());
88141
}
89142

90143
#[test]
91144
fn postcard() {
92-
use crate::langid;
93145
use crate::subtags::{Language, Region, Script};
146+
use crate::{langid, locale};
94147

95148
assert_eq!(
96149
postcard::to_stdvec(&langid!("en-US")).unwrap(),
97-
&[5, b'e', b'n', b'-', b'U', b'S']
150+
b"\x05en-US"
98151
);
99152
assert_eq!(
100-
postcard::from_bytes::<LanguageIdentifier>(&[5, b'e', b'n', b'-', b'U', b'S']).unwrap(),
153+
postcard::from_bytes::<LanguageIdentifier>(b"\x05en-US").unwrap(),
101154
langid!("en-US")
102155
);
103-
assert!(postcard::from_bytes::<LanguageIdentifier>(&[3, b'2', b'X', b's']).is_err());
156+
assert!(postcard::from_bytes::<LanguageIdentifier>(b"\x032Xs").is_err());
157+
158+
assert_eq!(
159+
postcard::to_stdvec(&locale!("en-US-u-hc-h12")).unwrap(),
160+
b"\x0Een-US-u-hc-h12"
161+
);
162+
assert_eq!(
163+
postcard::from_bytes::<Locale>(b"\x0Een-US-u-hc-h12").unwrap(),
164+
locale!("en-US-u-hc-h12")
165+
);
166+
assert!(postcard::from_bytes::<Locale>(b"\x032Xs").is_err());
104167

105168
assert_eq!(
106169
postcard::to_stdvec(&"fr".parse::<Language>().unwrap()).unwrap(),

0 commit comments

Comments
 (0)