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}