credential_exchange_format/
editable_field.rs

1use std::{borrow::Cow, fmt, str};
2
3use chrono::{Month, NaiveDate};
4use serde::{
5    de::{DeserializeOwned, Visitor},
6    ser::SerializeStruct,
7    Deserialize, Serialize,
8};
9
10use crate::{B64Url, Extension};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct EditableField<T, E = ()> {
14    /// A unique identifier for the [EditableField] which is machine generated and an opaque byte
15    /// sequence with a maximum size of 64 bytes. It SHOULD NOT be displayed to the user.
16    pub id: Option<B64Url>,
17    /// This member contains the fieldType defined by the user.
18    pub value: T,
19    /// This member contains a user facing value describing the value stored. This value MAY be
20    /// user defined.
21    pub label: Option<String>,
22    /// This member permits the exporting provider to add additional information associated to this
23    /// [EditableField]. This MAY be used to provide an exchange where a minimal amount of
24    /// information is lost.
25    pub extensions: Option<Vec<Extension<E>>>,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
29#[serde(rename_all = "kebab-case")]
30#[non_exhaustive]
31pub enum FieldType {
32    /// A UTF-8 encoded string value which is unconcealed and does not have a specified format.
33    String,
34    /// A UTF-8 encoded string value which should be considered secret and not displayed unless the
35    /// user explicitly requests it.
36    ConcealedString,
37    /// A UTF-8 encoded string value which follows the format specified in
38    /// [RFC5322](https://www.rfc-editor.org/rfc/rfc5322#section-3.4). This field SHOULD be
39    /// unconcealed.
40    Email,
41    /// A stringified numeric value which is unconcealed.
42    Number,
43    /// A boolean value which is unconcealed. It MUST be of the values "true" or "false".
44    Boolean,
45    /// A string value representing a calendar date which follows the format specified in
46    /// [RFC3339](https://www.rfc-editor.org/rfc/rfc3339).
47    Date,
48    /// A string value representing a calendar date which follows the date-fullyear "-" date-month
49    /// pattern as established in [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) Appendix A.
50    /// This is equivalent to the `YYYY-MM` format specified in ISO8601.
51    YearMonth,
52    /// A string value representing a value that SHOULD be a member of WIFINetworkSecurityType.
53    WifiNetworkSecurityType,
54    /// A string value which MUST follow the ISO3166-1 alpha-2 format.
55    SubdivisionCode,
56    /// A string which MUST follow the ISO3166-2 format.
57    CountryCode,
58    #[serde(untagged)]
59    Unknown(String),
60}
61
62/// A trait to associate the field structs with their `field_type` tag.
63pub trait EditableFieldType {
64    /// The `field_type` value associated with the type
65    fn field_type(&self) -> FieldType;
66}
67
68impl<T, E> Serialize for EditableField<T, E>
69where
70    T: EditableFieldType + Serialize,
71    E: Serialize,
72{
73    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
74    where
75        S: serde::Serializer,
76    {
77        let len = 2
78            + self.id.is_some() as usize
79            + self.label.is_some() as usize
80            + self.extensions.is_some() as usize;
81        let mut state = serializer.serialize_struct("editable_field", len)?;
82
83        if let Some(ref id) = self.id {
84            state.serialize_field("id", id)?;
85        } else {
86            state.skip_field("id")?;
87        }
88
89        state.serialize_field("fieldType", &self.value.field_type())?;
90        state.serialize_field("value", &self.value)?;
91
92        if let Some(ref label) = self.label {
93            state.serialize_field("label", label)?;
94        } else {
95            state.skip_field("label")?;
96        }
97
98        if let Some(ref ext) = self.extensions {
99            if ext.is_empty() {
100                state.skip_field("extensions")?;
101            } else {
102                state.serialize_field("extensions", ext)?;
103            }
104        } else {
105            state.skip_field("extensions")?;
106        }
107
108        state.end()
109    }
110}
111
112impl<'de, T, E> Deserialize<'de> for EditableField<T, E>
113where
114    T: EditableFieldType + DeserializeOwned,
115    E: Deserialize<'de>,
116{
117    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
118    where
119        D: serde::Deserializer<'de>,
120    {
121        #[derive(Deserialize)]
122        #[serde(rename_all = "camelCase")]
123        struct EditableFieldHelper<T, E> {
124            #[serde(default)]
125            id: Option<B64Url>,
126            value: T,
127            field_type: FieldType,
128            #[serde(default)]
129            label: Option<String>,
130            #[serde(default = "none::<E>")]
131            extensions: Option<Vec<Extension<E>>>,
132        }
133        // Need to use this instead of the normal default,
134        // otherwise the derive creates a `E: Default` constraint.
135        fn none<E>() -> Option<Vec<Extension<E>>> {
136            None
137        }
138
139        let helper: EditableFieldHelper<T, E> = EditableFieldHelper::deserialize(deserializer)?;
140
141        if helper.field_type != helper.value.field_type() {
142            return Err(serde::de::Error::custom(
143                "field_type does not match value type",
144            ));
145        }
146
147        Ok(Self {
148            id: helper.id,
149            value: helper.value,
150            label: helper.label,
151            extensions: helper.extensions,
152        })
153    }
154}
155
156// Helper for converting inner types into EditableField
157impl<T, E> From<T> for EditableField<T, E> {
158    fn from(s: T) -> Self {
159        EditableField {
160            id: None,
161            value: s,
162            label: None,
163            extensions: None,
164        }
165    }
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
169#[serde(transparent)]
170pub struct EditableFieldString(pub String);
171impl EditableFieldType for EditableFieldString {
172    fn field_type(&self) -> FieldType {
173        FieldType::String
174    }
175}
176
177impl<E> From<String> for EditableField<EditableFieldString, E> {
178    fn from(s: String) -> Self {
179        EditableFieldString(s).into()
180    }
181}
182
183impl<E> From<EditableField<EditableFieldString, E>> for String {
184    fn from(s: EditableField<EditableFieldString, E>) -> Self {
185        s.value.0
186    }
187}
188
189#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
190#[serde(transparent)]
191pub struct EditableFieldConcealedString(pub String);
192impl EditableFieldType for EditableFieldConcealedString {
193    fn field_type(&self) -> FieldType {
194        FieldType::ConcealedString
195    }
196}
197
198impl<E> From<String> for EditableField<EditableFieldConcealedString, E> {
199    fn from(s: String) -> Self {
200        EditableFieldConcealedString(s).into()
201    }
202}
203
204impl<E> From<EditableField<EditableFieldConcealedString, E>> for String {
205    fn from(s: EditableField<EditableFieldConcealedString, E>) -> Self {
206        s.value.0
207    }
208}
209
210#[derive(Clone, Debug, Serialize, Deserialize)]
211pub struct EditableFieldBoolean(#[serde(with = "serde_bool")] pub bool);
212impl EditableFieldType for EditableFieldBoolean {
213    fn field_type(&self) -> FieldType {
214        FieldType::Boolean
215    }
216}
217
218impl<E> From<bool> for EditableField<EditableFieldBoolean, E> {
219    fn from(b: bool) -> Self {
220        EditableFieldBoolean(b).into()
221    }
222}
223
224impl<E> From<EditableField<EditableFieldBoolean, E>> for bool {
225    fn from(b: EditableField<EditableFieldBoolean, E>) -> Self {
226        b.value.0
227    }
228}
229
230#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
231#[serde(transparent)]
232pub struct EditableFieldDate(pub NaiveDate);
233impl EditableFieldType for EditableFieldDate {
234    fn field_type(&self) -> FieldType {
235        FieldType::Date
236    }
237}
238
239#[derive(Clone, Debug, PartialEq, Eq)]
240pub struct EditableFieldYearMonth {
241    /// The year in the format `YYYY`
242    pub year: u16,
243    /// The month in the format `MM`
244    pub month: Month,
245}
246impl EditableFieldType for EditableFieldYearMonth {
247    fn field_type(&self) -> FieldType {
248        FieldType::YearMonth
249    }
250}
251
252impl Serialize for EditableFieldYearMonth {
253    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
254    where
255        S: serde::Serializer,
256    {
257        serializer.serialize_str(&format!(
258            "{:04}-{:02}",
259            self.year,
260            self.month.number_from_month()
261        ))
262    }
263}
264
265impl<'de> Deserialize<'de> for EditableFieldYearMonth {
266    fn deserialize<D>(deserializer: D) -> Result<EditableFieldYearMonth, D::Error>
267    where
268        D: serde::Deserializer<'de>,
269    {
270        let s = deserializer.deserialize_str(CowVisitor)?;
271        let (year_str, month_str) = s
272            .split_once('-')
273            .ok_or_else(|| serde::de::Error::custom("Invalid format"))?;
274
275        Ok(EditableFieldYearMonth {
276            year: year_str
277                .parse::<u16>()
278                .map_err(|_| serde::de::Error::custom("Invalid year"))?,
279            month: month_str
280                .parse::<u8>()
281                .map_err(|_| serde::de::Error::custom("Invalid month"))?
282                .try_into()
283                .map_err(|_| serde::de::Error::custom("Invalid month"))?,
284        })
285    }
286}
287
288/// Deserialize strings into `Cow` to avoid unnecessary allocations
289struct CowVisitor;
290impl<'de> Visitor<'de> for CowVisitor {
291    type Value = Cow<'de, str>;
292
293    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
294        formatter.write_str("a string")
295    }
296
297    fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
298    where
299        E: serde::de::Error,
300    {
301        Ok(Cow::Borrowed(value))
302    }
303
304    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
305    where
306        E: serde::de::Error,
307    {
308        Ok(Cow::Owned(value.to_owned()))
309    }
310
311    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
312    where
313        E: serde::de::Error,
314    {
315        Ok(Cow::Owned(value))
316    }
317}
318
319#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
320#[serde(transparent)]
321pub struct EditableFieldSubdivisionCode(pub String);
322impl EditableFieldType for EditableFieldSubdivisionCode {
323    fn field_type(&self) -> FieldType {
324        FieldType::SubdivisionCode
325    }
326}
327
328impl<E> From<String> for EditableField<EditableFieldSubdivisionCode, E> {
329    fn from(s: String) -> Self {
330        EditableFieldSubdivisionCode(s).into()
331    }
332}
333
334impl<E> From<EditableField<EditableFieldSubdivisionCode, E>> for String {
335    fn from(s: EditableField<EditableFieldSubdivisionCode, E>) -> Self {
336        s.value.0
337    }
338}
339
340#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
341#[serde(transparent)]
342pub struct EditableFieldCountryCode(pub String);
343impl EditableFieldType for EditableFieldCountryCode {
344    fn field_type(&self) -> FieldType {
345        FieldType::CountryCode
346    }
347}
348
349impl<E> From<String> for EditableField<EditableFieldCountryCode, E> {
350    fn from(s: String) -> Self {
351        EditableFieldCountryCode(s).into()
352    }
353}
354
355impl<E> From<EditableField<EditableFieldCountryCode, E>> for String {
356    fn from(s: EditableField<EditableFieldCountryCode, E>) -> Self {
357        s.value.0
358    }
359}
360
361#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
362#[serde(rename_all = "kebab-case")]
363#[non_exhaustive]
364pub enum EditableFieldWifiNetworkSecurityType {
365    Unsecured,
366    WpaPersonal,
367    Wpa2Personal,
368    Wpa3Personal,
369    Wep,
370
371    #[serde(untagged)]
372    Other(String),
373}
374impl EditableFieldType for EditableFieldWifiNetworkSecurityType {
375    fn field_type(&self) -> FieldType {
376        FieldType::WifiNetworkSecurityType
377    }
378}
379
380/// Helper wrapper for `CustomFieldsCredential`.
381#[derive(Clone, Debug, Serialize, Deserialize)]
382#[serde(untagged, bound(deserialize = "E: Deserialize<'de>"))]
383#[non_exhaustive]
384pub enum EditableFieldValue<E = ()> {
385    String(EditableField<EditableFieldString, E>),
386    ConcealedString(EditableField<EditableFieldConcealedString, E>),
387    Boolean(EditableField<EditableFieldBoolean, E>),
388    Date(EditableField<EditableFieldDate, E>),
389    YearMonth(EditableField<EditableFieldYearMonth, E>),
390    SubdivisionCode(EditableField<EditableFieldSubdivisionCode, E>),
391    CountryCode(EditableField<EditableFieldCountryCode, E>),
392    WifiNetworkSecurityType(EditableField<EditableFieldWifiNetworkSecurityType, E>),
393}
394
395mod serde_bool {
396    use serde::Deserialize;
397
398    pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
399    where
400        S: serde::Serializer,
401    {
402        serializer.serialize_str(&value.to_string())
403    }
404
405    pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
406    where
407        D: serde::Deserializer<'de>,
408    {
409        let value = <&str>::deserialize(deserializer)?;
410
411        value
412            .trim()
413            .to_lowercase()
414            .parse()
415            .map_err(serde::de::Error::custom)
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use serde_json::json;
422
423    use super::*;
424
425    #[test]
426    fn test_serialize_editable_field_string() {
427        let field: EditableField<EditableFieldString> = EditableField {
428            id: None,
429            value: EditableFieldString("value".to_string()),
430            label: Some("label".to_string()),
431            extensions: None,
432        };
433        let json = json!({
434            "value": "value",
435            "fieldType": "string",
436            "label": "label",
437        });
438        assert_eq!(serde_json::to_value(&field).unwrap(), json);
439    }
440
441    #[test]
442    fn test_deserialize_field_string() {
443        let json = json!({
444            "value": "value",
445            "fieldType": "string",
446            "label": "label",
447        });
448        let field: EditableField<EditableFieldString> = serde_json::from_value(json).unwrap();
449
450        assert_eq!(
451            field,
452            EditableField {
453                id: None,
454                value: EditableFieldString("value".to_string()),
455                label: Some("label".to_string()),
456                extensions: None,
457            }
458        );
459    }
460
461    #[test]
462    fn test_serialize_field_concealed_string() {
463        let field: EditableField<EditableFieldConcealedString> = EditableField {
464            id: None,
465            value: EditableFieldConcealedString("value".to_string()),
466            label: Some("label".to_string()),
467            extensions: None,
468        };
469        let json = json!({
470            "fieldType": "concealed-string",
471            "value": "value",
472            "label": "label",
473        });
474        assert_eq!(serde_json::to_value(&field).unwrap(), json);
475    }
476
477    #[test]
478    fn test_deserialize_field_wrong_type() {
479        let json = json!({
480            "value": "value",
481            "fieldType": "string",
482            "label": "label",
483        });
484        let field: Result<EditableField<EditableFieldConcealedString>, _> =
485            serde_json::from_value(json);
486
487        assert!(field.is_err());
488    }
489
490    #[test]
491    fn test_deserialize_field_bad_value_string() {
492        let json = json!({
493            "value": 5,
494            "fieldType": "string",
495            "label": "label",
496        });
497        let field: Result<EditableField<EditableFieldString>, _> = serde_json::from_value(json);
498
499        assert!(field.is_err());
500    }
501
502    #[test]
503    fn test_deserialize_field_bad_value_bool() {
504        let json = json!({
505            "value": "bad",
506            "fieldType": "bool",
507            "label": "label",
508        });
509        let field: Result<EditableField<EditableFieldBoolean>, _> = serde_json::from_value(json);
510
511        assert!(field.is_err());
512    }
513
514    #[test]
515    fn test_deserialize_field_missing_type() {
516        let json = json!({
517            "value": "value",
518            "label": "label",
519        });
520        let field: Result<EditableField<EditableFieldConcealedString>, _> =
521            serde_json::from_value(json);
522
523        assert!(field.is_err());
524    }
525
526    #[test]
527    fn test_deserialize_field_concealed_string() {
528        let json = json!({
529            "value": "value",
530            "fieldType": "concealed-string",
531            "label": "label",
532        });
533        let field: EditableField<EditableFieldConcealedString> =
534            serde_json::from_value(json).unwrap();
535
536        assert_eq!(
537            field,
538            EditableField {
539                id: None,
540                value: EditableFieldConcealedString("value".to_string()),
541                label: Some("label".to_string()),
542                extensions: None,
543            }
544        );
545    }
546
547    #[test]
548    fn test_serialize_field_boolean() {
549        let field: EditableField<EditableFieldBoolean> = EditableField {
550            id: None,
551            value: EditableFieldBoolean(true),
552            label: Some("label".to_string()),
553            extensions: None,
554        };
555        let json = json!({
556            "fieldType": "boolean",
557            "value": "true",
558            "label": "label",
559        });
560        assert_eq!(serde_json::to_value(&field).unwrap(), json);
561    }
562
563    #[test]
564    fn test_serialize_field_date() {
565        let field: EditableField<EditableFieldDate> = EditableField {
566            id: None,
567            value: EditableFieldDate(NaiveDate::from_ymd_opt(2025, 2, 24).unwrap()),
568            label: None,
569            extensions: None,
570        };
571        let json = json!({
572            "fieldType": "date",
573            "value": "2025-02-24",
574        });
575        assert_eq!(serde_json::to_value(&field).unwrap(), json);
576    }
577
578    #[test]
579    fn test_serialize_editable_field_year_month() {
580        let field: EditableField<EditableFieldYearMonth> = EditableField {
581            id: None,
582            value: EditableFieldYearMonth {
583                year: 2025,
584                month: Month::February,
585            },
586            label: None,
587            extensions: None,
588        };
589        let json = json!({
590            "fieldType": "year-month",
591            "value": "2025-02",
592        });
593        assert_eq!(serde_json::to_value(&field).unwrap(), json);
594    }
595
596    #[test]
597    fn test_deserialize_editable_field_year_month() {
598        let json = json!({
599            "fieldType": "year-month",
600            "value": "2025-02",
601        });
602        let field: EditableField<EditableFieldYearMonth> = serde_json::from_value(json).unwrap();
603
604        assert_eq!(
605            field,
606            EditableField {
607                id: None,
608                value: EditableFieldYearMonth {
609                    year: 2025,
610                    month: Month::February,
611                },
612                label: None,
613                extensions: None,
614            }
615        );
616    }
617
618    #[test]
619    fn test_deserialize_editable_field_year_month_invalid_format() {
620        let json = json!({
621            "fieldType": "year-month",
622            "value": "2025/02",
623        });
624        let field: Result<EditableField<EditableFieldYearMonth>, _> = serde_json::from_value(json);
625        assert!(field.is_err());
626    }
627
628    #[test]
629    fn test_extension_round_trip() {
630        let json = json!({
631            "fieldType": "string",
632            "value": "hello",
633            "extensions": [
634                {
635                    "name": "test",
636                    "contents": "world"
637                }
638            ]
639        });
640        #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
641        #[serde(tag = "name", rename_all = "camelCase")]
642        enum CustomExtension {
643            Test { contents: String },
644        }
645
646        let field: EditableField<EditableFieldString, CustomExtension> =
647            serde_json::from_value(json.clone()).expect("Could not deserialize custom extensions");
648
649        assert_eq!(
650            field,
651            EditableField {
652                id: None,
653                value: EditableFieldString("hello".to_string()),
654                label: None,
655                extensions: Some(vec![Extension::External(CustomExtension::Test {
656                    contents: "world".into()
657                })])
658            }
659        );
660
661        let returned = serde_json::to_value(field).expect("Could not serialize custom extensions");
662
663        assert_eq!(returned, json);
664    }
665}