use std::{borrow::Cow, fmt, str};
use chrono::{Month, NaiveDate};
use serde::{
de::{DeserializeOwned, Visitor},
ser::SerializeStruct,
Deserialize, Serialize,
};
use crate::{B64Url, Extension};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditableField<T, E = ()> {
pub id: Option<B64Url>,
pub value: T,
pub label: Option<String>,
pub extensions: Option<Vec<Extension<E>>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum FieldType {
String,
ConcealedString,
Email,
Number,
Boolean,
Date,
YearMonth,
WifiNetworkSecurityType,
SubdivisionCode,
CountryCode,
#[serde(untagged)]
Unknown(String),
}
pub trait EditableFieldType {
fn field_type(&self) -> FieldType;
}
impl<T, E> Serialize for EditableField<T, E>
where
T: EditableFieldType + Serialize,
E: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let len = 2
+ self.id.is_some() as usize
+ self.label.is_some() as usize
+ self.extensions.is_some() as usize;
let mut state = serializer.serialize_struct("editable_field", len)?;
if let Some(ref id) = self.id {
state.serialize_field("id", id)?;
} else {
state.skip_field("id")?;
}
state.serialize_field("fieldType", &self.value.field_type())?;
state.serialize_field("value", &self.value)?;
if let Some(ref label) = self.label {
state.serialize_field("label", label)?;
} else {
state.skip_field("label")?;
}
if let Some(ref ext) = self.extensions {
if ext.is_empty() {
state.skip_field("extensions")?;
} else {
state.serialize_field("extensions", ext)?;
}
} else {
state.skip_field("extensions")?;
}
state.end()
}
}
impl<'de, T, E> Deserialize<'de> for EditableField<T, E>
where
T: EditableFieldType + DeserializeOwned,
E: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct EditableFieldHelper<T, E> {
#[serde(default)]
id: Option<B64Url>,
value: T,
field_type: FieldType,
#[serde(default)]
label: Option<String>,
#[serde(default = "none::<E>")]
extensions: Option<Vec<Extension<E>>>,
}
fn none<E>() -> Option<Vec<Extension<E>>> {
None
}
let helper: EditableFieldHelper<T, E> = EditableFieldHelper::deserialize(deserializer)?;
if helper.field_type != helper.value.field_type() {
return Err(serde::de::Error::custom(
"field_type does not match value type",
));
}
Ok(Self {
id: helper.id,
value: helper.value,
label: helper.label,
extensions: helper.extensions,
})
}
}
impl<T, E> From<T> for EditableField<T, E> {
fn from(s: T) -> Self {
EditableField {
id: None,
value: s,
label: None,
extensions: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct EditableFieldString(pub String);
impl EditableFieldType for EditableFieldString {
fn field_type(&self) -> FieldType {
FieldType::String
}
}
impl<E> From<String> for EditableField<EditableFieldString, E> {
fn from(s: String) -> Self {
EditableFieldString(s).into()
}
}
impl<E> From<EditableField<EditableFieldString, E>> for String {
fn from(s: EditableField<EditableFieldString, E>) -> Self {
s.value.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct EditableFieldConcealedString(pub String);
impl EditableFieldType for EditableFieldConcealedString {
fn field_type(&self) -> FieldType {
FieldType::ConcealedString
}
}
impl<E> From<String> for EditableField<EditableFieldConcealedString, E> {
fn from(s: String) -> Self {
EditableFieldConcealedString(s).into()
}
}
impl<E> From<EditableField<EditableFieldConcealedString, E>> for String {
fn from(s: EditableField<EditableFieldConcealedString, E>) -> Self {
s.value.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EditableFieldBoolean(#[serde(with = "serde_bool")] pub bool);
impl EditableFieldType for EditableFieldBoolean {
fn field_type(&self) -> FieldType {
FieldType::Boolean
}
}
impl<E> From<bool> for EditableField<EditableFieldBoolean, E> {
fn from(b: bool) -> Self {
EditableFieldBoolean(b).into()
}
}
impl<E> From<EditableField<EditableFieldBoolean, E>> for bool {
fn from(b: EditableField<EditableFieldBoolean, E>) -> Self {
b.value.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct EditableFieldDate(pub NaiveDate);
impl EditableFieldType for EditableFieldDate {
fn field_type(&self) -> FieldType {
FieldType::Date
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditableFieldYearMonth {
pub year: u16,
pub month: Month,
}
impl EditableFieldType for EditableFieldYearMonth {
fn field_type(&self) -> FieldType {
FieldType::YearMonth
}
}
impl Serialize for EditableFieldYearMonth {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!(
"{:04}-{:02}",
self.year,
self.month.number_from_month()
))
}
}
impl<'de> Deserialize<'de> for EditableFieldYearMonth {
fn deserialize<D>(deserializer: D) -> Result<EditableFieldYearMonth, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = deserializer.deserialize_str(CowVisitor)?;
let (year_str, month_str) = s
.split_once('-')
.ok_or_else(|| serde::de::Error::custom("Invalid format"))?;
Ok(EditableFieldYearMonth {
year: year_str
.parse::<u16>()
.map_err(|_| serde::de::Error::custom("Invalid year"))?,
month: month_str
.parse::<u8>()
.map_err(|_| serde::de::Error::custom("Invalid month"))?
.try_into()
.map_err(|_| serde::de::Error::custom("Invalid month"))?,
})
}
}
struct CowVisitor;
impl<'de> Visitor<'de> for CowVisitor {
type Value = Cow<'de, str>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Cow::Borrowed(value))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Cow::Owned(value.to_owned()))
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Cow::Owned(value))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct EditableFieldSubdivisionCode(pub String);
impl EditableFieldType for EditableFieldSubdivisionCode {
fn field_type(&self) -> FieldType {
FieldType::SubdivisionCode
}
}
impl<E> From<String> for EditableField<EditableFieldSubdivisionCode, E> {
fn from(s: String) -> Self {
EditableFieldSubdivisionCode(s).into()
}
}
impl<E> From<EditableField<EditableFieldSubdivisionCode, E>> for String {
fn from(s: EditableField<EditableFieldSubdivisionCode, E>) -> Self {
s.value.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct EditableFieldCountryCode(pub String);
impl EditableFieldType for EditableFieldCountryCode {
fn field_type(&self) -> FieldType {
FieldType::CountryCode
}
}
impl<E> From<String> for EditableField<EditableFieldCountryCode, E> {
fn from(s: String) -> Self {
EditableFieldCountryCode(s).into()
}
}
impl<E> From<EditableField<EditableFieldCountryCode, E>> for String {
fn from(s: EditableField<EditableFieldCountryCode, E>) -> Self {
s.value.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum EditableFieldWifiNetworkSecurityType {
Unsecured,
WpaPersonal,
Wpa2Personal,
Wpa3Personal,
Wep,
#[serde(untagged)]
Other(String),
}
impl EditableFieldType for EditableFieldWifiNetworkSecurityType {
fn field_type(&self) -> FieldType {
FieldType::WifiNetworkSecurityType
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged, bound(deserialize = "E: Deserialize<'de>"))]
#[non_exhaustive]
pub enum EditableFieldValue<E = ()> {
String(EditableField<EditableFieldString, E>),
ConcealedString(EditableField<EditableFieldConcealedString, E>),
Boolean(EditableField<EditableFieldBoolean, E>),
Date(EditableField<EditableFieldDate, E>),
YearMonth(EditableField<EditableFieldYearMonth, E>),
SubdivisionCode(EditableField<EditableFieldSubdivisionCode, E>),
CountryCode(EditableField<EditableFieldCountryCode, E>),
WifiNetworkSecurityType(EditableField<EditableFieldWifiNetworkSecurityType, E>),
}
mod serde_bool {
use serde::Deserialize;
pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <&str>::deserialize(deserializer)?;
value
.trim()
.to_lowercase()
.parse()
.map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_serialize_editable_field_string() {
let field: EditableField<EditableFieldString> = EditableField {
id: None,
value: EditableFieldString("value".to_string()),
label: Some("label".to_string()),
extensions: None,
};
let json = json!({
"value": "value",
"fieldType": "string",
"label": "label",
});
assert_eq!(serde_json::to_value(&field).unwrap(), json);
}
#[test]
fn test_deserialize_field_string() {
let json = json!({
"value": "value",
"fieldType": "string",
"label": "label",
});
let field: EditableField<EditableFieldString> = serde_json::from_value(json).unwrap();
assert_eq!(
field,
EditableField {
id: None,
value: EditableFieldString("value".to_string()),
label: Some("label".to_string()),
extensions: None,
}
);
}
#[test]
fn test_serialize_field_concealed_string() {
let field: EditableField<EditableFieldConcealedString> = EditableField {
id: None,
value: EditableFieldConcealedString("value".to_string()),
label: Some("label".to_string()),
extensions: None,
};
let json = json!({
"fieldType": "concealed-string",
"value": "value",
"label": "label",
});
assert_eq!(serde_json::to_value(&field).unwrap(), json);
}
#[test]
fn test_deserialize_field_wrong_type() {
let json = json!({
"value": "value",
"fieldType": "string",
"label": "label",
});
let field: Result<EditableField<EditableFieldConcealedString>, _> =
serde_json::from_value(json);
assert!(field.is_err());
}
#[test]
fn test_deserialize_field_bad_value_string() {
let json = json!({
"value": 5,
"fieldType": "string",
"label": "label",
});
let field: Result<EditableField<EditableFieldString>, _> = serde_json::from_value(json);
assert!(field.is_err());
}
#[test]
fn test_deserialize_field_bad_value_bool() {
let json = json!({
"value": "bad",
"fieldType": "bool",
"label": "label",
});
let field: Result<EditableField<EditableFieldBoolean>, _> = serde_json::from_value(json);
assert!(field.is_err());
}
#[test]
fn test_deserialize_field_missing_type() {
let json = json!({
"value": "value",
"label": "label",
});
let field: Result<EditableField<EditableFieldConcealedString>, _> =
serde_json::from_value(json);
assert!(field.is_err());
}
#[test]
fn test_deserialize_field_concealed_string() {
let json = json!({
"value": "value",
"fieldType": "concealed-string",
"label": "label",
});
let field: EditableField<EditableFieldConcealedString> =
serde_json::from_value(json).unwrap();
assert_eq!(
field,
EditableField {
id: None,
value: EditableFieldConcealedString("value".to_string()),
label: Some("label".to_string()),
extensions: None,
}
);
}
#[test]
fn test_serialize_field_boolean() {
let field: EditableField<EditableFieldBoolean> = EditableField {
id: None,
value: EditableFieldBoolean(true),
label: Some("label".to_string()),
extensions: None,
};
let json = json!({
"fieldType": "boolean",
"value": "true",
"label": "label",
});
assert_eq!(serde_json::to_value(&field).unwrap(), json);
}
#[test]
fn test_serialize_field_date() {
let field: EditableField<EditableFieldDate> = EditableField {
id: None,
value: EditableFieldDate(NaiveDate::from_ymd_opt(2025, 2, 24).unwrap()),
label: None,
extensions: None,
};
let json = json!({
"fieldType": "date",
"value": "2025-02-24",
});
assert_eq!(serde_json::to_value(&field).unwrap(), json);
}
#[test]
fn test_serialize_editable_field_year_month() {
let field: EditableField<EditableFieldYearMonth> = EditableField {
id: None,
value: EditableFieldYearMonth {
year: 2025,
month: Month::February,
},
label: None,
extensions: None,
};
let json = json!({
"fieldType": "year-month",
"value": "2025-02",
});
assert_eq!(serde_json::to_value(&field).unwrap(), json);
}
#[test]
fn test_deserialize_editable_field_year_month() {
let json = json!({
"fieldType": "year-month",
"value": "2025-02",
});
let field: EditableField<EditableFieldYearMonth> = serde_json::from_value(json).unwrap();
assert_eq!(
field,
EditableField {
id: None,
value: EditableFieldYearMonth {
year: 2025,
month: Month::February,
},
label: None,
extensions: None,
}
);
}
#[test]
fn test_deserialize_editable_field_year_month_invalid_format() {
let json = json!({
"fieldType": "year-month",
"value": "2025/02",
});
let field: Result<EditableField<EditableFieldYearMonth>, _> = serde_json::from_value(json);
assert!(field.is_err());
}
#[test]
fn test_extension_round_trip() {
let json = json!({
"fieldType": "string",
"value": "hello",
"extensions": [
{
"name": "test",
"contents": "world"
}
]
});
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(tag = "name", rename_all = "camelCase")]
enum CustomExtension {
Test { contents: String },
}
let field: EditableField<EditableFieldString, CustomExtension> =
serde_json::from_value(json.clone()).expect("Could not deserialize custom extensions");
assert_eq!(
field,
EditableField {
id: None,
value: EditableFieldString("hello".to_string()),
label: None,
extensions: Some(vec![Extension::External(CustomExtension::Test {
contents: "world".into()
})])
}
);
let returned = serde_json::to_value(field).expect("Could not serialize custom extensions");
assert_eq!(returned, json);
}
}