Skip to main content

syncweb_core/editorial/
channel.rs

1use serde::{Deserialize, Serialize};
2
3/// A named content stream for editorial distribution.
4///
5/// Channels provide **authoritative views** on top of the default,
6/// unfiltered gossip catalog.  Publishers announce content into channels
7/// once it reaches a given editorial state (e.g. "curated"), and consumers
8/// subscribe only to the channels they trust.
9///
10/// Under the hood a channel is a separate gossip topic derived from its
11/// name, so channel membership is inherently opt-in and decentralised.
12#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct Channel {
15    /// Human-readable channel name (e.g. `"curated"`, `"latest"`,
16    /// `"publisher-acme"`).
17    pub name: String,
18    /// Optional human-readable description.
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub description: Option<String>,
21}
22
23impl Channel {
24    /// Create a new channel with an optional description.
25    #[must_use]
26    pub fn new(name: impl Into<String>, description: Option<impl Into<String>>) -> Self {
27        Self {
28            name: name.into(),
29            description: description.map(Into::into),
30        }
31    }
32
33    /// The gossip topic seed for this channel.
34    ///
35    /// Topic IDs are derived from a BLAKE3 hash of this seed, so every
36    /// channel is a fully independent gossip swarm.
37    #[must_use]
38    pub fn topic_seed(&self) -> String {
39        format!("syncweb/editorial/{}/v1", self.name)
40    }
41
42    /// Whether content should appear on this channel.
43    ///
44    /// By default every channel accepts all content; publishers apply
45    /// editorial policy *before* calling announce.
46    #[must_use]
47    pub const fn accepts_all(&self) -> bool {
48        true
49    }
50}