#[derive(Debug, serde::Deserialize)]
#[serde(untagged)]
pub(crate) enum BitfieldRepr<T> {
Bits(T),
Object {
bits: T,
},
}
impl<T> BitfieldRepr<T> {
pub(crate) fn into_bits(self) -> T {
match self {
Self::Bits(bits) | Self::Object { bits } => bits,
}
}
}
macro_rules! impl_bitfield_serde {
($t:ty, $int:ty, $( $name:literal => $mask:expr ),+ $(,)?) => {
impl serde::Serialize for $t {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct as _;
let mut flags: Vec<&'static str> = Vec::new();
$(
{
let mask: $int = $mask;
if mask != 0 && self.0 & mask == mask {
flags.push($name);
}
}
)+
let mut state = serializer.serialize_struct("Bitfield", 2)?;
state.serialize_field("bits", &self.0)?;
state.serialize_field("flags", &flags)?;
state.end()
}
}
impl<'de> serde::Deserialize<'de> for $t {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let repr = $crate::serde_helpers::BitfieldRepr::<$int>::deserialize(
deserializer,
)?;
Ok(Self(repr.into_bits()))
}
}
};
}
pub(crate) use impl_bitfield_serde;
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
#[test]
fn test_bitfield_serialises_bits_and_flags() -> Result<(), Box<dyn std::error::Error>> {
let permissions = crate::lsl::ScriptPermissions(
crate::lsl::ScriptPermissions::DEBIT | crate::lsl::ScriptPermissions::TRIGGER_ANIMATION,
);
let json = serde_json::to_value(permissions)?;
assert_eq!(
json,
serde_json::json!({ "bits": 18, "flags": ["DEBIT", "TRIGGER_ANIMATION"] })
);
Ok(())
}
#[test]
fn test_bitfield_round_trips_including_unknown_bits() -> Result<(), Box<dyn std::error::Error>>
{
let raw = crate::lsl::ScriptPermissions::DEBIT | (1 << 30);
let permissions = crate::lsl::ScriptPermissions(raw);
let json = serde_json::to_string(&permissions)?;
let back: crate::lsl::ScriptPermissions = serde_json::from_str(&json)?;
assert_eq!(back, permissions);
let from_int: crate::lsl::ScriptPermissions = serde_json::from_str(&raw.to_string())?;
assert_eq!(from_int, permissions);
Ok(())
}
}