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 OR archived).
56    #[must_use]
57    pub const fn is_withdrawn(self) -> bool {
58        matches!(self, Self::Retracted | Self::Archived)
59    }
60
61    /// Check whether a transition from `self` to `target` is a valid editorial
62    /// workflow step.
63    #[must_use]
64    pub const fn can_transition_to(self, target: Self) -> bool {
65        use EditorialState as S;
66        matches!(
67            (self, target),
68            (S::Draft | S::Approved, S::Proposed | S::Published)
69                | (S::Proposed, S::InReview | S::Draft)
70                | (S::InReview, S::ChangesRequested | S::Approved)
71                | (S::ChangesRequested, S::Proposed | S::Draft)
72                | (S::Published, S::Archived | S::Retracted)
73                | (S::Archived, S::Published)
74                | (S::Retracted, S::Draft)
75        )
76    }
77}