Skip to main content

ephemeris_forge/
backend.rs

1//! The contract between the shared planner and a forge adapter.
2
3use std::error::Error as StdError;
4use std::fmt;
5
6use facet::Facet;
7
8use crate::{RepoRef, Report};
9
10/// An adapter that reads one forge and applies planned writes to it.
11///
12/// The adapter owns the shape of the desired state, the live state and the
13/// writes. The shared code only orders and reports them.
14pub trait Backend {
15    /// The desired repository state, decoded from the state document.
16    type Config: for<'a> Facet<'a> + Sync;
17
18    /// The adapter's transport or API error.
19    type Error: StdError + Send + Sync + 'static;
20
21    /// The state read from the forge.
22    type Live: Send;
23
24    /// One labelled write. The label names the resource the write changes.
25    type Operation: fmt::Display + Send + Sync;
26
27    /// Perform one planned operation.
28    fn apply(
29        &self,
30        repository: &RepoRef,
31        operation: &Self::Operation,
32    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
33
34    /// Compare the desired and the live state without any I/O.
35    fn plan(config: &Self::Config, live: &Self::Live) -> Planned<Self::Operation>;
36
37    /// Read the state that planning compares with `config`.
38    fn read(
39        &self,
40        repository: &RepoRef,
41        config: &Self::Config,
42    ) -> impl Future<Output = Result<Self::Live, Self::Error>> + Send;
43
44    /// Check a configuration before any network access.
45    ///
46    /// # Errors
47    ///
48    /// Returns an error when the forge cannot represent the configuration.
49    fn validate(config: &Self::Config) -> Result<(), Self::Error>;
50}
51
52/// The result of [`Backend::plan`].
53#[derive(Clone, Debug)]
54pub struct Planned<Operation> {
55    /// The writes in the order they must run.
56    pub operations: Vec<Operation>,
57    /// What the writes change, and what they leave alone.
58    pub report: Report,
59}
60
61impl<Operation> Default for Planned<Operation> {
62    fn default() -> Self {
63        Self {
64            operations: Vec::new(),
65            report: Report::default(),
66        }
67    }
68}
69
70impl<Operation> Planned<Operation> {
71    /// Plan one resource and append its operations.
72    ///
73    /// `planner` receives the report and returns the operations of the
74    /// resource.
75    pub fn extend<Resource, Items, Planner>(&mut self, planner: Planner)
76    where
77        Resource: Into<Operation>,
78        Items: IntoIterator<Item = Resource>,
79        Planner: FnOnce(&mut Report) -> Items,
80    {
81        let operations = planner(&mut self.report);
82        self.operations
83            .extend(operations.into_iter().map(Into::into));
84    }
85}