bitwarden_exporters/cxf/
editable_field.rs

1use bitwarden_vault::FieldType;
2use credential_exchange_format::{
3    EditableField, EditableFieldBoolean, EditableFieldConcealedString, EditableFieldCountryCode,
4    EditableFieldDate, EditableFieldString, EditableFieldWifiNetworkSecurityType,
5    EditableFieldYearMonth,
6};
7
8use crate::Field;
9
10/// Helper function to create a Field from any EditableField type
11pub(super) fn create_field<T>(name: impl Into<String>, field: &T) -> Field
12where
13    T: EditableFieldToField,
14{
15    Field {
16        name: Some(name.into()),
17        value: Some(field.field_value()),
18        r#type: T::FIELD_TYPE as u8,
19        linked_id: None,
20    }
21}
22
23/// Trait to convert CXP EditableField types to Bitwarden Field values and types
24pub(super) trait EditableFieldToField {
25    const FIELD_TYPE: FieldType;
26
27    fn field_value(&self) -> String;
28}
29
30impl EditableFieldToField for EditableField<EditableFieldString> {
31    const FIELD_TYPE: FieldType = FieldType::Text;
32
33    fn field_value(&self) -> String {
34        self.value.0.clone()
35    }
36}
37
38impl EditableFieldToField for EditableField<EditableFieldConcealedString> {
39    const FIELD_TYPE: FieldType = FieldType::Hidden;
40
41    fn field_value(&self) -> String {
42        self.value.0.clone()
43    }
44}
45
46impl EditableFieldToField for EditableField<EditableFieldBoolean> {
47    const FIELD_TYPE: FieldType = FieldType::Boolean;
48
49    fn field_value(&self) -> String {
50        self.value.0.to_string()
51    }
52}
53
54impl EditableFieldToField for EditableField<EditableFieldWifiNetworkSecurityType> {
55    const FIELD_TYPE: FieldType = FieldType::Text;
56
57    fn field_value(&self) -> String {
58        security_type_to_string(&self.value).to_string()
59    }
60}
61
62impl EditableFieldToField for EditableField<EditableFieldCountryCode> {
63    const FIELD_TYPE: FieldType = FieldType::Text;
64
65    fn field_value(&self) -> String {
66        self.value.0.clone()
67    }
68}
69
70impl EditableFieldToField for EditableField<EditableFieldDate> {
71    const FIELD_TYPE: FieldType = FieldType::Text;
72
73    fn field_value(&self) -> String {
74        self.value.0.to_string()
75    }
76}
77
78impl EditableFieldToField for EditableField<EditableFieldYearMonth> {
79    const FIELD_TYPE: FieldType = FieldType::Text;
80
81    fn field_value(&self) -> String {
82        format!(
83            "{:04}-{:02}",
84            self.value.year,
85            self.value.month.number_from_month()
86        )
87    }
88}
89
90/// Convert WiFi security type enum to human-readable string
91fn security_type_to_string(security_type: &EditableFieldWifiNetworkSecurityType) -> &str {
92    use EditableFieldWifiNetworkSecurityType::*;
93    match security_type {
94        Unsecured => "Unsecured",
95        WpaPersonal => "WPA Personal",
96        Wpa2Personal => "WPA2 Personal",
97        Wpa3Personal => "WPA3 Personal",
98        Wep => "WEP",
99        Other(s) => s,
100        _ => "Unknown",
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_create_field_string() {
110        let editable_field = EditableField {
111            id: None,
112            label: None,
113            value: EditableFieldString("Test Value".to_string()),
114            extensions: None,
115        };
116
117        let field = create_field("Test Name", &editable_field);
118
119        assert_eq!(
120            field,
121            Field {
122                name: Some("Test Name".to_string()),
123                value: Some("Test Value".to_string()),
124                r#type: FieldType::Text as u8,
125                linked_id: None,
126            }
127        );
128    }
129
130    #[test]
131    fn test_create_field_concealed_string() {
132        let editable_field = EditableField {
133            id: None,
134            label: None,
135            value: EditableFieldConcealedString("Secret123".to_string()),
136            extensions: None,
137        };
138
139        let field = create_field("Password", &editable_field);
140
141        assert_eq!(
142            field,
143            Field {
144                name: Some("Password".to_string()),
145                value: Some("Secret123".to_string()),
146                r#type: FieldType::Hidden as u8,
147                linked_id: None,
148            }
149        );
150    }
151
152    #[test]
153    fn test_create_field_boolean_true() {
154        let editable_field = EditableField {
155            id: None,
156            label: None,
157            value: EditableFieldBoolean(true),
158            extensions: None,
159        };
160
161        let field = create_field("Is Enabled", &editable_field);
162
163        assert_eq!(
164            field,
165            Field {
166                name: Some("Is Enabled".to_string()),
167                value: Some("true".to_string()),
168                r#type: FieldType::Boolean as u8,
169                linked_id: None,
170            }
171        );
172    }
173
174    #[test]
175    fn test_create_field_boolean_false() {
176        let editable_field = EditableField {
177            id: None,
178            label: None,
179            value: EditableFieldBoolean(false),
180            extensions: None,
181        };
182
183        let field = create_field("Is Hidden", &editable_field);
184
185        assert_eq!(
186            field,
187            Field {
188                name: Some("Is Hidden".to_string()),
189                value: Some("false".to_string()),
190                r#type: FieldType::Boolean as u8,
191                linked_id: None,
192            }
193        );
194    }
195
196    #[test]
197    fn test_create_field_wifi_security() {
198        let editable_field = EditableField {
199            id: None,
200            label: None,
201            value: EditableFieldWifiNetworkSecurityType::Wpa3Personal,
202            extensions: None,
203        };
204
205        let field = create_field("WiFi Security", &editable_field);
206
207        assert_eq!(
208            field,
209            Field {
210                name: Some("WiFi Security".to_string()),
211                value: Some("WPA3 Personal".to_string()),
212                r#type: FieldType::Text as u8,
213                linked_id: None,
214            }
215        );
216    }
217
218    #[test]
219    fn test_security_type_to_string() {
220        assert_eq!(
221            security_type_to_string(&EditableFieldWifiNetworkSecurityType::Unsecured),
222            "Unsecured"
223        );
224        assert_eq!(
225            security_type_to_string(&EditableFieldWifiNetworkSecurityType::WpaPersonal),
226            "WPA Personal"
227        );
228        assert_eq!(
229            security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wpa2Personal),
230            "WPA2 Personal"
231        );
232        assert_eq!(
233            security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wpa3Personal),
234            "WPA3 Personal"
235        );
236        assert_eq!(
237            security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wep),
238            "WEP"
239        );
240
241        let custom_security = "WPA2 Enterprise";
242        assert_eq!(
243            security_type_to_string(&EditableFieldWifiNetworkSecurityType::Other(
244                custom_security.to_string()
245            )),
246            custom_security
247        );
248    }
249
250    #[test]
251    fn test_create_field_date() {
252        use chrono::NaiveDate;
253
254        let editable_field = EditableField {
255            id: None,
256            label: None,
257            value: EditableFieldDate(NaiveDate::from_ymd_opt(2025, 1, 15).unwrap()),
258            extensions: None,
259        };
260
261        let field = create_field("Expiry Date".to_string(), &editable_field);
262
263        assert_eq!(
264            field,
265            Field {
266                name: Some("Expiry Date".to_string()),
267                value: Some("2025-01-15".to_string()),
268                r#type: FieldType::Text as u8,
269                linked_id: None,
270            }
271        );
272    }
273
274    #[test]
275    fn test_create_field_year_month() {
276        use chrono::Month;
277
278        let editable_field = EditableField {
279            id: None,
280            label: None,
281            value: EditableFieldYearMonth {
282                year: 2025,
283                month: Month::December,
284            },
285            extensions: None,
286        };
287
288        let field = create_field("Card Expiry", &editable_field);
289
290        assert_eq!(
291            field,
292            Field {
293                name: Some("Card Expiry".to_string()),
294                value: Some("2025-12".to_string()),
295                r#type: FieldType::Text as u8,
296                linked_id: None,
297            }
298        );
299    }
300}