ready_active_safe/model.rs
1//! Core types for the lifecycle engine.
2//!
3//! These types are always available and `no_std`-compatible (using `alloc`
4//! for heap allocation). They define the fundamental contracts that every
5//! lifecycle implementation must satisfy.
6
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::error::LifecycleError;
11
12/// A requested change from one mode to another.
13///
14/// Produced inside a [`Decision`] when an event triggers a mode transition.
15/// The runtime inspects this value to determine whether the system should
16/// move to a new mode.
17///
18/// # Examples
19///
20/// ```
21/// use ready_active_safe::Decision;
22///
23/// let d: Decision<&str, (), ()> = Decision::transition("active");
24/// let change = d.mode_change();
25/// assert_eq!(change.map(|mc| mc.target), Some("active"));
26/// ```
27#[derive(Debug, Clone, PartialEq, Eq)]
28#[non_exhaustive]
29pub struct ModeChange<M> {
30 /// The mode to transition to.
31 pub target: M,
32}
33
34/// The outcome of processing an event through [`Machine::on_event`].
35///
36/// A `Decision` captures two orthogonal concerns:
37/// - Whether the system should change mode ([`ModeChange`]).
38/// - What commands the runtime should execute.
39///
40/// Decisions are **pure data**. They describe intent, not side effects.
41/// The runtime is responsible for interpreting and executing them.
42///
43/// # Examples
44///
45/// ```
46/// use ready_active_safe::Decision;
47///
48/// #[derive(Debug, Clone, PartialEq, Eq)]
49/// enum Mode { Ready, Active }
50///
51/// // Stay in the current mode with no commands
52/// let stay: Decision<Mode, &str, ()> = Decision::stay();
53/// assert!(stay.target_mode().is_none());
54///
55/// // Transition to a new mode
56/// let go: Decision<Mode, &str, ()> = Decision::transition(Mode::Active);
57/// assert_eq!(go.target_mode(), Some(&Mode::Active));
58/// ```
59#[derive(Debug, Clone, PartialEq, Eq)]
60#[non_exhaustive]
61pub struct Decision<M, C, E = core::convert::Infallible> {
62 /// An optional mode change. `None` means stay in the current mode.
63 change: Option<ModeChange<M>>,
64 /// Commands for the runtime to execute.
65 commands: Vec<C>,
66 /// Marker for the error type. Used by the trait contract but not
67 /// stored in the decision itself.
68 _error: core::marker::PhantomData<E>,
69}
70
71impl<M, C, E> Decision<M, C, E> {
72 /// Creates a decision that stays in the current mode with no commands.
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// use ready_active_safe::Decision;
78 ///
79 /// let d: Decision<(), (), ()> = Decision::stay();
80 /// assert!(d.mode_change().is_none());
81 /// assert!(d.commands().is_empty());
82 /// ```
83 #[must_use]
84 pub const fn stay() -> Self {
85 Self {
86 change: None,
87 commands: Vec::new(),
88 _error: core::marker::PhantomData,
89 }
90 }
91
92 /// Creates a decision that transitions to the given mode.
93 ///
94 /// # Examples
95 ///
96 /// ```
97 /// use ready_active_safe::Decision;
98 ///
99 /// let d: Decision<&str, (), ()> = Decision::transition("active");
100 /// assert_eq!(d.mode_change().map(|mc| mc.target), Some("active"));
101 /// ```
102 #[must_use]
103 pub const fn transition(target: M) -> Self {
104 Self {
105 change: Some(ModeChange { target }),
106 commands: Vec::new(),
107 _error: core::marker::PhantomData,
108 }
109 }
110
111 /// Returns the mode change, if any.
112 ///
113 /// # Examples
114 ///
115 /// ```
116 /// use ready_active_safe::Decision;
117 ///
118 /// let d: Decision<&str, (), ()> = Decision::stay();
119 /// assert!(d.mode_change().is_none());
120 /// ```
121 #[must_use]
122 pub const fn mode_change(&self) -> Option<&ModeChange<M>> {
123 self.change.as_ref()
124 }
125
126 /// Returns the target mode, if this decision requests a transition.
127 ///
128 /// This is a convenience wrapper around [`Decision::mode_change`]
129 /// for the common case where you only care about the target.
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use ready_active_safe::Decision;
135 ///
136 /// #[derive(Debug, Clone, PartialEq, Eq)]
137 /// enum Mode { Ready, Active, Safe }
138 ///
139 /// let d: Decision<Mode, (), ()> = Decision::transition(Mode::Active);
140 /// assert_eq!(d.target_mode(), Some(&Mode::Active));
141 /// ```
142 #[must_use]
143 pub const fn target_mode(&self) -> Option<&M> {
144 match &self.change {
145 Some(change) => Some(&change.target),
146 None => None,
147 }
148 }
149
150 /// Returns the commands to execute.
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// use ready_active_safe::Decision;
156 ///
157 /// let d: Decision<(), (), ()> = Decision::stay();
158 /// assert!(d.commands().is_empty());
159 /// ```
160 #[must_use]
161 pub fn commands(&self) -> &[C] {
162 &self.commands
163 }
164
165 /// Returns `true` if this decision requests a mode transition.
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// use ready_active_safe::Decision;
171 ///
172 /// let d: Decision<&str, (), ()> = Decision::transition("active");
173 /// assert!(d.is_transition());
174 ///
175 /// let s: Decision<&str, (), ()> = Decision::stay();
176 /// assert!(!s.is_transition());
177 /// ```
178 #[must_use]
179 pub const fn is_transition(&self) -> bool {
180 self.change.is_some()
181 }
182
183 /// Returns `true` if this decision stays in the current mode.
184 ///
185 /// # Examples
186 ///
187 /// ```
188 /// use ready_active_safe::Decision;
189 ///
190 /// let d: Decision<&str, (), ()> = Decision::stay();
191 /// assert!(d.is_stay());
192 ///
193 /// let t: Decision<&str, (), ()> = Decision::transition("active");
194 /// assert!(!t.is_stay());
195 /// ```
196 #[must_use]
197 pub const fn is_stay(&self) -> bool {
198 self.change.is_none()
199 }
200
201 /// Consumes the decision and returns its parts.
202 ///
203 /// This is useful in runtimes that own the current mode and want to move
204 /// the next-mode target and commands out of the decision without cloning.
205 ///
206 /// # Examples
207 ///
208 /// ```
209 /// use ready_active_safe::Decision;
210 ///
211 /// let d: Decision<&str, &str, ()> =
212 /// Decision::transition("active").with_command("initialize");
213 ///
214 /// let (change, commands) = d.into_parts();
215 /// assert_eq!(change.map(|mc| mc.target), Some("active"));
216 /// assert_eq!(commands, vec!["initialize"]);
217 /// ```
218 #[must_use]
219 pub fn into_parts(self) -> (Option<ModeChange<M>>, Vec<C>) {
220 (self.change, self.commands)
221 }
222
223 /// Consumes the decision and returns the requested mode change, if any.
224 ///
225 /// Prefer [`Decision::into_parts`] if you also need the commands.
226 #[must_use]
227 pub fn into_mode_change(self) -> Option<ModeChange<M>> {
228 self.change
229 }
230
231 /// Consumes the decision and returns its commands.
232 ///
233 /// Prefer [`Decision::into_parts`] if you also need the mode change.
234 #[must_use]
235 pub fn into_commands(self) -> Vec<C> {
236 self.commands
237 }
238
239 /// Adds a command to this decision.
240 ///
241 /// # Examples
242 ///
243 /// ```
244 /// use ready_active_safe::Decision;
245 ///
246 /// let d: Decision<(), &str, ()> = Decision::stay()
247 /// .with_command("initialize");
248 /// assert_eq!(d.commands(), &["initialize"]);
249 /// ```
250 #[must_use]
251 pub fn with_command(mut self, command: C) -> Self {
252 self.commands.push(command);
253 self
254 }
255
256 /// Adds a command to this decision.
257 ///
258 /// This is an alias for [`Decision::with_command`]. It can read better in
259 /// `match` arms:
260 ///
261 /// ```
262 /// use ready_active_safe::Decision;
263 ///
264 /// let d: Decision<(), &str, ()> = Decision::stay().emit("log");
265 /// assert_eq!(d.commands(), &["log"]);
266 /// ```
267 #[must_use]
268 pub fn emit(self, command: C) -> Self {
269 self.with_command(command)
270 }
271
272 /// Adds multiple commands to this decision.
273 ///
274 /// Commands are appended in iteration order.
275 ///
276 /// # Examples
277 ///
278 /// ```
279 /// use ready_active_safe::Decision;
280 ///
281 /// #[derive(Debug, Clone, PartialEq, Eq)]
282 /// enum Command { Initialize, Begin }
283 ///
284 /// let d: Decision<(), Command, ()> =
285 /// Decision::stay().with_commands([Command::Initialize, Command::Begin]);
286 /// assert_eq!(d.commands(), &[Command::Initialize, Command::Begin]);
287 /// ```
288 #[must_use]
289 pub fn with_commands<I>(mut self, commands: I) -> Self
290 where
291 I: IntoIterator<Item = C>,
292 {
293 self.commands.extend(commands);
294 self
295 }
296
297 /// Adds multiple commands to this decision.
298 ///
299 /// This is an alias for [`Decision::with_commands`].
300 ///
301 /// ```
302 /// use ready_active_safe::Decision;
303 ///
304 /// let d: Decision<(), &str, ()> = Decision::stay().emit_all(["a", "b"]);
305 /// assert_eq!(d.commands(), &["a", "b"]);
306 /// ```
307 #[must_use]
308 pub fn emit_all<I>(self, commands: I) -> Self
309 where
310 I: IntoIterator<Item = C>,
311 {
312 self.with_commands(commands)
313 }
314
315 /// Applies this decision to an owned current mode.
316 ///
317 /// This is a method form of [`apply_decision`]. It tends to read well in
318 /// runtime loops.
319 ///
320 /// ```
321 /// use ready_active_safe::prelude::*;
322 ///
323 /// let mode = "ready";
324 /// let decision: Decision<&str, &str, ()> = transition("active").emit("initialize");
325 ///
326 /// let (mode, commands) = decision.apply(mode);
327 /// assert_eq!(mode, "active");
328 /// assert_eq!(commands, vec!["initialize"]);
329 /// ```
330 #[must_use]
331 pub fn apply(self, current_mode: M) -> (M, Vec<C>) {
332 apply_decision(current_mode, self)
333 }
334
335 /// Applies this decision after checking a policy.
336 ///
337 /// This is a method form of [`apply_decision_checked`]. If the policy
338 /// denies the transition, the mode is unchanged and commands are dropped.
339 ///
340 /// # Examples
341 ///
342 /// ```
343 /// use ready_active_safe::prelude::*;
344 ///
345 /// #[derive(Debug, Clone, PartialEq, Eq)]
346 /// enum Mode { Ready, Active, Safe }
347 ///
348 /// struct ForwardOnly;
349 /// impl Policy<Mode> for ForwardOnly {
350 /// fn is_allowed(&self, from: &Mode, to: &Mode) -> bool {
351 /// matches!((from, to), (Mode::Ready, Mode::Active) | (Mode::Active, Mode::Safe))
352 /// }
353 /// }
354 ///
355 /// let policy = ForwardOnly;
356 ///
357 /// // Allowed transition
358 /// let d: Decision<Mode, &str> = transition(Mode::Active).emit("go");
359 /// let (mode, cmds) = d.apply_checked(Mode::Ready, &policy).unwrap();
360 /// assert_eq!(mode, Mode::Active);
361 /// assert_eq!(cmds, vec!["go"]);
362 ///
363 /// // Denied transition: mode unchanged, commands dropped
364 /// let d: Decision<Mode, &str> = transition(Mode::Ready).emit("back");
365 /// let err = d.apply_checked(Mode::Active, &policy).unwrap_err();
366 /// assert!(matches!(err, LifecycleError::TransitionDenied { .. }));
367 /// ```
368 ///
369 /// # Errors
370 ///
371 /// Returns [`LifecycleError::TransitionDenied`] if the policy rejects
372 /// the requested mode change.
373 pub fn apply_checked<P>(
374 self,
375 current_mode: M,
376 policy: &P,
377 ) -> Result<(M, Vec<C>), LifecycleError<M>>
378 where
379 P: Policy<M>,
380 {
381 apply_decision_checked(current_mode, self, policy)
382 }
383}
384
385impl<M: fmt::Display, C, E> fmt::Display for Decision<M, C, E> {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 match &self.change {
388 Some(mc) => write!(f, "transition to {}", mc.target),
389 None => write!(f, "stay"),
390 }
391 }
392}
393
394/// Creates a decision that stays in the current mode.
395///
396/// This is a free function wrapper around [`Decision::stay`]. It exists to make
397/// `match` arms read like a sentence.
398///
399/// ```
400/// use ready_active_safe::prelude::*;
401///
402/// #[derive(Debug, Clone, PartialEq, Eq)]
403/// enum Mode {
404/// Ready,
405/// Active,
406/// }
407///
408/// #[derive(Debug)]
409/// enum Event {
410/// Start,
411/// }
412///
413/// #[derive(Debug, Clone, PartialEq, Eq)]
414/// enum Command {
415/// Log,
416/// }
417///
418/// struct System;
419///
420/// impl Machine for System {
421/// type Mode = Mode;
422/// type Event = Event;
423/// type Command = Command;
424///
425/// fn initial_mode(&self) -> Mode { Mode::Ready }
426///
427/// fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
428/// use Event::*;
429/// use Mode::*;
430///
431/// match (mode, event) {
432/// (Ready, Start) => transition(Active).emit(Command::Log),
433/// _ => stay(),
434/// }
435/// }
436/// }
437/// ```
438#[must_use]
439pub const fn stay<M, C, E>() -> Decision<M, C, E> {
440 Decision::stay()
441}
442
443/// Creates a decision that transitions to the given mode.
444///
445/// This is a free function wrapper around [`Decision::transition`]. It exists
446/// to make `match` arms read like a sentence.
447///
448/// ```
449/// use ready_active_safe::prelude::*;
450///
451/// let d: Decision<&str, (), ()> = transition("active");
452/// assert_eq!(d.target_mode(), Some(&"active"));
453/// ```
454#[must_use]
455pub const fn transition<M, C, E>(target: M) -> Decision<M, C, E> {
456 Decision::transition(target)
457}
458
459/// Creates a decision that deliberately ignores an unmatched event.
460///
461/// Semantically identical to [`stay`], but communicates intent. Use
462/// `ignore()` in catch-all arms to signal that silence is by design,
463/// not an oversight.
464///
465/// ```
466/// use ready_active_safe::prelude::*;
467///
468/// #[derive(Debug, Clone, PartialEq, Eq)]
469/// enum Mode { Ready, Active }
470///
471/// #[derive(Debug)]
472/// enum Event { Start, Ping }
473///
474/// struct System;
475///
476/// impl Machine for System {
477/// type Mode = Mode;
478/// type Event = Event;
479/// type Command = ();
480///
481/// fn initial_mode(&self) -> Mode { Mode::Ready }
482///
483/// fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, ()> {
484/// match (mode, event) {
485/// (Mode::Ready, Event::Start) => transition(Mode::Active),
486/// _ => ignore(), // deliberate: these events need no response
487/// }
488/// }
489/// }
490/// ```
491#[must_use]
492pub const fn ignore<M, C, E>() -> Decision<M, C, E> {
493 Decision::stay()
494}
495
496/// Applies a decision to an owned current mode.
497///
498/// This helper is for runtimes that own the mode and want to move the target
499/// mode and commands out of a [`Decision`] without cloning.
500///
501/// ```
502/// use ready_active_safe::prelude::*;
503///
504/// let mode = "ready";
505/// let decision: Decision<&str, &str, ()> = transition("active").emit("initialize");
506///
507/// let (mode, commands) = apply_decision(mode, decision);
508/// assert_eq!(mode, "active");
509/// assert_eq!(commands, vec!["initialize"]);
510/// ```
511#[must_use]
512pub fn apply_decision<M, C, E>(current_mode: M, decision: Decision<M, C, E>) -> (M, Vec<C>) {
513 let (change, commands) = decision.into_parts();
514
515 let next_mode = match change {
516 Some(change) => change.target,
517 None => current_mode,
518 };
519
520 (next_mode, commands)
521}
522
523/// Applies a decision to an owned current mode after checking a policy.
524///
525/// If the decision requests a mode change and the policy denies it, this
526/// returns a [`LifecycleError::TransitionDenied`] and the commands are dropped.
527///
528/// ```
529/// use ready_active_safe::prelude::*;
530///
531/// #[derive(Debug, Clone, PartialEq, Eq)]
532/// enum Mode {
533/// Ready,
534/// Active,
535/// }
536///
537/// struct ForwardOnly;
538///
539/// impl Policy<Mode> for ForwardOnly {
540/// fn is_allowed(&self, from: &Mode, to: &Mode) -> bool {
541/// matches!((from, to), (Mode::Ready, Mode::Active))
542/// }
543/// }
544///
545/// let policy = ForwardOnly;
546/// let decision: Decision<Mode, (), ()> = transition(Mode::Active);
547///
548/// let (mode, _commands) = apply_decision_checked(Mode::Ready, decision, &policy)?;
549/// assert_eq!(mode, Mode::Active);
550/// # Ok::<(), LifecycleError<Mode>>(())
551/// ```
552///
553/// # Errors
554///
555/// Returns [`LifecycleError::TransitionDenied`] if the policy rejects
556/// the requested mode change.
557pub fn apply_decision_checked<M, C, E, P>(
558 current_mode: M,
559 decision: Decision<M, C, E>,
560 policy: &P,
561) -> Result<(M, Vec<C>), LifecycleError<M>>
562where
563 P: Policy<M>,
564{
565 let (change, commands) = decision.into_parts();
566
567 match change {
568 Some(change) => {
569 let target = change.target;
570
571 match policy.check(¤t_mode, &target) {
572 Ok(()) => Ok((target, commands)),
573 Err(reason) => Err(LifecycleError::TransitionDenied {
574 from: current_mode,
575 to: target,
576 reason,
577 }),
578 }
579 }
580 None => Ok((current_mode, commands)),
581 }
582}
583
584/// A pure function from (mode, event) to [`Decision`].
585///
586/// This is the central trait of the lifecycle engine. Implementations
587/// define how the system responds to events in each mode. The trait
588/// is deliberately minimal:
589///
590/// - **No `self` mutation**: `on_event` takes `&self`, ensuring the
591/// machine logic is pure and deterministic.
592/// - **No time parameter**: time composes via the runtime and the
593/// `time` feature. The base trait stays usable without it.
594/// - **No I/O**: the machine never performs side effects. It returns
595/// a [`Decision`] that the runtime interprets.
596///
597/// # Associated Types
598///
599/// - `Mode`: the set of lifecycle phases (e.g., Ready, Active, Safe).
600/// - `Event`: external stimuli the system can receive.
601/// - `Command`: instructions for the runtime to execute.
602///
603/// # Type Parameter
604///
605/// - `E`: domain errors that can occur during event processing. Defaults to `Infallible`.
606///
607/// # Examples
608///
609/// ```
610/// use ready_active_safe::prelude::*;
611///
612/// struct Echo;
613///
614/// impl Machine for Echo {
615/// type Mode = ();
616/// type Event = String;
617/// type Command = String;
618///
619/// fn initial_mode(&self) {}
620///
621/// fn on_event(&self, _mode: &(), event: &String) -> Decision<(), String> {
622/// Decision::stay().with_command(event.clone())
623/// }
624/// }
625///
626/// let echo = Echo;
627/// let event = "hello".to_string();
628/// let d = echo.on_event(&(), &event);
629/// assert_eq!(d.commands(), &[event]);
630/// ```
631pub trait Machine<E = core::convert::Infallible> {
632 /// The set of lifecycle modes.
633 type Mode;
634 /// External events that drive the system.
635 type Event;
636 /// Commands emitted for the runtime to execute.
637 type Command;
638
639 /// Returns the starting mode for this lifecycle.
640 ///
641 /// Every lifecycle has a well-defined starting point. Declaring it
642 /// here keeps the definition self-contained instead of requiring
643 /// the caller to know the right value.
644 ///
645 /// # Examples
646 ///
647 /// ```
648 /// use ready_active_safe::prelude::*;
649 ///
650 /// #[derive(Debug, Clone, PartialEq, Eq)]
651 /// enum Mode { Ready, Active }
652 ///
653 /// struct System;
654 ///
655 /// impl Machine for System {
656 /// type Mode = Mode;
657 /// type Event = ();
658 /// type Command = ();
659 ///
660 /// fn initial_mode(&self) -> Mode { Mode::Ready }
661 ///
662 /// fn on_event(&self, _: &Mode, _: &()) -> Decision<Mode, ()> {
663 /// stay()
664 /// }
665 /// }
666 ///
667 /// let system = System;
668 /// assert_eq!(system.initial_mode(), Mode::Ready);
669 /// ```
670 fn initial_mode(&self) -> Self::Mode;
671
672 /// Evaluates an event in the context of the current mode.
673 ///
674 /// Returns a [`Decision`] describing whether the system should
675 /// change mode and what commands to emit.
676 fn on_event(
677 &self,
678 mode: &Self::Mode,
679 event: &Self::Event,
680 ) -> Decision<Self::Mode, Self::Command, E>;
681
682 /// Alias for [`Machine::on_event`].
683 ///
684 /// This reads well in runtimes and tests.
685 fn decide(
686 &self,
687 mode: &Self::Mode,
688 event: &Self::Event,
689 ) -> Decision<Self::Mode, Self::Command, E> {
690 self.on_event(mode, event)
691 }
692
693 /// Returns the next mode and commands for an owned current mode.
694 ///
695 /// This is a convenience for runtimes that own the mode value.
696 /// It is equivalent to calling `decide` and then [`Decision::apply`].
697 #[must_use]
698 fn step(&self, mode: Self::Mode, event: &Self::Event) -> (Self::Mode, Vec<Self::Command>) {
699 self.decide(&mode, event).apply(mode)
700 }
701
702 /// Returns the next mode and commands after checking a policy.
703 ///
704 /// This is a convenience for runtimes that want policy checks to be
705 /// explicit and centralized. It is equivalent to calling `decide` and then
706 /// [`Decision::apply_checked`].
707 ///
708 /// # Errors
709 ///
710 /// Returns [`LifecycleError::TransitionDenied`] if the policy rejects
711 /// the requested mode change.
712 #[allow(clippy::type_complexity)]
713 fn step_checked<P>(
714 &self,
715 mode: Self::Mode,
716 event: &Self::Event,
717 policy: &P,
718 ) -> Result<(Self::Mode, Vec<Self::Command>), LifecycleError<Self::Mode>>
719 where
720 P: Policy<Self::Mode>,
721 {
722 self.decide(&mode, event).apply_checked(mode, policy)
723 }
724}
725
726/// Determines whether a mode transition is allowed.
727///
728/// Policies let you enforce transition rules externally, for example by
729/// ensuring that certain modes can only be reached from specific
730/// predecessors. The runtime checks the policy before applying a
731/// [`ModeChange`] from a [`Decision`].
732///
733/// **When a transition is denied**, the current mode is unchanged and
734/// any commands in the decision are dropped. Plan accordingly in your
735/// runtime if commands must always execute.
736///
737/// # Examples
738///
739/// ```
740/// use ready_active_safe::Policy;
741///
742/// #[derive(Debug, PartialEq)]
743/// enum Mode { Ready, Active, Safe }
744///
745/// struct AllowAll;
746///
747/// impl Policy<Mode> for AllowAll {
748/// fn is_allowed(&self, _from: &Mode, _to: &Mode) -> bool {
749/// true
750/// }
751/// }
752///
753/// let policy = AllowAll;
754/// assert!(policy.is_allowed(&Mode::Ready, &Mode::Active));
755/// ```
756pub trait Policy<M> {
757 /// Returns `true` if transitioning from `from` to `to` is permitted.
758 fn is_allowed(&self, from: &M, to: &M) -> bool;
759
760 /// Returns `Ok(())` if the transition is allowed, or a reason string if denied.
761 ///
762 /// This has a default implementation based on [`Policy::is_allowed`].
763 /// Override it when you want a more specific error reason.
764 ///
765 /// # Errors
766 ///
767 /// Returns a static reason string when the transition is denied.
768 fn check(&self, from: &M, to: &M) -> Result<(), &'static str> {
769 if self.is_allowed(from, to) {
770 Ok(())
771 } else {
772 Err("transition denied by policy")
773 }
774 }
775}
776
777/// A policy that permits every transition.
778///
779/// Use `AllowAll` in tests and prototypes where you do not need
780/// transition restrictions. This saves writing a trivial policy impl
781/// for every test file.
782///
783/// # Examples
784///
785/// ```
786/// use ready_active_safe::prelude::*;
787///
788/// let policy = AllowAll;
789/// assert!(policy.is_allowed(&"ready", &"active"));
790/// assert!(policy.is_allowed(&"active", &"ready"));
791/// ```
792#[derive(Debug, Clone, Copy, PartialEq, Eq)]
793pub struct AllowAll;
794
795impl<M> Policy<M> for AllowAll {
796 fn is_allowed(&self, _from: &M, _to: &M) -> bool {
797 true
798 }
799}
800
801/// A policy that denies every transition.
802///
803/// Use `DenyAll` to test that your runtime correctly handles
804/// policy denials. Every transition attempt will produce a
805/// [`LifecycleError::TransitionDenied`](crate::LifecycleError::TransitionDenied).
806///
807/// # Examples
808///
809/// ```
810/// use ready_active_safe::prelude::*;
811///
812/// let policy = DenyAll;
813/// assert!(!policy.is_allowed(&"ready", &"active"));
814///
815/// let err = policy.check(&"ready", &"active").unwrap_err();
816/// assert_eq!(err, "transition denied by policy");
817/// ```
818#[derive(Debug, Clone, Copy, PartialEq, Eq)]
819pub struct DenyAll;
820
821impl<M> Policy<M> for DenyAll {
822 fn is_allowed(&self, _from: &M, _to: &M) -> bool {
823 false
824 }
825}