Skip to main content

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(&current_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}