1use serde::Deserialize;
15use serde::de::{self, Deserializer, MapAccess, Visitor};
16use std::collections::BTreeMap;
17use std::fmt;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum LocalizedField {
41 Plain(String),
43 Multilingual(BTreeMap<String, String>),
47}
48
49impl LocalizedField {
50 pub fn resolve(&self, preferred_language: &str) -> String {
59 match self {
60 LocalizedField::Plain(s) => s.clone(),
61 LocalizedField::Multilingual(map) => map
62 .get(preferred_language)
63 .filter(|s| !s.is_empty())
64 .or_else(|| {
65 if preferred_language != "en" {
66 map.get("en").filter(|s| !s.is_empty())
67 } else {
68 None
69 }
70 })
71 .or_else(|| map.values().find(|s| !s.is_empty()))
72 .cloned()
73 .unwrap_or_default(),
74 }
75 }
76}
77
78impl<'de> Deserialize<'de> for LocalizedField {
79 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80 where
81 D: Deserializer<'de>,
82 {
83 struct LocalizedFieldVisitor;
84
85 impl<'de> Visitor<'de> for LocalizedFieldVisitor {
86 type Value = LocalizedField;
87
88 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 f.write_str("a string or a map of language codes to strings")
90 }
91
92 fn visit_str<E: de::Error>(self, value: &str) -> Result<LocalizedField, E> {
93 Ok(LocalizedField::Plain(value.to_string()))
94 }
95
96 fn visit_string<E: de::Error>(self, value: String) -> Result<LocalizedField, E> {
97 Ok(LocalizedField::Plain(value))
98 }
99
100 fn visit_map<M>(self, map: M) -> Result<LocalizedField, M::Error>
101 where
102 M: MapAccess<'de>,
103 {
104 let translations: BTreeMap<String, String> =
105 Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
106 Ok(LocalizedField::Multilingual(translations))
107 }
108 }
109
110 deserializer.deserialize_any(LocalizedFieldVisitor)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_resolve_plain_ignores_language() {
120 let field = LocalizedField::Plain("Hello".to_string());
121 assert_eq!(field.resolve("en"), "Hello");
122 assert_eq!(field.resolve("de"), "Hello");
123 assert_eq!(field.resolve("fr"), "Hello");
124 }
125
126 #[test]
127 fn test_resolve_multilingual_preferred_language() {
128 let mut map = BTreeMap::new();
129 map.insert("en".to_string(), "English".to_string());
130 map.insert("de".to_string(), "Deutsch".to_string());
131 map.insert("fr".to_string(), "Francais".to_string());
132 let field = LocalizedField::Multilingual(map);
133
134 assert_eq!(field.resolve("de"), "Deutsch");
135 assert_eq!(field.resolve("fr"), "Francais");
136 assert_eq!(field.resolve("en"), "English");
137 }
138
139 #[test]
140 fn test_resolve_multilingual_falls_back_to_en() {
141 let mut map = BTreeMap::new();
142 map.insert("en".to_string(), "English".to_string());
143 map.insert("de".to_string(), "Deutsch".to_string());
144 let field = LocalizedField::Multilingual(map);
145
146 assert_eq!(field.resolve("it"), "English");
148 }
149
150 #[test]
151 fn test_resolve_multilingual_falls_back_to_first_available() {
152 let mut map = BTreeMap::new();
153 map.insert("de".to_string(), "Deutsch".to_string());
154 let field = LocalizedField::Multilingual(map);
155
156 assert_eq!(field.resolve("fr"), "Deutsch");
158 }
159
160 #[test]
161 fn test_resolve_multilingual_empty_map() {
162 let field = LocalizedField::Multilingual(BTreeMap::new());
163 assert_eq!(field.resolve("en"), "");
164 }
165
166 #[test]
167 fn test_resolve_multilingual_skips_empty_preferred() {
168 let mut map = BTreeMap::new();
169 map.insert("en".to_string(), "".to_string());
170 map.insert("de".to_string(), "Deutsch".to_string());
171 map.insert("fr".to_string(), "".to_string());
172 let field = LocalizedField::Multilingual(map);
173
174 assert_eq!(field.resolve("en"), "Deutsch");
176 assert_eq!(field.resolve("fr"), "Deutsch");
178 }
179
180 #[test]
181 fn test_deserialize_plain_string() {
182 let field: LocalizedField = serde_json::from_str(r#""My Dataset""#).unwrap();
183 assert_eq!(field, LocalizedField::Plain("My Dataset".to_string()));
184 }
185
186 #[test]
187 fn test_deserialize_multilingual_object() {
188 let field: LocalizedField =
189 serde_json::from_str(r#"{"en": "English Title", "de": "Deutscher Titel"}"#).unwrap();
190 match &field {
191 LocalizedField::Multilingual(map) => {
192 assert_eq!(map.get("en").unwrap(), "English Title");
193 assert_eq!(map.get("de").unwrap(), "Deutscher Titel");
194 }
195 _ => panic!("Expected Multilingual variant"),
196 }
197 }
198
199 #[test]
200 fn test_deserialize_option_null() {
201 let field: Option<LocalizedField> = serde_json::from_str("null").unwrap();
202 assert!(field.is_none());
203 }
204
205 #[test]
206 fn test_deserialize_option_plain() {
207 let field: Option<LocalizedField> = serde_json::from_str(r#""description""#).unwrap();
208 assert_eq!(
209 field,
210 Some(LocalizedField::Plain("description".to_string()))
211 );
212 }
213
214 #[test]
215 fn test_deserialize_option_multilingual() {
216 let field: Option<LocalizedField> =
217 serde_json::from_str(r#"{"en": "English", "fr": "Francais"}"#).unwrap();
218 assert!(matches!(field, Some(LocalizedField::Multilingual(_))));
219 }
220}