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.
63trait 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 + self.id.is_some() as usize + self.label.is_some() as usize;
78        let mut state = serializer.serialize_struct("editable_field", len)?;
79
80        if let Some(ref id) = self.id {
81            state.serialize_field("id", id)?;
82        } else {
83            state.skip_field("id")?;
84        }
85
86        state.serialize_field("fieldType", &self.value.field_type())?;
87        state.serialize_field("value", &self.value)?;
88
89        if let Some(ref label) = self.label {
90            state.serialize_field("label", label)?;
91        } else {
92            state.skip_field("label")?;
93        }
94
95        state.end()
96    }
97}
98
99impl<'de, T, E> Deserialize<'de> for EditableField<T, E>
100where
101    T: EditableFieldType + DeserializeOwned,
102    E: Deserialize<'de>,
103{
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106        D: serde::Deserializer<'de>,
107    {
108        #[derive(Deserialize)]
109        #[serde(rename_all = "camelCase")]
110        struct EditableFieldHelper<T> {
111            id: Option<B64Url>,
112            value: T,
113            field_type: FieldType,
114            label: Option<String>,
115        }
116
117        let helper: EditableFieldHelper<T> = EditableFieldHelper::deserialize(deserializer)?;
118
119        if helper.field_type != helper.value.field_type() {
120            return Err(serde::de::Error::custom(
121                "field_type does not match value type",
122            ));
123        }
124
125        Ok(Self {
126            id: helper.id,
127            value: helper.value,
128            label: helper.label,
129            extensions: None,
130        })
131    }
132}
133
134// Helper for converting inner types into EditableField
135impl<T, E> From<T> for EditableField<T, E> {
136    fn from(s: T) -> Self {
137        EditableField {
138            id: None,
139            value: s,
140            label: None,
141            extensions: None,
142        }
143    }
144}
145
146#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
147#[serde(transparent)]
148pub struct EditableFieldString(pub String);
149impl EditableFieldType for EditableFieldString {
150    fn field_type(&self) -> FieldType {
151        FieldType::String
152    }
153}
154
155impl<E> From<String> for EditableField<EditableFieldString, E> {
156    fn from(s: String) -> Self {
157        EditableFieldString(s).into()
158    }
159}
160
161impl<E> From<EditableField<EditableFieldString, E>> for String {
162    fn from(s: EditableField<EditableFieldString, E>) -> Self {
163        s.value.0
164    }
165}
166
167#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
168#[serde(transparent)]
169pub struct EditableFieldConcealedString(pub String);
170impl EditableFieldType for EditableFieldConcealedString {
171    fn field_type(&self) -> FieldType {
172        FieldType::ConcealedString
173    }
174}
175
176impl<E> From<String> for EditableField<EditableFieldConcealedString, E> {
177    fn from(s: String) -> Self {
178        EditableFieldConcealedString(s).into()
179    }
180}
181
182impl<E> From<EditableField<EditableFieldConcealedString, E>> for String {
183    fn from(s: EditableField<EditableFieldConcealedString, E>) -> Self {
184        s.value.0
185    }
186}
187
188#[derive(Clone, Debug, Serialize, Deserialize)]
189pub struct EditableFieldBoolean(#[serde(with = "serde_bool")] pub bool);
190impl EditableFieldType for EditableFieldBoolean {
191    fn field_type(&self) -> FieldType {
192        FieldType::Boolean
193    }
194}
195
196impl<E> From<bool> for EditableField<EditableFieldBoolean, E> {
197    fn from(b: bool) -> Self {
198        EditableFieldBoolean(b).into()
199    }
200}
201
202impl<E> From<EditableField<EditableFieldBoolean, E>> for bool {
203    fn from(b: EditableField<EditableFieldBoolean, E>) -> Self {
204        b.value.0
205    }
206}
207
208#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
209#[serde(transparent)]
210pub struct EditableFieldDate(pub NaiveDate);
211impl EditableFieldType for EditableFieldDate {
212    fn field_type(&self) -> FieldType {
213        FieldType::Date
214    }
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
218pub struct EditableFieldYearMonth {
219    /// The year in the format `YYYY`
220    pub year: u16,
221    /// The month in the format `MM`
222    pub month: Month,
223}
224impl EditableFieldType for EditableFieldYearMonth {
225    fn field_type(&self) -> FieldType {
226        FieldType::YearMonth
227    }
228}
229
230impl Serialize for EditableFieldYearMonth {
231    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232    where
233        S: serde::Serializer,
234    {
235        serializer.serialize_str(&format!(
236            "{:04}-{:02}",
237            self.year,
238            self.month.number_from_month()
239        ))
240    }
241}
242
243impl<'de> Deserialize<'de> for EditableFieldYearMonth {
244    fn deserialize<D>(deserializer: D) -> Result<EditableFieldYearMonth, D::Error>
245    where
246        D: serde::Deserializer<'de>,
247    {
248        let s = deserializer.deserialize_str(CowVisitor)?;
249        let (year_str, month_str) = s
250            .split_once('-')
251            .ok_or_else(|| serde::de::Error::custom("Invalid format"))?;
252
253        Ok(EditableFieldYearMonth {
254            year: year_str
255                .parse::<u16>()
256                .map_err(|_| serde::de::Error::custom("Invalid year"))?,
257            month: month_str
258                .parse::<u8>()
259                .map_err(|_| serde::de::Error::custom("Invalid month"))?
260                .try_into()
261                .map_err(|_| serde::de::Error::custom("Invalid month"))?,
262        })
263    }
264}
265
266/// Deserialize strings into `Cow` to avoid unnecessary allocations
267struct CowVisitor;
268impl<'de> Visitor<'de> for CowVisitor {
269    type Value = Cow<'de, str>;
270
271    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
272        formatter.write_str("a string")
273    }
274
275    fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
276    where
277        E: serde::de::Error,
278    {
279        Ok(Cow::Borrowed(value))
280    }
281
282    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
283    where
284        E: serde::de::Error,
285    {
286        Ok(Cow::Owned(value.to_owned()))
287    }
288
289    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
290    where
291        E: serde::de::Error,
292    {
293        Ok(Cow::Owned(value))
294    }
295}
296
297#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
298#[serde(transparent)]
299pub struct EditableFieldSubdivisionCode(pub String);
300impl EditableFieldType for EditableFieldSubdivisionCode {
301    fn field_type(&self) -> FieldType {
302        FieldType::SubdivisionCode
303    }
304}
305
306impl<E> From<String> for EditableField<EditableFieldSubdivisionCode, E> {
307    fn from(s: String) -> Self {
308        EditableFieldSubdivisionCode(s).into()
309    }
310}
311
312impl<E> From<EditableField<EditableFieldSubdivisionCode, E>> for String {
313    fn from(s: EditableField<EditableFieldSubdivisionCode, E>) -> Self {
314        s.value.0
315    }
316}
317
318#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
319#[serde(transparent)]
320pub struct EditableFieldCountryCode(pub String);
321impl EditableFieldType for EditableFieldCountryCode {
322    fn field_type(&self) -> FieldType {
323        FieldType::CountryCode
324    }
325}
326
327impl<E> From<String> for EditableField<EditableFieldCountryCode, E> {
328    fn from(s: String) -> Self {
329        EditableFieldCountryCode(s).into()
330    }
331}
332
333impl<E> From<EditableField<EditableFieldCountryCode, E>> for String {
334    fn from(s: EditableField<EditableFieldCountryCode, E>) -> Self {
335        s.value.0
336    }
337}
338
339#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
340#[serde(rename_all = "kebab-case")]
341#[non_exhaustive]
342pub enum EditableFieldWifiNetworkSecurityType {
343    Unsecured,
344    WpaPersonal,
345    Wpa2Personal,
346    Wpa3Personal,
347    Wep,
348
349    #[serde(untagged)]
350    Other(String),
351}
352impl EditableFieldType for EditableFieldWifiNetworkSecurityType {
353    fn field_type(&self) -> FieldType {
354        FieldType::WifiNetworkSecurityType
355    }
356}
357
358mod serde_bool {
359    use serde::Deserialize;
360
361    pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
362    where
363        S: serde::Serializer,
364    {
365        serializer.serialize_str(&value.to_string())
366    }
367
368    pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
369    where
370        D: serde::Deserializer<'de>,
371    {
372        let value = <&str>::deserialize(deserializer)?;
373
374        value
375            .trim()
376            .to_lowercase()
377            .parse()
378            .map_err(serde::de::Error::custom)
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    use serde_json::json;
385
386    use super::*;
387
388    #[test]
389    fn test_serialize_editable_field_string() {
390        let field: EditableField<EditableFieldString> = EditableField {
391            id: None,
392            value: EditableFieldString("value".to_string()),
393            label: Some("label".to_string()),
394            extensions: None,
395        };
396        let json = json!({
397            "value": "value",
398            "fieldType": "string",
399            "label": "label",
400        });
401        assert_eq!(serde_json::to_value(&field).unwrap(), json);
402    }
403
404    #[test]
405    fn test_deserialize_field_string() {
406        let json = json!({
407            "value": "value",
408            "fieldType": "string",
409            "label": "label",
410        });
411        let field: EditableField<EditableFieldString> = serde_json::from_value(json).unwrap();
412
413        assert_eq!(
414            field,
415            EditableField {
416                id: None,
417                value: EditableFieldString("value".to_string()),
418                label: Some("label".to_string()),
419                extensions: None,
420            }
421        );
422    }
423
424    #[test]
425    fn test_serialize_field_concealed_string() {
426        let field: EditableField<EditableFieldConcealedString> = EditableField {
427            id: None,
428            value: EditableFieldConcealedString("value".to_string()),
429            label: Some("label".to_string()),
430            extensions: None,
431        };
432        let json = json!({
433            "fieldType": "concealed-string",
434            "value": "value",
435            "label": "label",
436        });
437        assert_eq!(serde_json::to_value(&field).unwrap(), json);
438    }
439
440    #[test]
441    fn test_deserialize_field_wrong_type() {
442        let json = json!({
443            "value": "value",
444            "fieldType": "string",
445            "label": "label",
446        });
447        let field: Result<EditableField<EditableFieldConcealedString>, _> =
448            serde_json::from_value(json);
449
450        assert!(field.is_err());
451    }
452
453    #[test]
454    fn test_deserialize_field_bad_value_string() {
455        let json = json!({
456            "value": 5,
457            "fieldType": "string",
458            "label": "label",
459        });
460        let field: Result<EditableField<EditableFieldString>, _> = serde_json::from_value(json);
461
462        assert!(field.is_err());
463    }
464
465    #[test]
466    fn test_deserialize_field_bad_value_bool() {
467        let json = json!({
468            "value": "bad",
469            "fieldType": "bool",
470            "label": "label",
471        });
472        let field: Result<EditableField<EditableFieldBoolean>, _> = serde_json::from_value(json);
473
474        assert!(field.is_err());
475    }
476
477    #[test]
478    fn test_deserialize_field_missing_type() {
479        let json = json!({
480            "value": "value",
481            "label": "label",
482        });
483        let field: Result<EditableField<EditableFieldConcealedString>, _> =
484            serde_json::from_value(json);
485
486        assert!(field.is_err());
487    }
488
489    #[test]
490    fn test_deserialize_field_concealed_string() {
491        let json = json!({
492            "value": "value",
493            "fieldType": "concealed-string",
494            "label": "label",
495        });
496        let field: EditableField<EditableFieldConcealedString> =
497            serde_json::from_value(json).unwrap();
498
499        assert_eq!(
500            field,
501            EditableField {
502                id: None,
503                value: EditableFieldConcealedString("value".to_string()),
504                label: Some("label".to_string()),
505                extensions: None,
506            }
507        );
508    }
509
510    #[test]
511    fn test_serialize_field_boolean() {
512        let field: EditableField<EditableFieldBoolean> = EditableField {
513            id: None,
514            value: EditableFieldBoolean(true),
515            label: Some("label".to_string()),
516            extensions: None,
517        };
518        let json = json!({
519            "fieldType": "boolean",
520            "value": "true",
521            "label": "label",
522        });
523        assert_eq!(serde_json::to_value(&field).unwrap(), json);
524    }
525
526    #[test]
527    fn test_serialize_field_date() {
528        let field: EditableField<EditableFieldDate> = EditableField {
529            id: None,
530            value: EditableFieldDate(NaiveDate::from_ymd_opt(2025, 2, 24).unwrap()),
531            label: None,
532            extensions: None,
533        };
534        let json = json!({
535            "fieldType": "date",
536            "value": "2025-02-24",
537        });
538        assert_eq!(serde_json::to_value(&field).unwrap(), json);
539    }
540
541    #[test]
542    fn test_serialize_editable_field_year_month() {
543        let field: EditableField<EditableFieldYearMonth> = EditableField {
544            id: None,
545            value: EditableFieldYearMonth {
546                year: 2025,
547                month: Month::February,
548            },
549            label: None,
550            extensions: None,
551        };
552        let json = json!({
553            "fieldType": "year-month",
554            "value": "2025-02",
555        });
556        assert_eq!(serde_json::to_value(&field).unwrap(), json);
557    }
558
559    #[test]
560    fn test_deserialize_editable_field_year_month() {
561        let json = json!({
562            "fieldType": "year-month",
563            "value": "2025-02",
564        });
565        let field: EditableField<EditableFieldYearMonth> = serde_json::from_value(json).unwrap();
566
567        assert_eq!(
568            field,
569            EditableField {
570                id: None,
571                value: EditableFieldYearMonth {
572                    year: 2025,
573                    month: Month::February,
574                },
575                label: None,
576                extensions: None,
577            }
578        );
579    }
580
581    #[test]
582    fn test_deserialize_editable_field_year_month_invalid_format() {
583        let json = json!({
584            "fieldType": "year-month",
585            "value": "2025/02",
586        });
587        let field: Result<EditableField<EditableFieldYearMonth>, _> = serde_json::from_value(json);
588        assert!(field.is_err());
589    }
590}