1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
use std::{any::Any, fmt::Debug};
use dyn_clone::DynClone;
use peace_cfg::{async_trait, FnCtx};
use peace_data::fn_graph::{DataAccess, DataAccessDyn};
use peace_item_model::ItemId;
use peace_params::ParamsSpecs;
use peace_resource_rt::{
resources::ts::{Empty, SetUp},
states::StatesCurrent,
type_reg::untagged::{BoxDtDisplay, TypeMap},
Resources,
};
use crate::{
outcomes::{ItemApplyBoxed, ItemApplyPartialBoxed},
ParamsSpecsTypeReg, StatesTypeReg,
};
/// Internal trait that erases the types from [`Item`]
///
/// This exists so that different implementations of [`Item`] can be held
/// under the same boxed trait.
///
/// [`Item`]: peace_cfg::Item
#[async_trait(?Send)]
pub trait ItemRt<E>:
Any + Debug + DataAccess + DataAccessDyn + DynClone + Send + Sync + 'static
{
/// Returns the ID of this item.
///
/// See [`Item::id`];
///
/// [`Item::id`]: peace_cfg::Item::id
fn id(&self) -> &ItemId;
/// Returns whether this item is equal to the other.
fn eq(&self, other: &dyn ItemRt<E>) -> bool;
/// Returns `&self` as `&dyn Any`.
///
/// This is needed to upcast to `&dyn Any` and satisfy the upcast lifetime
/// requirement.
fn as_any(&self) -> &dyn Any;
/// Initializes data for the item's functions.
async fn setup(&self, resources: &mut Resources<Empty>) -> Result<(), E>
where
E: Debug + std::error::Error;
/// Registers params and state types with the type registries for
/// deserializing from disk.
///
/// This is necessary to deserialize `ItemParamsFile`,
/// `ParamsSpecsFile`, `StatesCurrentFile`, and `StatesGoalFile`.
fn params_and_state_register(
&self,
params_specs_type_reg: &mut ParamsSpecsTypeReg,
states_type_reg: &mut StatesTypeReg,
);
/// Returns if the given two states equal.
///
/// This returns an error if the boxed states could not be downcasted to
/// this item's state, which indicates one of the following:
///
/// * Peace contains a bug, and passed an incorrect box to this item.
/// * Item IDs were swapped, such that `ItemA`'s state is passed to `ItemB`.
///
/// This needs some rework on how item IDs are implemented -- as in,
/// whether we should use a string newtype for `ItemId`s, or redesign
/// how `Item`s or related types are keyed.
///
/// Note: it is impossible to call this method if an `Item`'s state type has
/// changed -- it would have failed on deserialization.
fn state_eq(&self, state_a: &BoxDtDisplay, state_b: &BoxDtDisplay) -> Result<bool, E>
where
E: Debug + std::error::Error;
/// Returns an example fully deployed state of the managed item.
///
/// # Design
///
/// This is *expected* to always return a value, as it is used to:
///
/// * Display a diagram that shows the user what the item looks like when it
/// is fully deployed, without actually interacting with any external
/// state.
///
/// As much as possible, use the values in the provided params and data.
///
/// This function should **NOT** interact with any external services, or
/// read from files that are part of the automation process, e.g.
/// querying data from a web endpoint, or reading files that may be
/// downloaded by a predecessor.
///
/// ## Fallibility
///
/// [`Item::state_example`] is deliberately infallible to signal to
/// implementors that calling an external service / read from a file is
/// incorrect implementation for this method -- values in params / data
/// may be example values from other items that may not resolve.
///
/// [`ItemRt::state_example`] *is* fallible as value resolution for
/// parameters may fail, e.g. if there is a bug in Peace, or an item's
/// parameters requests a type that doesn't exist in [`Resources`].
///
/// ## Non-async
///
/// This signals to implementors that this function should be a cheap
/// example state computation that is relatively realistic rather than
/// determining an accurate value.
///
/// [`Item::state_example`]: peace_cfg::Item::Item::state_example
#[cfg(feature = "item_state_example")]
fn state_example(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
) -> Result<BoxDtDisplay, E>
where
E: Debug + std::error::Error;
/// Runs [`Item::state_clean`].
///
/// [`Item::state_clean`]: peace_cfg::Item::state_clean
async fn state_clean(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
) -> Result<BoxDtDisplay, E>
where
E: Debug + std::error::Error;
/// Runs [`Item::state_current`]`::`[`try_exec`].
///
/// [`Item::state_current`]: peace_cfg::Item::state_current
/// [`try_exec`]: peace_cfg::TryFnSpec::try_exec
async fn state_current_try_exec(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
fn_ctx: FnCtx<'_>,
) -> Result<Option<BoxDtDisplay>, E>
where
E: Debug + std::error::Error;
/// Runs [`Item::state_current`]`::`[`exec`].
///
/// [`Item::state_current`]: peace_cfg::Item::state_current
/// [`exec`]: peace_cfg::TryFnSpec::exec
async fn state_current_exec(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
fn_ctx: FnCtx<'_>,
) -> Result<BoxDtDisplay, E>
where
E: Debug + std::error::Error;
/// Runs [`Item::state_goal`]`::`[`try_exec`].
///
/// [`Item::state_goal`]: peace_cfg::Item::state_goal
/// [`try_exec`]: peace_cfg::TryFnSpec::try_exec
async fn state_goal_try_exec(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
fn_ctx: FnCtx<'_>,
) -> Result<Option<BoxDtDisplay>, E>
where
E: Debug + std::error::Error;
/// Runs [`Item::state_goal`]`::`[`exec`].
///
/// [`Item::state_goal`]: peace_cfg::Item::state_goal
/// [`exec`]: peace_cfg::TryFnSpec::exec
async fn state_goal_exec(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
fn_ctx: FnCtx<'_>,
) -> Result<BoxDtDisplay, E>
where
E: Debug + std::error::Error;
/// Returns the diff between the given [`State`]s.
///
/// Given `states_a` and `states_b` represent current states and goal
/// states, then this method returns `None` in the following cases:
///
/// * The current state cannot be retrieved, due to a predecessor's state
/// not existing.
/// * The goal state cannot be retrieved, due to a predecessor's state not
/// existing.
/// * A bug exists, e.g. the state is stored against the wrong type
/// parameter.
///
/// [`State`]: peace_cfg::State
async fn state_diff_exec(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
states_a: &TypeMap<ItemId, BoxDtDisplay>,
states_b: &TypeMap<ItemId, BoxDtDisplay>,
) -> Result<Option<BoxDtDisplay>, E>
where
E: Debug + std::error::Error;
/// Discovers the information needed for an ensure execution.
///
/// This runs the following functions in order:
///
/// * [`Item::state_current`]
/// * [`Item::state_goal`]
/// * [`Item::state_diff`]
/// * [`ApplyFns::check`]
///
/// [`Item::state_current`]: peace_cfg::Item::state_current
/// [`Item::state_goal`]: peace_cfg::Item::state_goal
/// [`Item::state_diff`]: peace_cfg::Item::state_diff
/// [`ApplyFns::check`]: peace_cfg::Item::ApplyFns
async fn ensure_prepare(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
fn_ctx: FnCtx<'_>,
) -> Result<ItemApplyBoxed, (E, ItemApplyPartialBoxed)>
where
E: Debug + std::error::Error;
/// Discovers the information needed for a clean execution.
///
/// This runs the following functions in order:
///
/// * [`Item::state_current`]
/// * [`Item::state_clean`]
/// * [`Item::state_diff`]
/// * [`ApplyFns::check`]
///
/// [`Item::state_current`]: peace_cfg::Item::state_current
/// [`Item::state_clean`]: peace_cfg::Item::state_clean
/// [`Item::state_diff`]: peace_cfg::Item::state_diff
/// [`ApplyFns::check`]: peace_cfg::Item::ApplyFns
async fn clean_prepare(
&self,
states_current: &StatesCurrent,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
) -> Result<ItemApplyBoxed, (E, ItemApplyPartialBoxed)>
where
E: Debug + std::error::Error;
/// Dry applies the item from its current state to its goal state.
///
/// This runs the following function in order, passing in the information
/// collected from [`ensure_prepare`] or [`clean_prepare`]:
///
/// * [`ApplyFns::exec_dry`]
///
/// # Parameters
///
/// * `resources`: The resources in the current execution.
/// * `item_apply`: The information collected in `self.ensure_prepare`.
///
/// [`ApplyFns::exec_dry`]: peace_cfg::Item::ApplyFns
async fn apply_exec_dry(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
fn_ctx: FnCtx<'_>,
item_apply: &mut ItemApplyBoxed,
) -> Result<(), E>
where
E: Debug + std::error::Error;
/// Applies the item from its current state to its goal state.
///
/// This runs the following function in order, passing in the information
/// collected from [`ensure_prepare`] or [`clean_prepare`]:
///
/// * [`ApplyFns::exec`]
///
/// # Parameters
///
/// * `resources`: The resources in the current execution.
/// * `item_apply`: The information collected in `self.ensure_prepare`.
///
/// [`ApplyFns::exec`]: peace_cfg::Item::ApplyFns
async fn apply_exec(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
fn_ctx: FnCtx<'_>,
item_apply: &mut ItemApplyBoxed,
) -> Result<(), E>
where
E: Debug + std::error::Error;
/// Returns the physical resources that this item interacts with, purely
/// using example state.
///
/// # Design
///
/// This method returns interactions from [`Item::interactions`], passing in
/// parameters computed from example state.
///
/// ## Fallibility
///
/// [`Item::interactions`] is infallible as computing `ItemInteractions`
/// should purely be instantiating objects.
///
/// [`ItemRt::interactions_example`] *is* fallible as value resolution for
/// parameters may fail, e.g. if there is a bug in Peace, or an item's
/// parameters requests a type that doesn't exist in [`Resources`].
#[cfg(all(feature = "item_interactions", feature = "item_state_example"))]
fn interactions_example(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
) -> Result<peace_item_interaction_model::ItemInteractionsExample, E>;
/// Returns the physical resources that this item interacts with, merging
/// any available current state over example state.
///
/// # Design
///
/// This method returns interactions from [`Item::interactions`], passing in
/// parameters computed from current state, or if not available, example
/// state.
///
/// For tracking which item interactions are known, for the purpose of
/// styling unknown state differently, we could return the
/// `ItemInteractions` alongside with how they were constructed:
///
/// 1. One for `ItemInteraction`s where params are fully computed using
/// fully known state.
/// 2. One for `ItemInteraction`s where params are computed using some or
/// all example state.
///
/// ## Fallibility
///
/// [`Item::interactions`] is infallible as computing `ItemInteractions`
/// should purely be instantiating objects.
///
/// [`ItemRt::interactions_current`] *is* fallible as value resolution
/// for parameters may fail, e.g. if there is a bug in Peace, or an
/// item's parameters requests a type that doesn't exist in
/// [`Resources`].
#[cfg(all(feature = "item_interactions", feature = "item_state_example"))]
fn interactions_try_current(
&self,
params_specs: &ParamsSpecs,
resources: &Resources<SetUp>,
) -> Result<peace_item_interaction_model::ItemInteractionsCurrentOrExample, E>;
/// Returns a human readable tag name that represents this item.
///
/// For example, a `FileDownloadItem<WebApp>` should return a string similar
/// to: `"Web App: File Download"`. This allows tags to be grouped by the
/// concept / information they are associated with, rather than grouping
/// tags by the type of operation.
#[cfg(all(feature = "item_interactions", feature = "item_state_example"))]
fn interactions_tag_name(&self) -> String;
}