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}