Skip to main content

icu_locale_core/preferences/extensions/unicode/macros/
struct_keyword.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5/// Macro used to generate a preference keyword as a struct.
6///
7/// # Examples
8///
9/// ```
10/// use icu::locale::{
11///     extensions::unicode::{Key, Value},
12///     preferences::extensions::unicode::struct_keyword,
13/// };
14///
15/// struct_keyword!(
16///     CurrencyType,
17///     "cu",
18///     String,
19///     |input: &Value| { Ok(Self(input.to_string())) },
20///     |input: &CurrencyType| {
21///         icu::locale::extensions::unicode::Value::try_from_str(
22///             input.0.as_str(),
23///         )
24///         .unwrap()
25///     }
26/// );
27/// ```
28#[macro_export]
29#[doc(hidden)]
30macro_rules! __struct_keyword {
31    ($(#[$doc:meta])* $([$derive_attrs:ty])? $name:ident, $ext_key:literal, $value:ty, $try_from:expr, $into:expr) => {
32        $(#[$doc])*
33        #[derive(Debug, Clone, Eq, PartialEq, Hash)]
34        $(#[derive($derive_attrs)])?
35        #[allow(clippy::exhaustive_structs)] // TODO
36        pub struct $name(pub(crate) $value);
37
38        impl TryFrom<$crate::extensions::unicode::Value> for $name {
39            type Error = $crate::preferences::extensions::unicode::errors::PreferencesParseError;
40
41            fn try_from(
42                input: $crate::extensions::unicode::Value,
43            ) -> Result<Self, Self::Error> {
44                Self::try_from(&input)
45            }
46        }
47
48        impl TryFrom<&$crate::extensions::unicode::Value> for $name {
49            type Error = $crate::preferences::extensions::unicode::errors::PreferencesParseError;
50
51            fn try_from(
52                input: &$crate::extensions::unicode::Value,
53            ) -> Result<Self, Self::Error> {
54                $try_from(input)
55            }
56        }
57
58        impl From<$name> for $crate::extensions::unicode::Value {
59            fn from(input: $name) -> $crate::extensions::unicode::Value {
60                (&input).into()
61            }
62        }
63
64        impl From<&$name> for $crate::extensions::unicode::Value {
65            fn from(input: &$name) -> $crate::extensions::unicode::Value {
66                $into(input)
67            }
68        }
69
70        impl $crate::preferences::PreferenceKey for $name {
71            fn unicode_extension_key() -> Option<$crate::extensions::unicode::Key> {
72                Some(Self::UNICODE_EXTENSION_KEY)
73            }
74
75            fn try_from_key_value(
76                key: &$crate::extensions::unicode::Key,
77                value: &$crate::extensions::unicode::Value,
78            ) -> Result<Option<Self>, $crate::preferences::extensions::unicode::errors::PreferencesParseError> {
79                if Self::UNICODE_EXTENSION_KEY == *key {
80                    let result = Self::try_from(value.clone())?;
81                    Ok(Some(result))
82                } else {
83                    Ok(None)
84                }
85            }
86
87            fn unicode_extension_value(
88                &self,
89            ) -> Option<$crate::extensions::unicode::Value> {
90                Some(self.clone().into())
91            }
92        }
93
94        impl $name {
95            pub(crate) const UNICODE_EXTENSION_KEY: $crate::extensions::unicode::Key = $crate::extensions::unicode::key!($ext_key);
96        }
97
98        impl core::ops::Deref for $name {
99            type Target = $value;
100
101            fn deref(&self) -> &Self::Target {
102                &self.0
103            }
104        }
105    };
106}
107pub use __struct_keyword as struct_keyword;
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::{
113        extensions::unicode,
114        subtags::{Subtag, subtag},
115    };
116    use core::str::FromStr;
117
118    #[test]
119    fn struct_keywords_test() {
120        struct_keyword!(
121            DummyKeyword,
122            "dk",
123            Subtag,
124            |input: &unicode::Value| {
125                if let Some(&subtag) = input.as_single_subtag()
126                    && subtag.len() == 3
127                {
128                    return Ok(DummyKeyword(subtag));
129                }
130                Err(crate::preferences::extensions::unicode::errors::PreferencesParseError::InvalidKeywordValue)
131            },
132            |input: &DummyKeyword| { unicode::Value::from_subtag(Some(input.0)) }
133        );
134
135        let v = unicode::Value::from_str("foo").unwrap();
136        let dk: DummyKeyword = v.clone().try_into().unwrap();
137        assert_eq!(dk, DummyKeyword(subtag!("foo")));
138        assert_eq!(unicode::Value::from(dk), v);
139
140        let v = unicode::Value::from_str("foobar").unwrap();
141        let dk: Result<DummyKeyword, _> = v.clone().try_into();
142        assert!(dk.is_err());
143    }
144}