idiolect-records 0.8.0

Rust record types mirroring the dev.idiolect.* Lexicon family.
Documentation
// @generated by idiolect-codegen. do not edit.

//! Generated record family for `dev.idiolect`.
//!
//! Per-record types come from sibling generated modules; this
//! file emits the discriminated-union view, the dispatch
//! function, and the [`RecordFamily`] impl that lets every
//! family-agnostic boundary in the workspace work with this
//! family the same way it works with any other.

#![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};
/** Marker type for the `dev.idiolect` family. Implementing
[`RecordFamily`] makes the family first-class alongside any
downstream-curated family or composed [`OrFamily`](crate::OrFamily).*/
#[derive(Debug, Clone, Copy)]
pub struct IdiolectFamily;
/// Discriminated-union view over every record type in the
/// family. Produced by [`decode_record`] when an appview
/// receives JSON whose NSID is only known at runtime
/// (e.g. firehose traffic).
#[derive(Debug, Clone)]
pub enum AnyRecord {
    /// A `dev.idiolect.adapter` record.
    Adapter(Adapter),
    /// A `dev.idiolect.belief` record.
    Belief(Belief),
    /// A `dev.idiolect.bounty` record.
    Bounty(Bounty),
    /// A `dev.idiolect.community` record.
    Community(Community),
    /// A `dev.idiolect.correction` record.
    Correction(Correction),
    /// A `dev.idiolect.deliberation` record.
    Deliberation(Deliberation),
    /// A `dev.idiolect.deliberationOutcome` record.
    DeliberationOutcome(DeliberationOutcome),
    /// A `dev.idiolect.deliberationStatement` record.
    DeliberationStatement(DeliberationStatement),
    /// A `dev.idiolect.deliberationVote` record.
    DeliberationVote(DeliberationVote),
    /// A `dev.idiolect.dialect` record.
    Dialect(Dialect),
    /// A `dev.idiolect.encounter` record.
    Encounter(Encounter),
    /// A `dev.idiolect.observation` record.
    Observation(Observation),
    /// A `dev.idiolect.recommendation` record.
    Recommendation(Recommendation),
    /// A `dev.idiolect.retrospection` record.
    Retrospection(Retrospection),
    /// A `dev.idiolect.verification` record.
    Verification(Verification),
    /// A `dev.idiolect.vocab` record.
    Vocab(Vocab),
}
impl AnyRecord {
    /// Canonical NSID string of the contained record.
    #[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,
        }
    }
    /// Typed NSID of the contained record. Parses
    /// [`Self::nsid_str`] each call.
    ///
    /// # Panics
    ///
    /// Panics if the underlying record's `Record::NSID`
    /// constant is not a valid atproto NSID. Codegen emits
    /// per-record unit tests proving this never panics in
    /// practice.
    #[must_use]
    pub fn nsid(&self) -> Nsid {
        Nsid::parse(self.nsid_str()).expect("Record::NSID must be a valid atproto NSID")
    }
    /// Serialize the inner record into a `serde_json::Value`
    /// and splice in a `$type` field carrying [`Self::nsid`].
    /// This is the wire form atproto records take when
    /// serialized into a `com.atproto.repo.*` xrpc request or
    /// a firehose frame.
    ///
    /// # Errors
    ///
    /// [`serde_json::Error`] when the inner record fails to
    /// serialize, or when its serialized form is not a JSON
    /// object (which never happens for generated record types).
    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",
            ))
        }
    }
    /// Decode a JSON value into the variant identified by its
    /// embedded `$type` field. Internally reads `$type` and
    /// delegates to [`decode_record`].
    ///
    /// # Errors
    ///
    /// [`DecodeError::UnknownNsid`] when `$type` is missing,
    /// not a string, or not a known NSID.
    /// [`DecodeError::Serde`] when the body fails to match
    /// the variant the `$type` selects.
    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),
        }
    }
}
/// Serialize an [`AnyRecord`] in its typed-json wire form.
/// PDSes expect the union discriminator inside the object via
/// `$type`.
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())
    }
}
/// Decode a JSON value into the [`AnyRecord`] variant
/// selected by `nsid`.
///
/// # Errors
///
/// [`DecodeError::UnknownNsid`] if `nsid` is not a member of
/// this family. [`DecodeError::Serde`] if `value` does not
/// deserialize into the record type selected by `nsid`.
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()
    }
}