use serde::{Serialize, Serializer, de::DeserializeOwned};
pub trait Record: Serialize + DeserializeOwned + Clone + std::fmt::Debug + 'static {
const NSID: &'static str;
#[must_use]
fn kind() -> &'static str {
let n = Self::NSID;
n.rsplit('.').next().unwrap_or(n)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum AnyRecord {
Community(crate::Community),
Dialect(crate::Dialect),
Encounter(crate::Encounter),
Correction(crate::Correction),
Verification(crate::Verification),
Observation(crate::Observation),
Retrospection(crate::Retrospection),
Recommendation(crate::Recommendation),
Adapter(crate::Adapter),
Bounty(crate::Bounty),
Belief(crate::Belief),
Vocab(crate::Vocab),
}
impl AnyRecord {
#[must_use]
pub const fn nsid(&self) -> &'static str {
match self {
Self::Community(_) => crate::Community::NSID,
Self::Dialect(_) => crate::Dialect::NSID,
Self::Encounter(_) => crate::Encounter::NSID,
Self::Correction(_) => crate::Correction::NSID,
Self::Verification(_) => crate::Verification::NSID,
Self::Observation(_) => crate::Observation::NSID,
Self::Retrospection(_) => crate::Retrospection::NSID,
Self::Recommendation(_) => crate::Recommendation::NSID,
Self::Adapter(_) => crate::Adapter::NSID,
Self::Bounty(_) => crate::Bounty::NSID,
Self::Belief(_) => crate::Belief::NSID,
Self::Vocab(_) => crate::Vocab::NSID,
}
}
pub fn to_typed_json(&self) -> Result<serde_json::Value, serde_json::Error> {
let mut value = self.inner_to_json()?;
if let Some(obj) = value.as_object_mut() {
obj.insert(
"$type".to_owned(),
serde_json::Value::String(self.nsid().to_owned()),
);
Ok(value)
} else {
Err(serde::ser::Error::custom(
"record did not serialize to a json object",
))
}
}
pub fn from_typed_json(mut value: serde_json::Value) -> Result<Self, DecodeError> {
let Some(serde_json::Value::String(nsid)) =
value.as_object_mut().and_then(|o| o.remove("$type"))
else {
return Err(DecodeError::UnknownNsid("<missing $type field>".to_owned()));
};
decode_record(&nsid, value)
}
fn inner_to_json(&self) -> Result<serde_json::Value, serde_json::Error> {
match self {
Self::Adapter(r) => serde_json::to_value(r),
Self::Belief(r) => serde_json::to_value(r),
Self::Bounty(r) => serde_json::to_value(r),
Self::Community(r) => serde_json::to_value(r),
Self::Correction(r) => serde_json::to_value(r),
Self::Dialect(r) => serde_json::to_value(r),
Self::Encounter(r) => serde_json::to_value(r),
Self::Observation(r) => serde_json::to_value(r),
Self::Recommendation(r) => serde_json::to_value(r),
Self::Retrospection(r) => serde_json::to_value(r),
Self::Verification(r) => serde_json::to_value(r),
Self::Vocab(r) => serde_json::to_value(r),
}
}
}
impl Serialize for AnyRecord {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let value = self.to_typed_json().map_err(serde::ser::Error::custom)?;
value.serialize(serializer)
}
}
impl std::fmt::Display for AnyRecord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AnyRecord({})", self.nsid())
}
}
pub fn decode_record(nsid: &str, value: serde_json::Value) -> Result<AnyRecord, DecodeError> {
fn from<R: Record>(value: serde_json::Value) -> Result<R, DecodeError> {
serde_json::from_value(value).map_err(DecodeError::Serde)
}
match nsid {
s if s == crate::Community::NSID => Ok(AnyRecord::Community(from(value)?)),
s if s == crate::Dialect::NSID => Ok(AnyRecord::Dialect(from(value)?)),
s if s == crate::Encounter::NSID => Ok(AnyRecord::Encounter(from(value)?)),
s if s == crate::Correction::NSID => Ok(AnyRecord::Correction(from(value)?)),
s if s == crate::Verification::NSID => Ok(AnyRecord::Verification(from(value)?)),
s if s == crate::Observation::NSID => Ok(AnyRecord::Observation(from(value)?)),
s if s == crate::Retrospection::NSID => Ok(AnyRecord::Retrospection(from(value)?)),
s if s == crate::Recommendation::NSID => Ok(AnyRecord::Recommendation(from(value)?)),
s if s == crate::Adapter::NSID => Ok(AnyRecord::Adapter(from(value)?)),
s if s == crate::Bounty::NSID => Ok(AnyRecord::Bounty(from(value)?)),
s if s == crate::Belief::NSID => Ok(AnyRecord::Belief(from(value)?)),
s if s == crate::Vocab::NSID => Ok(AnyRecord::Vocab(from(value)?)),
other => Err(DecodeError::UnknownNsid(other.to_owned())),
}
}
#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
#[error("unknown dev.idiolect.* nsid: {0}")]
UnknownNsid(String),
#[error("record deserialization failed: {0}")]
Serde(#[from] serde_json::Error),
}