#![allow(clippy::large_enum_variant)]
use crate::Nsid;
use crate::Record;
use crate::family::RecordFamily;
use crate::generated::dev::idiolect::adapter::Adapter;
use crate::generated::dev::idiolect::belief::Belief;
use crate::generated::dev::idiolect::bounty::Bounty;
use crate::generated::dev::idiolect::community::Community;
use crate::generated::dev::idiolect::correction::Correction;
use crate::generated::dev::idiolect::deliberation::Deliberation;
use crate::generated::dev::idiolect::deliberation_outcome::DeliberationOutcome;
use crate::generated::dev::idiolect::deliberation_statement::DeliberationStatement;
use crate::generated::dev::idiolect::deliberation_vote::DeliberationVote;
use crate::generated::dev::idiolect::dialect::Dialect;
use crate::generated::dev::idiolect::encounter::Encounter;
use crate::generated::dev::idiolect::observation::Observation;
use crate::generated::dev::idiolect::recommendation::Recommendation;
use crate::generated::dev::idiolect::retrospection::Retrospection;
use crate::generated::dev::idiolect::verification::Verification;
use crate::generated::dev::idiolect::vocab::Vocab;
use crate::record::DecodeError;
use serde::{Serialize, Serializer};
#[derive(Debug, Clone, Copy)]
pub struct IdiolectFamily;
#[derive(Debug, Clone)]
pub enum AnyRecord {
Adapter(Adapter),
Belief(Belief),
Bounty(Bounty),
Community(Community),
Correction(Correction),
Deliberation(Deliberation),
DeliberationOutcome(DeliberationOutcome),
DeliberationStatement(DeliberationStatement),
DeliberationVote(DeliberationVote),
Dialect(Dialect),
Encounter(Encounter),
Observation(Observation),
Recommendation(Recommendation),
Retrospection(Retrospection),
Verification(Verification),
Vocab(Vocab),
}
impl AnyRecord {
#[must_use]
pub const fn nsid_str(&self) -> &'static str {
match self {
Self::Adapter(_) => Adapter::NSID,
Self::Belief(_) => Belief::NSID,
Self::Bounty(_) => Bounty::NSID,
Self::Community(_) => Community::NSID,
Self::Correction(_) => Correction::NSID,
Self::Deliberation(_) => Deliberation::NSID,
Self::DeliberationOutcome(_) => DeliberationOutcome::NSID,
Self::DeliberationStatement(_) => DeliberationStatement::NSID,
Self::DeliberationVote(_) => DeliberationVote::NSID,
Self::Dialect(_) => Dialect::NSID,
Self::Encounter(_) => Encounter::NSID,
Self::Observation(_) => Observation::NSID,
Self::Recommendation(_) => Recommendation::NSID,
Self::Retrospection(_) => Retrospection::NSID,
Self::Verification(_) => Verification::NSID,
Self::Vocab(_) => 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::Deliberation(r) => serde_json::to_value(r),
Self::DeliberationOutcome(r) => serde_json::to_value(r),
Self::DeliberationStatement(r) => serde_json::to_value(r),
Self::DeliberationVote(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 == Adapter::NSID => Ok(AnyRecord::Adapter(from(value)?)),
s if s == Belief::NSID => Ok(AnyRecord::Belief(from(value)?)),
s if s == Bounty::NSID => Ok(AnyRecord::Bounty(from(value)?)),
s if s == Community::NSID => Ok(AnyRecord::Community(from(value)?)),
s if s == Correction::NSID => Ok(AnyRecord::Correction(from(value)?)),
s if s == Deliberation::NSID => Ok(AnyRecord::Deliberation(from(value)?)),
s if s == DeliberationOutcome::NSID => Ok(AnyRecord::DeliberationOutcome(from(value)?)),
s if s == DeliberationStatement::NSID => Ok(AnyRecord::DeliberationStatement(from(value)?)),
s if s == DeliberationVote::NSID => Ok(AnyRecord::DeliberationVote(from(value)?)),
s if s == Dialect::NSID => Ok(AnyRecord::Dialect(from(value)?)),
s if s == Encounter::NSID => Ok(AnyRecord::Encounter(from(value)?)),
s if s == Observation::NSID => Ok(AnyRecord::Observation(from(value)?)),
s if s == Recommendation::NSID => Ok(AnyRecord::Recommendation(from(value)?)),
s if s == Retrospection::NSID => Ok(AnyRecord::Retrospection(from(value)?)),
s if s == Verification::NSID => Ok(AnyRecord::Verification(from(value)?)),
s if s == 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(),
Adapter::NSID
| Belief::NSID
| Bounty::NSID
| Community::NSID
| Correction::NSID
| Deliberation::NSID
| DeliberationOutcome::NSID
| DeliberationStatement::NSID
| DeliberationVote::NSID
| Dialect::NSID
| Encounter::NSID
| Observation::NSID
| Recommendation::NSID
| Retrospection::NSID
| Verification::NSID
| 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()
}
}