Skip to main content

syncweb_core/editorial/
state.rs

1use serde::{Deserialize, Serialize};
2
3/// Content lifecycle stage.
4///
5/// The default is [`Published`](Self::Published) so that collections that
6/// do not opt into editorial flows behave identically to the pre-editorial
7/// behaviour.
8#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
9#[non_exhaustive]
10pub enum EditorialState {
11    /// Local-only, not yet shared.
12    Draft,
13    /// Shared for review, not yet approved.
14    Proposed,
15    /// Actively being reviewed.
16    InReview,
17    /// Reviewers have requested changes.
18    ChangesRequested,
19    /// Reviewers have approved.
20    Approved,
21    /// Publicly available — pinned and announced.
22    /// This is the default state (backward-compatible).
23    #[default]
24    Published,
25    /// Deprecated but kept for reference.
26    Archived,
27    /// Published then pulled (tombstone entry remains).
28    Retracted,
29}
30
31impl EditorialState {
32    /// Whether the content is considered finalised and visible to general
33    /// consumers.
34    #[must_use]
35    pub const fn is_published(self) -> bool {
36        matches!(self, Self::Published)
37    }
38
39    /// Whether the content is available for public consumption (published or
40    /// archived).
41    #[must_use]
42    pub const fn is_public(self) -> bool {
43        matches!(self, Self::Published | Self::Archived)
44    }
45
46    /// Whether the state is part of the pre-publication review pipeline.
47    #[must_use]
48    pub const fn is_pre_publication(self) -> bool {
49        matches!(
50            self,
51            Self::Draft | Self::Proposed | Self::InReview | Self::ChangesRequested | Self::Approved
52        )
53    }
54
55    /// Whether the content has been withdrawn (retracted).
56    #[must_use]
57    pub const fn is_withdrawn(self) -> bool {
58        matches!(self, Self::Retracted)
59    }
60
61    /// Check whether a transition from `self` to `target` is a valid editorial
62    /// workflow step.
63    ///
64    /// Content that enters the editorial pipeline at `Draft` must go through
65    /// review (`Proposed → InReview → Approved`) before being published.
66    /// Retracted content must complete the full review cycle again before
67    /// re-publishing — `Draft → Published` is not permitted.
68    #[must_use]
69    pub const fn can_transition_to(self, target: Self) -> bool {
70        use EditorialState as S;
71        matches!(
72            (self, target),
73            (S::Draft | S::Approved, S::Proposed)
74                | (S::Proposed, S::InReview | S::Draft)
75                | (S::InReview, S::ChangesRequested | S::Approved)
76                | (S::ChangesRequested, S::Proposed | S::Draft)
77                | (S::Approved | S::Archived, S::Published)
78                | (S::Published, S::Archived | S::Retracted)
79                | (S::Retracted, S::Draft)
80        )
81    }
82}