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}