idiolect-records 0.6.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::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(crate::Adapter),
    /// A `dev.idiolect.belief` record.
    Belief(crate::Belief),
    /// A `dev.idiolect.bounty` record.
    Bounty(crate::Bounty),
    /// A `dev.idiolect.community` record.
    Community(crate::Community),
    /// A `dev.idiolect.correction` record.
    Correction(crate::Correction),
    /// A `dev.idiolect.dialect` record.
    Dialect(crate::Dialect),
    /// A `dev.idiolect.encounter` record.
    Encounter(crate::Encounter),
    /// A `dev.idiolect.observation` record.
    Observation(crate::Observation),
    /// A `dev.idiolect.recommendation` record.
    Recommendation(crate::Recommendation),
    /// A `dev.idiolect.retrospection` record.
    Retrospection(crate::Retrospection),
    /// A `dev.idiolect.verification` record.
    Verification(crate::Verification),
    /// A `dev.idiolect.vocab` record.
    Vocab(crate::Vocab),
}
impl AnyRecord {
    /// Canonical NSID string of the contained record.
    #[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,
        }
    }
    /// 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::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 == 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()
    }
}