1use bitwarden_vault::FieldType;
2use credential_exchange_format::{
3 EditableField, EditableFieldBoolean, EditableFieldConcealedString, EditableFieldCountryCode,
4 EditableFieldDate, EditableFieldString, EditableFieldSubdivisionCode, EditableFieldValue,
5 EditableFieldWifiNetworkSecurityType, EditableFieldYearMonth,
6};
7
8use crate::Field;
9
10pub(super) fn create_field<T>(field: &T, overridden_name: Option<impl Into<String>>) -> Field
12where
13 T: EditableFieldToField,
14{
15 let field_name = overridden_name
16 .map(Into::into)
17 .or_else(|| field.label().clone());
18
19 Field {
20 name: field_name,
21 value: Some(field.field_value()),
22 r#type: T::FIELD_TYPE as u8,
23 linked_id: None,
24 }
25}
26
27pub(super) fn create_editable_field<T>(name: String, value: T) -> EditableField<T> {
29 EditableField {
30 id: None,
31 label: Some(name),
32 value,
33 extensions: None,
34 }
35}
36
37pub(super) fn field_to_editable_field_value(field: Field) -> Option<EditableFieldValue> {
39 let name = field.name?;
40
41 match field.r#type {
42 x if x == FieldType::Text as u8 => field.value.map(|value| {
43 EditableFieldValue::String(create_editable_field(name, EditableFieldString(value)))
44 }),
45
46 x if x == FieldType::Hidden as u8 => field.value.map(|value| {
47 EditableFieldValue::ConcealedString(create_editable_field(
48 name,
49 EditableFieldConcealedString(value),
50 ))
51 }),
52
53 x if x == FieldType::Boolean as u8 => field.value?.parse::<bool>().ok().map(|bool_value| {
54 EditableFieldValue::Boolean(create_editable_field(
55 name,
56 EditableFieldBoolean(bool_value),
57 ))
58 }),
59
60 x if x == FieldType::Linked as u8 => {
61 let value = field
62 .value
63 .or_else(|| field.linked_id.map(|id| id.to_string()))?;
64 Some(EditableFieldValue::String(create_editable_field(
65 name,
66 EditableFieldString(value),
67 )))
68 }
69
70 _ => field.value.map(|value| {
71 EditableFieldValue::String(create_editable_field(name, EditableFieldString(value)))
72 }),
73 }
74}
75
76pub(super) trait InnerFieldType {
78 const FIELD_TYPE: FieldType;
79
80 fn to_field_value(&self) -> String;
81}
82
83impl InnerFieldType for EditableFieldString {
84 const FIELD_TYPE: FieldType = FieldType::Text;
85
86 fn to_field_value(&self) -> String {
87 self.0.clone()
88 }
89}
90
91impl InnerFieldType for EditableFieldConcealedString {
92 const FIELD_TYPE: FieldType = FieldType::Hidden;
93
94 fn to_field_value(&self) -> String {
95 self.0.clone()
96 }
97}
98
99impl InnerFieldType for EditableFieldBoolean {
100 const FIELD_TYPE: FieldType = FieldType::Boolean;
101
102 fn to_field_value(&self) -> String {
103 self.0.to_string()
104 }
105}
106
107impl InnerFieldType for EditableFieldWifiNetworkSecurityType {
108 const FIELD_TYPE: FieldType = FieldType::Text;
109
110 fn to_field_value(&self) -> String {
111 security_type_to_string(self).to_string()
112 }
113}
114
115impl InnerFieldType for EditableFieldCountryCode {
116 const FIELD_TYPE: FieldType = FieldType::Text;
117
118 fn to_field_value(&self) -> String {
119 self.0.clone()
120 }
121}
122
123impl InnerFieldType for EditableFieldDate {
124 const FIELD_TYPE: FieldType = FieldType::Text;
125
126 fn to_field_value(&self) -> String {
127 self.0.to_string()
128 }
129}
130
131impl InnerFieldType for EditableFieldYearMonth {
132 const FIELD_TYPE: FieldType = FieldType::Text;
133
134 fn to_field_value(&self) -> String {
135 format!("{:04}-{:02}", self.year, self.month.number_from_month())
136 }
137}
138
139impl InnerFieldType for EditableFieldSubdivisionCode {
140 const FIELD_TYPE: FieldType = FieldType::Text;
141
142 fn to_field_value(&self) -> String {
143 self.0.clone()
144 }
145}
146
147pub(super) trait EditableFieldToField {
149 const FIELD_TYPE: FieldType;
150
151 fn field_value(&self) -> String;
152 fn label(&self) -> &Option<String>;
153}
154
155impl<T> EditableFieldToField for EditableField<T>
156where
157 T: InnerFieldType,
158{
159 const FIELD_TYPE: FieldType = T::FIELD_TYPE;
160
161 fn field_value(&self) -> String {
162 self.value.to_field_value()
163 }
164
165 fn label(&self) -> &Option<String> {
166 &self.label
167 }
168}
169
170fn security_type_to_string(security_type: &EditableFieldWifiNetworkSecurityType) -> &str {
172 use EditableFieldWifiNetworkSecurityType::*;
173 match security_type {
174 Unsecured => "Unsecured",
175 WpaPersonal => "WPA Personal",
176 Wpa2Personal => "WPA2 Personal",
177 Wpa3Personal => "WPA3 Personal",
178 Wep => "WEP",
179 Other(s) => s,
180 _ => "Unknown",
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_create_field_string() {
190 let editable_field = EditableField {
191 id: None,
192 label: None,
193 value: EditableFieldString("Test Value".to_string()),
194 extensions: None,
195 };
196
197 let field = create_field(&editable_field, Some("Test Name"));
198
199 assert_eq!(
200 field,
201 Field {
202 name: Some("Test Name".to_string()),
203 value: Some("Test Value".to_string()),
204 r#type: FieldType::Text as u8,
205 linked_id: None,
206 }
207 );
208 }
209
210 #[test]
211 fn test_create_field_concealed_string() {
212 let editable_field = EditableField {
213 id: None,
214 label: None,
215 value: EditableFieldConcealedString("Secret123".to_string()),
216 extensions: None,
217 };
218
219 let field = create_field(&editable_field, Some("Password"));
220
221 assert_eq!(
222 field,
223 Field {
224 name: Some("Password".to_string()),
225 value: Some("Secret123".to_string()),
226 r#type: FieldType::Hidden as u8,
227 linked_id: None,
228 }
229 );
230 }
231
232 #[test]
233 fn test_create_field_boolean_true() {
234 let editable_field = EditableField {
235 id: None,
236 label: None,
237 value: EditableFieldBoolean(true),
238 extensions: None,
239 };
240
241 let field = create_field(&editable_field, Some("Is Enabled"));
242
243 assert_eq!(
244 field,
245 Field {
246 name: Some("Is Enabled".to_string()),
247 value: Some("true".to_string()),
248 r#type: FieldType::Boolean as u8,
249 linked_id: None,
250 }
251 );
252 }
253
254 #[test]
255 fn test_create_field_boolean_false() {
256 let editable_field = EditableField {
257 id: None,
258 label: None,
259 value: EditableFieldBoolean(false),
260 extensions: None,
261 };
262
263 let field = create_field(&editable_field, Some("Is Hidden"));
264
265 assert_eq!(
266 field,
267 Field {
268 name: Some("Is Hidden".to_string()),
269 value: Some("false".to_string()),
270 r#type: FieldType::Boolean as u8,
271 linked_id: None,
272 }
273 );
274 }
275
276 #[test]
277 fn test_create_field_wifi_security() {
278 let editable_field = EditableField {
279 id: None,
280 label: None,
281 value: EditableFieldWifiNetworkSecurityType::Wpa3Personal,
282 extensions: None,
283 };
284
285 let field = create_field(&editable_field, Some("WiFi Security"));
286
287 assert_eq!(
288 field,
289 Field {
290 name: Some("WiFi Security".to_string()),
291 value: Some("WPA3 Personal".to_string()),
292 r#type: FieldType::Text as u8,
293 linked_id: None,
294 }
295 );
296 }
297
298 #[test]
299 fn test_security_type_to_string() {
300 assert_eq!(
301 security_type_to_string(&EditableFieldWifiNetworkSecurityType::Unsecured),
302 "Unsecured"
303 );
304 assert_eq!(
305 security_type_to_string(&EditableFieldWifiNetworkSecurityType::WpaPersonal),
306 "WPA Personal"
307 );
308 assert_eq!(
309 security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wpa2Personal),
310 "WPA2 Personal"
311 );
312 assert_eq!(
313 security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wpa3Personal),
314 "WPA3 Personal"
315 );
316 assert_eq!(
317 security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wep),
318 "WEP"
319 );
320
321 let custom_security = "WPA2 Enterprise";
322 assert_eq!(
323 security_type_to_string(&EditableFieldWifiNetworkSecurityType::Other(
324 custom_security.to_string()
325 )),
326 custom_security
327 );
328 }
329
330 #[test]
331 fn test_create_field_date() {
332 use chrono::NaiveDate;
333
334 let editable_field = EditableField {
335 id: None,
336 label: None,
337 value: EditableFieldDate(NaiveDate::from_ymd_opt(2025, 1, 15).unwrap()),
338 extensions: None,
339 };
340
341 let field = create_field(&editable_field, Some("Expiry Date".to_string()));
342
343 assert_eq!(
344 field,
345 Field {
346 name: Some("Expiry Date".to_string()),
347 value: Some("2025-01-15".to_string()),
348 r#type: FieldType::Text as u8,
349 linked_id: None,
350 }
351 );
352 }
353
354 #[test]
355 fn test_create_field_year_month() {
356 use chrono::Month;
357
358 let editable_field = EditableField {
359 id: None,
360 label: None,
361 value: EditableFieldYearMonth {
362 year: 2025,
363 month: Month::December,
364 },
365 extensions: None,
366 };
367
368 let field = create_field(&editable_field, Some("Card Expiry"));
369
370 assert_eq!(
371 field,
372 Field {
373 name: Some("Card Expiry".to_string()),
374 value: Some("2025-12".to_string()),
375 r#type: FieldType::Text as u8,
376 linked_id: None,
377 }
378 );
379 }
380
381 #[test]
382 fn test_create_field_with_none_name_uses_label() {
383 let editable_field = EditableField {
384 id: None,
385 label: Some("Label From Field".to_string()),
386 value: EditableFieldString("Test Value".to_string()),
387 extensions: None,
388 };
389
390 let field = create_field(&editable_field, None::<String>);
391
392 assert_eq!(
393 field,
394 Field {
395 name: Some("Label From Field".to_string()),
396 value: Some("Test Value".to_string()),
397 r#type: FieldType::Text as u8,
398 linked_id: None,
399 }
400 );
401 }
402
403 #[test]
404 fn test_create_field_with_none_name_and_none_label() {
405 let editable_field = EditableField {
406 id: None,
407 label: None,
408 value: EditableFieldString("Test Value".to_string()),
409 extensions: None,
410 };
411
412 let field = create_field(&editable_field, None::<String>);
413
414 assert_eq!(
415 field,
416 Field {
417 name: None,
418 value: Some("Test Value".to_string()),
419 r#type: FieldType::Text as u8,
420 linked_id: None,
421 }
422 );
423 }
424}