use bytes::Bytes;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use serde_with::{base64::Base64, serde_as};
#[derive(Debug, Clone, PartialEq, Default)]
pub enum Container {
#[default]
Legacy,
Cmaf {
init: Bytes,
},
Loc,
Unknown(UnknownContainer),
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnknownContainer(serde_json::Map<String, serde_json::Value>);
impl UnknownContainer {
pub fn kind(&self) -> Option<&str> {
self.0.get("kind").and_then(serde_json::Value::as_str)
}
pub fn fields(&self) -> &serde_json::Map<String, serde_json::Value> {
&self.0
}
}
#[serde_as]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "kind")]
enum Known {
#[serde(rename = "legacy")]
Legacy,
Cmaf {
#[serde_as(as = "Base64")]
init: Bytes,
},
Loc,
}
impl Serialize for Container {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let known = match self {
Self::Legacy => Known::Legacy,
Self::Cmaf { init } => Known::Cmaf { init: init.clone() },
Self::Loc => Known::Loc,
Self::Unknown(unknown) => return unknown.0.serialize(serializer),
};
known.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Container {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let object = serde_json::Map::deserialize(deserializer)?;
match object.get("kind").and_then(serde_json::Value::as_str) {
Some("legacy" | "cmaf" | "loc") => {
let known = Known::deserialize(serde_json::Value::Object(object)).map_err(de::Error::custom)?;
Ok(match known {
Known::Legacy => Self::Legacy,
Known::Cmaf { init } => Self::Cmaf { init },
Known::Loc => Self::Loc,
})
}
_ => Ok(Self::Unknown(UnknownContainer(object))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legacy_roundtrip() {
let parsed: Container = serde_json::from_str(r#"{"kind":"legacy"}"#).unwrap();
assert_eq!(parsed, Container::Legacy);
assert_eq!(serde_json::to_string(&parsed).unwrap(), r#"{"kind":"legacy"}"#);
}
#[test]
fn cmaf_roundtrip() {
let parsed: Container = serde_json::from_str(r#"{"kind":"cmaf","init":"AAEC"}"#).unwrap();
assert_eq!(
parsed,
Container::Cmaf {
init: Bytes::from_static(&[0, 1, 2])
}
);
assert_eq!(
serde_json::to_string(&parsed).unwrap(),
r#"{"kind":"cmaf","init":"AAEC"}"#
);
}
#[test]
fn loc_roundtrip() {
let parsed: Container = serde_json::from_str(r#"{"kind":"loc"}"#).unwrap();
assert_eq!(parsed, Container::Loc);
assert_eq!(serde_json::to_string(&parsed).unwrap(), r#"{"kind":"loc"}"#);
}
#[test]
fn unknown_roundtrip() {
let json = r#"{"extra":{"nested":[1,2]},"flag":true,"kind":"future"}"#;
let parsed: Container = serde_json::from_str(json).unwrap();
let Container::Unknown(unknown) = &parsed else {
panic!("expected unknown: {parsed:?}");
};
assert_eq!(unknown.kind(), Some("future"));
assert_eq!(unknown.fields().len(), 3);
assert_eq!(serde_json::to_string(&parsed).unwrap(), json);
}
#[test]
fn malformed_known_kind_errors() {
serde_json::from_str::<Container>(r#"{"kind":"cmaf"}"#).unwrap_err();
}
}