#![allow(clippy::large_enum_variant)]
use crate::Nsid;
use crate::Record;
use crate::family::RecordFamily;
use crate::record::DecodeError;
use serde::{Serialize, Serializer};
#[derive(Debug, Clone, Copy)]
pub struct IdiolectFamily;
#[derive(Debug, Clone)]
pub enum AnyRecord {
Adapter(crate::Adapter),
Belief(crate::Belief),
Bounty(crate::Bounty),
Community(crate::Community),
Correction(crate::Correction),
Dialect(crate::Dialect),
Encounter(crate::Encounter),
Observation(crate::Observation),
Recommendation(crate::Recommendation),
Retrospection(crate::Retrospection),
Verification(crate::Verification),
Vocab(crate::Vocab),
}
impl AnyRecord {
#[must_use]
pub const fn nsid_str(&self) -> &'static str {
match self {
Self::Adapter(_) => crate::Adapter::NSID,
Self::Belief(_) => crate::Belief::NSID,
Self::Bounty(_) => crate::Bounty::NSID,
Self::Community(_) => crate::Community::NSID,
Self::Correction(_) => crate::Correction::NSID,
Self::Dialect(_) => crate::Dialect::NSID,
Self::Encounter(_) => crate::Encounter::NSID,
Self::Observation(_) => crate::Observation::NSID,
Self::Recommendation(_) => crate::Recommendation::NSID,
Self::Retrospection(_) => crate::Retrospection::NSID,
Self::Verification(_) => crate::Verification::NSID,
Self::Vocab(_) => crate::Vocab::NSID,
}
}
#[must_use]
pub fn nsid(&self) -> Nsid {
Nsid::parse(self.nsid_str()).expect("Record::NSID must be a valid atproto 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_str().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_str)) =
value.as_object_mut().and_then(|o| o.remove("$type"))
else {
return Err(DecodeError::UnknownNsid("<missing $type field>".to_owned()));
};
let nsid = Nsid::parse(&nsid_str).map_err(|_| DecodeError::UnknownNsid(nsid_str))?;
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_str())
}
}
pub fn decode_record(nsid: &Nsid, 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)
}
let s = nsid.as_str();
match s {
s if s == crate::Adapter::NSID => Ok(AnyRecord::Adapter(from(value)?)),
s if s == crate::Belief::NSID => Ok(AnyRecord::Belief(from(value)?)),
s if s == crate::Bounty::NSID => Ok(AnyRecord::Bounty(from(value)?)),
s if s == crate::Community::NSID => Ok(AnyRecord::Community(from(value)?)),
s if s == crate::Correction::NSID => Ok(AnyRecord::Correction(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::Observation::NSID => Ok(AnyRecord::Observation(from(value)?)),
s if s == crate::Recommendation::NSID => Ok(AnyRecord::Recommendation(from(value)?)),
s if s == crate::Retrospection::NSID => Ok(AnyRecord::Retrospection(from(value)?)),
s if s == crate::Verification::NSID => Ok(AnyRecord::Verification(from(value)?)),
s if s == crate::Vocab::NSID => Ok(AnyRecord::Vocab(from(value)?)),
other => Err(DecodeError::UnknownNsid(other.to_owned())),
}
}
impl RecordFamily for IdiolectFamily {
type AnyRecord = AnyRecord;
const ID: &'static str = "dev.idiolect";
fn contains(nsid: &Nsid) -> bool {
matches!(
nsid.as_str(),
crate::Adapter::NSID
| crate::Belief::NSID
| crate::Bounty::NSID
| crate::Community::NSID
| crate::Correction::NSID
| crate::Dialect::NSID
| crate::Encounter::NSID
| crate::Observation::NSID
| crate::Recommendation::NSID
| crate::Retrospection::NSID
| crate::Verification::NSID
| crate::Vocab::NSID
)
}
fn decode(
nsid: &Nsid,
body: serde_json::Value,
) -> Result<Option<Self::AnyRecord>, DecodeError> {
if !Self::contains(nsid) {
return Ok(None);
}
decode_record(nsid, body).map(Some)
}
fn nsid_str(record: &Self::AnyRecord) -> &'static str {
record.nsid_str()
}
fn to_typed_json(record: &Self::AnyRecord) -> Result<serde_json::Value, serde_json::Error> {
record.to_typed_json()
}
}