peace_rt/cmds/
diff_cmd.rs

1use std::{fmt::Debug, marker::PhantomData};
2
3use futures::{StreamExt, TryStreamExt};
4use peace_cmd_ctx::{CmdCtxMpsf, CmdCtxMpsfFields, CmdCtxSpsf, CmdCtxTypes};
5use peace_cmd_model::CmdOutcome;
6use peace_cmd_rt::{CmdBlockWrapper, CmdExecution, CmdExecutionBuilder};
7use peace_flow_rt::Flow;
8use peace_item_model::ItemId;
9use peace_params::ParamsSpecs;
10use peace_profile_model::Profile;
11use peace_resource_rt::{
12    internal::StateDiffsMut,
13    resources::ts::SetUp,
14    states::{
15        ts::{CurrentStored, GoalStored},
16        StateDiffs,
17    },
18    type_reg::untagged::{BoxDtDisplay, TypeMap},
19    Resources,
20};
21use peace_rt_model::Error;
22
23use crate::cmd_blocks::{
24    DiffCmdBlock, DiffCmdBlockStatesTsExt, StatesCurrentReadCmdBlock, StatesDiscoverCmdBlock,
25    StatesGoalReadCmdBlock,
26};
27
28pub use self::{diff_info_spec::DiffInfoSpec, diff_state_spec::DiffStateSpec};
29
30mod diff_info_spec;
31mod diff_state_spec;
32
33pub struct DiffCmd<CmdCtxTypesT, Scope>(PhantomData<(CmdCtxTypesT, Scope)>);
34
35impl<CmdCtxTypesT, Scope> Debug for DiffCmd<CmdCtxTypesT, Scope> {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_tuple("DiffCmd").field(&self.0).finish()
38    }
39}
40
41impl<'ctx, CmdCtxTypesT> DiffCmd<CmdCtxTypesT, CmdCtxSpsf<'ctx, CmdCtxTypesT>>
42where
43    CmdCtxTypesT: CmdCtxTypes,
44{
45    /// Returns the [`state_diff`]`s between the stored current and goal
46    /// states.
47    ///
48    /// Both current and goal states must have been discovered prior to
49    /// running this. See [`StatesDiscoverCmd::current_and_goal`].
50    ///
51    /// This is equivalent to calling:
52    ///
53    /// ```rust,ignore
54    /// DiffCmd::diff(cmd_ctx, DiffStateSpec::CurrentStored, DiffStateSpec::GoalStored).await?;
55    /// ```
56    ///
57    /// [`state_diff`]: peace_cfg::Item::state_diff
58    /// [`StatesDiscoverCmd::current_and_goal`]: crate::cmds::StatesDiscoverCmd::current_and_goal
59    pub async fn diff_stored(
60        cmd_ctx: &mut CmdCtxSpsf<'ctx, CmdCtxTypesT>,
61    ) -> Result<
62        CmdOutcome<StateDiffs, <CmdCtxTypesT as CmdCtxTypes>::AppError>,
63        <CmdCtxTypesT as CmdCtxTypes>::AppError,
64    > {
65        Self::diff::<CurrentStored, GoalStored>(cmd_ctx).await
66    }
67
68    /// Returns the [`state_diff`]`s between two states.
69    ///
70    /// For `CurrentStored` and `GoalStored`, states must have been discovered
71    /// prior to running this. See [`StatesDiscoverCmd::current_and_goal`].
72    ///
73    /// For `Current` and `Goal` states, though they are discovered during the
74    /// `DiffCmd` execution, they are not serialized.
75    ///
76    /// [`state_diff`]: peace_cfg::Item::state_diff
77    /// [`StatesDiscoverCmd::current_and_goal`]: crate::cmds::StatesDiscoverCmd::current_and_goal
78    pub async fn diff<StatesTs0, StatesTs1>(
79        cmd_ctx: &mut CmdCtxSpsf<'ctx, CmdCtxTypesT>,
80    ) -> Result<
81        CmdOutcome<StateDiffs, <CmdCtxTypesT as CmdCtxTypes>::AppError>,
82        <CmdCtxTypesT as CmdCtxTypes>::AppError,
83    >
84    where
85        StatesTs0: Debug + DiffCmdBlockStatesTsExt + Send + Sync + Unpin + 'static,
86        StatesTs1: Debug + DiffCmdBlockStatesTsExt + Send + Sync + Unpin + 'static,
87    {
88        let mut cmd_execution_builder = CmdExecution::<StateDiffs, _>::builder();
89        cmd_execution_builder = Self::states_fetch_cmd_block_append(
90            cmd_execution_builder,
91            StatesTs0::diff_state_spec(),
92        );
93        cmd_execution_builder = Self::states_fetch_cmd_block_append(
94            cmd_execution_builder,
95            StatesTs1::diff_state_spec(),
96        );
97
98        cmd_execution_builder = cmd_execution_builder.with_cmd_block(CmdBlockWrapper::new(
99            DiffCmdBlock::<_, StatesTs0, StatesTs1>::new(),
100            |_state_diffs_ts0_and_ts1| StateDiffs::new(),
101        ));
102
103        #[cfg(feature = "output_progress")]
104        let cmd_execution_builder = cmd_execution_builder.with_progress_render_enabled(false);
105
106        cmd_execution_builder.build().exec(cmd_ctx).await
107    }
108
109    fn states_fetch_cmd_block_append(
110        cmd_execution_builder: CmdExecutionBuilder<'ctx, StateDiffs, CmdCtxTypesT>,
111        diff_state_spec: DiffStateSpec,
112    ) -> CmdExecutionBuilder<'ctx, StateDiffs, CmdCtxTypesT> {
113        match diff_state_spec {
114            DiffStateSpec::Current => cmd_execution_builder.with_cmd_block(CmdBlockWrapper::new(
115                StatesDiscoverCmdBlock::current(),
116                |_states_current_mut| StateDiffs::new(),
117            )),
118            DiffStateSpec::CurrentStored => cmd_execution_builder.with_cmd_block(
119                CmdBlockWrapper::new(StatesCurrentReadCmdBlock::new(), |_| StateDiffs::new()),
120            ),
121            DiffStateSpec::Goal => cmd_execution_builder
122                .with_cmd_block(CmdBlockWrapper::new(StatesDiscoverCmdBlock::goal(), |_| {
123                    StateDiffs::new()
124                })),
125            DiffStateSpec::GoalStored => {
126                cmd_execution_builder
127                    .with_cmd_block(CmdBlockWrapper::new(StatesGoalReadCmdBlock::new(), |_| {
128                        StateDiffs::new()
129                    }))
130            }
131        }
132    }
133}
134
135impl<'ctx, CmdCtxTypesT> DiffCmd<CmdCtxTypesT, CmdCtxMpsf<'ctx, CmdCtxTypesT>>
136where
137    CmdCtxTypesT: CmdCtxTypes,
138{
139    /// Returns the [`state_diff`]`s between the stored current states of two
140    /// profiles.
141    ///
142    /// Both profiles' current states must have been discovered prior to
143    /// running this. See [`StatesDiscoverCmd::current`].
144    ///
145    /// [`state_diff`]: peace_cfg::Item::state_diff
146    /// [`StatesDiscoverCmd::current`]: crate::cmds::StatesDiscoverCmd::current
147    pub async fn diff_current_stored(
148        cmd_ctx: &mut CmdCtxMpsf<'ctx, CmdCtxTypesT>,
149        profile_a: &Profile,
150        profile_b: &Profile,
151    ) -> Result<StateDiffs, <CmdCtxTypesT as CmdCtxTypes>::AppError> {
152        let CmdCtxMpsfFields {
153            flow,
154            profiles,
155            profile_to_params_specs,
156            profile_to_states_current_stored,
157            resources,
158            ..
159        } = cmd_ctx.fields();
160
161        let params_specs = profile_to_params_specs
162            .get(profile_a)
163            .or_else(|| profile_to_params_specs.get(profile_b));
164        let params_specs = if let Some(params_specs) = params_specs {
165            params_specs
166        } else {
167            Err(Error::ParamsSpecsNotDefinedForDiff {
168                profile_a: profile_a.clone(),
169                profile_b: profile_b.clone(),
170            })?
171        };
172        let states_a = profile_to_states_current_stored
173            .get(profile_a)
174            .ok_or_else(|| {
175                let profile = profile_a.clone();
176                let profiles_in_scope = profiles.to_vec();
177                Error::ProfileNotInScope {
178                    profile,
179                    profiles_in_scope,
180                }
181            })?
182            .as_ref()
183            .ok_or_else(|| {
184                let profile = profile_a.clone();
185                Error::ProfileStatesCurrentNotDiscovered { profile }
186            })?;
187        let states_b = profile_to_states_current_stored
188            .get(profile_b)
189            .ok_or_else(|| {
190                let profile = profile_b.clone();
191                let profiles_in_scope = profiles.to_vec();
192                Error::ProfileNotInScope {
193                    profile,
194                    profiles_in_scope,
195                }
196            })?
197            .as_ref()
198            .ok_or_else(|| {
199                let profile = profile_b.clone();
200                Error::ProfileStatesCurrentNotDiscovered { profile }
201            })?;
202
203        Self::diff_any(flow, params_specs, resources, states_a, states_b).await
204    }
205}
206
207impl<CmdCtxTypesT, Scope> DiffCmd<CmdCtxTypesT, Scope>
208where
209    CmdCtxTypesT: CmdCtxTypes,
210{
211    /// Returns the [`state_diff`]` for each [`Item`].
212    ///
213    /// This does not take in `CmdCtx` as it may be used by both
214    /// `CmdCtxSpsf` and `CmdCtxMpsf`
215    /// commands.
216    ///
217    /// [`Item`]: peace_cfg::Item
218    /// [`state_diff`]: peace_cfg::Item::state_diff
219    pub async fn diff_any(
220        flow: &Flow<<CmdCtxTypesT as CmdCtxTypes>::AppError>,
221        params_specs: &ParamsSpecs,
222        resources: &Resources<SetUp>,
223        states_a: &TypeMap<ItemId, BoxDtDisplay>,
224        states_b: &TypeMap<ItemId, BoxDtDisplay>,
225    ) -> Result<StateDiffs, <CmdCtxTypesT as CmdCtxTypes>::AppError> {
226        let state_diffs = {
227            let state_diffs_mut = flow
228                .graph()
229                .stream()
230                .map(Result::<_, <CmdCtxTypesT as CmdCtxTypes>::AppError>::Ok)
231                .try_filter_map(|item| async move {
232                    let state_diff_opt = item
233                        .state_diff_exec(params_specs, resources, states_a, states_b)
234                        .await?;
235
236                    Ok(state_diff_opt.map(|state_diff| (item.id().clone(), state_diff)))
237                })
238                .try_collect::<StateDiffsMut>()
239                .await?;
240
241            StateDiffs::from(state_diffs_mut)
242        };
243
244        Ok(state_diffs)
245    }
246}
247
248impl<CmdCtxTypesT, Scope> Default for DiffCmd<CmdCtxTypesT, Scope> {
249    fn default() -> Self {
250        Self(PhantomData)
251    }
252}