use serde::de::{self, Deserializer, IgnoredAny, MapAccess, Visitor};
use std::fmt;
pub trait CodeEnum: Sized + Clone + 'static {
const VALUES: &'static [&'static str];
fn as_str(&self) -> &str;
fn from_code(code: &str) -> Option<Self>;
}
pub trait MapKey: CodeEnum + Ord {
const FIELD: &'static str;
const ALSO_FIELD: Option<&'static str>;
const CODES: &'static [(&'static str, &'static str, Option<&'static str>)];
fn of_element(element: &serde_json::Value) -> Result<Self, String> {
let value = code_str(element.get(Self::FIELD))
.ok_or_else(|| format!("element has no `{}` to derive its key from", Self::FIELD))?;
let also = Self::ALSO_FIELD.and_then(|f| code_str(element.get(f)));
let code = also
.and_then(|a| {
Self::CODES
.iter()
.find(|(_, w, al)| *w == value && *al == Some(a))
})
.or_else(|| Self::CODES.iter().find(|(_, w, _)| *w == value))
.map(|(code, _, _)| *code)
.unwrap_or(value);
Self::from_code(code)
.filter(|k| k.as_str() == code)
.ok_or_else(|| format!("`{}` = {value:?} is not a known qualifier", Self::FIELD))
}
fn fill_element(&self, element: &mut serde_json::Value) {
let Some(obj) = element.as_object_mut() else {
return;
};
let Some((_, written, also)) = Self::CODES.iter().find(|(c, _, _)| *c == self.as_str())
else {
return;
};
obj.entry(Self::FIELD)
.or_insert_with(|| serde_json::Value::String((*written).to_string()));
if let (Some(field), Some(also)) = (Self::ALSO_FIELD, also) {
obj.entry(field)
.or_insert_with(|| serde_json::Value::String((*also).to_string()));
}
}
}
fn code_str(value: Option<&serde_json::Value>) -> Option<&str> {
let value = value?;
value
.as_str()
.or_else(|| value.get("code").and_then(|c| c.as_str()))
}
pub fn deserialize_code<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: CodeEnum,
{
struct CodeVisitor<T>(std::marker::PhantomData<T>);
impl<T: CodeEnum> CodeVisitor<T> {
fn resolve<E: de::Error>(code: &str) -> Result<T, E> {
T::from_code(code).ok_or_else(|| de::Error::unknown_variant(code, T::VALUES))
}
}
impl<'de, T: CodeEnum> Visitor<'de> for CodeVisitor<T> {
type Value = T;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a code string or a {code, meaning} object")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<T, E> {
Self::resolve(v)
}
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<T, M::Error> {
let mut code: Option<String> = None;
while let Some(key) = map.next_key::<String>()? {
if key == "code" {
code = Some(map.next_value()?);
} else {
map.next_value::<IgnoredAny>()?;
}
}
let code = code.ok_or_else(|| de::Error::missing_field("code"))?;
Self::resolve(&code)
}
}
deserializer.deserialize_any(CodeVisitor(std::marker::PhantomData))
}
#[allow(unused_macros)]
macro_rules! code_enum {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$(
$(#[$vmeta:meta])*
$variant:ident = $value:literal $(| $alias:literal)*
),+ $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
$vis enum $name {
$( $(#[$vmeta])* $variant, )+
Other(String),
}
impl $crate::code_enum::CodeEnum for $name {
const VALUES: &'static [&'static str] = &[$($value),+];
fn as_str(&self) -> &str {
match self {
$(Self::$variant => $value,)+
Self::Other(code) => code,
}
}
fn from_code(code: &str) -> Option<Self> {
Some(match code {
$($value $(| $alias)* => Self::$variant,)+
other => Self::Other(other.to_string()),
})
}
}
$crate::code_enum::code_enum!(@serde $name);
};
(@serde $name:ident) => {
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_str($crate::code_enum::CodeEnum::as_str(self))
}
}
impl ::serde::Serialize for $name {
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str($crate::code_enum::CodeEnum::as_str(self))
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
$crate::code_enum::deserialize_code(d)
}
}
};
}
#[allow(unused_macros)]
macro_rules! key_enum {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$(
$(#[$vmeta:meta])*
$variant:ident = $value:literal
),+ $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
$vis enum $name {
$( $(#[$vmeta])* $variant, )+
}
impl $crate::code_enum::CodeEnum for $name {
const VALUES: &'static [&'static str] = &[$($value),+];
fn as_str(&self) -> &str {
match self {
$(Self::$variant => $value,)+
}
}
fn from_code(code: &str) -> Option<Self> {
match code {
$($value => Some(Self::$variant),)+
_ => None,
}
}
}
$crate::code_enum::code_enum!(@serde $name);
};
}
#[allow(unused_macros)]
macro_rules! map_key {
(
$name:ident: $field:literal $(, also $also:literal)? {
$($code:literal => $written:literal, $also_value:expr),+ $(,)?
}
) => {
impl $crate::code_enum::MapKey for $name {
const FIELD: &'static str = $field;
const ALSO_FIELD: Option<&'static str> = $crate::code_enum::map_key!(@also $($also)?);
const CODES: &'static [(&'static str, &'static str, Option<&'static str>)] =
&[$(($code, $written, $also_value)),+];
}
};
(@also $also:literal) => { Some($also) };
(@also) => { None };
}
#[allow(unused_imports)]
pub(crate) use code_enum;
#[allow(unused_imports)]
pub(crate) use key_enum;
#[allow(unused_imports)]
pub(crate) use map_key;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
code_enum! {
pub enum Partnerrolle {
KundeMsb = "kundeMsb" | "Z39" | "Z40",
Korrespondenz = "korrespondenzKundeMsb" | "Z08",
Z99 = "Z99",
}
}
key_enum! {
pub enum TestKey {
Z08 = "Z08",
Z39 = "Z39",
Z40 = "Z40",
}
}
map_key! {
TestKey: "partnerrolle", also "datenqualitaet" {
"Z08" => "korrespondenzKundeMsb", None,
"Z39" => "kundeMsb", Some("base"),
"Z40" => "kundeMsb", Some("informative"),
}
}
#[test]
fn serializes_as_written_value() {
assert_eq!(json!(Partnerrolle::KundeMsb), json!("kundeMsb"));
assert_eq!(
Partnerrolle::Korrespondenz.to_string(),
"korrespondenzKundeMsb"
);
}
#[test]
fn deserializes_written_value_and_raw_code_aliases() {
for input in ["kundeMsb", "Z39", "Z40"] {
let v: Partnerrolle = serde_json::from_value(json!(input)).unwrap();
assert_eq!(v, Partnerrolle::KundeMsb, "{input}");
}
let v: Partnerrolle = serde_json::from_value(json!("Z99")).unwrap();
assert_eq!(v, Partnerrolle::Z99);
}
#[test]
fn keeps_codes_outside_the_code_list() {
let v: Partnerrolle = serde_json::from_value(json!("Z77")).unwrap();
assert_eq!(v, Partnerrolle::Other("Z77".into()));
assert_eq!(json!(v), json!("Z77"));
let err = serde_json::from_value::<TestKey>(json!("Z77")).unwrap_err();
assert!(err.to_string().contains("Z77"), "{err}");
}
#[test]
fn deserializes_enriched_object() {
let v: Partnerrolle = serde_json::from_value(
json!({"code": "korrespondenzKundeMsb", "meaning": "Korrespondenz", "enum": "X"}),
)
.unwrap();
assert_eq!(v, Partnerrolle::Korrespondenz);
let v: Option<Partnerrolle> =
serde_json::from_value(json!({"meaning": null, "code": "Z40"})).unwrap();
assert_eq!(v, Some(Partnerrolle::KundeMsb));
}
#[test]
fn map_key_resolves_many_to_one_via_also_field() {
let key = |v| TestKey::of_element(&v);
assert_eq!(
key(json!({"partnerrolle": "kundeMsb", "datenqualitaet": "informative"})),
Ok(TestKey::Z40)
);
assert_eq!(
key(
json!({"partnerrolle": {"code": "kundeMsb", "meaning": null}, "datenqualitaet": "base"})
),
Ok(TestKey::Z39)
);
assert_eq!(key(json!({"partnerrolle": "kundeMsb"})), Ok(TestKey::Z39));
assert_eq!(key(json!({"partnerrolle": "Z40"})), Ok(TestKey::Z40));
assert!(key(json!({"name1": "x"})).is_err());
assert!(key(json!({"partnerrolle": "unbekannt"})).is_err());
}
#[test]
fn map_key_fills_missing_fields_only() {
let mut element = json!({"name1": "x"});
TestKey::Z40.fill_element(&mut element);
assert_eq!(
element,
json!({"name1": "x", "partnerrolle": "kundeMsb", "datenqualitaet": "informative"})
);
let mut element = json!({"partnerrolle": "Z40"});
TestKey::Z40.fill_element(&mut element);
assert_eq!(element["partnerrolle"], "Z40");
}
}