pub struct Subscription {
pub type: String,
pub url: String,
pub title: Option<String>,
pub site_url: Option<String>,
pub folder: Option<String>,
pub fetch_hint: Option<FetchHint>,
pub private: Option<bool>,
pub created_at: String,
}Expand description
community.lexicon.rss.subscription — a subscription to a syndication feed
(RSS / Atom / JSON Feed). Record key: tid.
url + createdAt are required; everything else is optional.
§Public feeds only (and the reserved private marker)
atproto PDS records are public: anyone can read them via unauthenticated
getRecord / listRecords and off the firehose, and they are retained even
after deleteRecord. A private feed (a Substack …/feed/private/<token>,
a Patreon ?auth=… feed, a Ghost members ?uuid= feed, a private-podcast
token feed, or any URL that carries a secret token / key / auth credential)
has its secret in the URL, so writing that URL here would leak paid /
members-only access to the whole network.
Current decision: FeatherReader supports PUBLIC feeds only. A private
feed is refused at the add / import boundary (see
crate::feed::classify_feed_privacy) — it is never fetched, never stored,
and no record (redacted or otherwise) is ever written. The server therefore
holds NO private secret, which keeps “your data lives in your public PDS”
100% honest. Consequently every Subscription record actually written
carries a real, public feed url, and [private] is always omitted.
The [private] field is retained ONLY as a documented, forward-compatible
reserved marker for the eventual migration once atproto ships
permissioned data / permission-sets (early-proposal as of mid-2026,
bluesky-social/proposals#94). At that point a private feed’s secret can live
in an owner-scoped, permission-gated collection and this record can reference
it with private: true. Until then the field has no runtime behavior —
nothing sets it and nothing branches on it.
Fields§
§type: StringThe $type NSID discriminator; always nsid::SUBSCRIPTION.
url: StringCanonical feed URL (the RSS/Atom/JSON Feed document). Required.
Always a real, PUBLIC feed URL: private/secret-bearing feeds are refused
at the add boundary (see the type-level docs), so no record with a
withheld or redacted url is ever written.
title: Option<String>Display title; a reader MAY override from feed metadata.
site_url: Option<String>Human-facing site the feed belongs to.
Scheme-checked on the way in. Any atproto client can write this field
into the user’s repo, and the lexicon invites readers to render it as a
link, so a record fetched from the PDS is attacker-controlled input. The
deserialize_with below is the read-side counterpart to the write-side
vet in crate::repo: together they mean a Subscription that entered
this process from outside cannot be carrying a javascript: URL, whatever
it is later rendered into — an href, or an OPML htmlUrl we hand back
to the user as a file.
The READ side only. A record built in-process rather than
deserialised does not pass through here — OPML import parses htmlUrl
out of XML by hand, and the manage form assigns the field directly.
Those are the write boundary’s to vet, which is why both guards exist
rather than either one being sufficient.
A rejected value becomes None, so it is omitted rather than emitted
empty; a consumer renders no link instead of a broken one.
Round-trip fidelity is deliberately lost. Read a record holding a
hostile siteUrl, re-put it, and we write it back cleaned rather than
preserving what another client stored. That heals the user’s repo
instead of propagating someone else’s script URL — but it does mean a
putRecord following a read is not byte-identical to what was there,
and that is a decision, not an accident.
folder: Option<String>Optional at:// strong ref to a Folder record.
fetch_hint: Option<FetchHint>Optional polling-cadence hint; readers MAY honor or ignore it.
private: Option<bool>Reserved — no runtime behavior today.
FeatherReader currently supports public feeds only (private/secret-bearing
feeds are refused at the add boundary), so nothing sets this and every
written record omits it (None). It is kept as a documented,
forward-compatible seam for the eventual migration once atproto ships
permissioned data: at that point a private feed’s secret can live in an
owner-scoped, permission-gated collection and this record can reference it
with private: true. See the type-level docs.
created_at: StringRecord creation time (ISO-8601 datetime). Required.
Implementations§
Trait Implementations§
Source§impl Clone for Subscription
impl Clone for Subscription
Source§fn clone(&self) -> Subscription
fn clone(&self) -> Subscription
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for Subscription
impl Debug for Subscription
Source§impl<'de> Deserialize<'de> for Subscription
impl<'de> Deserialize<'de> for Subscription
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for Subscription
Source§impl PartialEq for Subscription
impl PartialEq for Subscription
Source§impl Serialize for Subscription
impl Serialize for Subscription
impl StructuralPartialEq for Subscription
Auto Trait Implementations§
impl Freeze for Subscription
impl RefUnwindSafe for Subscription
impl Send for Subscription
impl Sync for Subscription
impl Unpin for Subscription
impl UnsafeUnpin for Subscription
impl UnwindSafe for Subscription
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more