jacquard_common/types/
collection.rs

1use core::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::IntoStatic;
6use crate::types::value::Data;
7use crate::types::{
8    aturi::RepoPath,
9    nsid::Nsid,
10    recordkey::{RecordKey, RecordKeyType, Rkey},
11};
12use crate::xrpc::XrpcResp;
13
14/// Trait for a collection of records that can be stored in a repository.
15///
16/// The records all have the same Lexicon schema.
17///
18/// Implemented on the record type itself.
19pub trait Collection: fmt::Debug + Serialize {
20    /// The NSID for the Lexicon that defines the schema of records in this collection.
21    const NSID: &'static str;
22
23    /// A marker type implementing [`XrpcResp`] that allows typed deserialization of records
24    /// from this collection. Used by [`Agent::get_record`] to return properly typed responses.
25    type Record: XrpcResp;
26
27    /// Returns the [`Nsid`] for the Lexicon that defines the schema of records in this
28    /// collection.
29    ///
30    /// This is a convenience method that parses [`Self::NSID`].
31    ///
32    /// # Panics
33    ///
34    /// Panics if [`Self::NSID`] is not a valid NSID.
35    ///
36    /// [`Nsid`]: crate::types::string::Nsid
37    fn nsid() -> crate::types::nsid::Nsid<'static> {
38        Nsid::new_static(Self::NSID).expect("should be valid NSID")
39    }
40
41    /// Returns the repo path for a record in this collection with the given record key.
42    ///
43    /// Per the [Repo Data Structure v3] specification:
44    /// > Repo paths currently have a fixed structure of `<collection>/<record-key>`. This
45    /// > means a valid, normalized [`Nsid`], followed by a `/`, followed by a valid
46    /// > [`RecordKey`].
47    ///
48    /// [Repo Data Structure v3]: https://atproto.com/specs/repository#repo-data-structure-v3
49    /// [`Nsid`]: crate::types::string::Nsid
50    fn repo_path<'u, T: RecordKeyType>(
51        rkey: &'u crate::types::recordkey::RecordKey<T>,
52    ) -> RepoPath<'u> {
53        RepoPath {
54            collection: Self::nsid(),
55            rkey: Some(RecordKey::from(Rkey::raw(rkey.as_ref()))),
56        }
57    }
58}
59
60/// Generic error type for record retrieval operations.
61///
62/// Used by generated collection marker types as their error type.
63#[derive(
64    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error, miette::Diagnostic,
65)]
66#[serde(tag = "error", content = "message")]
67pub enum RecordError<'a> {
68    /// The requested record was not found
69    #[error("RecordNotFound")]
70    #[serde(rename = "RecordNotFound")]
71    RecordNotFound(Option<String>),
72    /// An unknown error occurred
73    #[error("Unknown")]
74    #[serde(rename = "Unknown")]
75    #[serde(borrow)]
76    Unknown(Data<'a>),
77}
78
79impl IntoStatic for RecordError<'_> {
80    type Output = RecordError<'static>;
81
82    fn into_static(self) -> Self::Output {
83        match self {
84            RecordError::RecordNotFound(msg) => RecordError::RecordNotFound(msg),
85            RecordError::Unknown(data) => RecordError::Unknown(data.into_static()),
86        }
87    }
88}