Params Specification
For an item to work with different values, the values must be passed in.
Item implementors define these as part of the Item
trait:
trait Item {
type Params: ..;
}
Use Cases
The following shows a number of use cases of these params:
-
State Apply: Param values must be known, and Peace should pass concrete values to the
Item::{state_current, state_goal, apply}
functions. -
State Discovery (fallible):
Param values may be known, if predecessors have previously executed.
-
try_state_current
:StateDiscoverCmd::current
e.g. Look up file contents on a remote host:
match params_partial.dest_ip() { Some(dest_ip) => Some(file_hash(dest_ip, path)), None => None, // or `Some(FileState::None)` }
-
try_state_goal
:StateDiscoverCmd::goal
e.g. Look up source file contents:
match params_partial.src_path() { Some(src_path) => file_hash(src_path), None => None, // or `Some(FileState::None)` }
-
By Item Function
-
try_state_current
: Should work withfield_partial
s. -
try_state_goal
: Should work withfield_partial
s. -
state_current
: Needs real concrete param values. -
state_goal
: Needs real concrete param values. -
state_diff
: Doesn't need parameters or data; everything should be captured inState
s.But for presentation, it's useful to know what a file should be (current vs goal), or difference between params (multiple profile current vs current).
-
state_clean
: Maybe always returnsItemState::None
, and doesn't need parameters or data.However, presenting
state_clean
with e.g. a file path, would mean the None state contains the value, which meansstate_clean
needs params.Arguably
state_goal
will show the path that would be created.StateDiff
for cleaning should also show the deletion of the path. -
apply_check
: Doesn't need parameters or data. -
apply_dry
: Needs concrete param values, even if they are fake. -
apply
: Needs real concrete param values.
Encoding: Serialization / Deserialization
Because:
- It is convenient to serialize
Item::Params::Spec
and store it, and deserialize it for use at a later time. - It is useful to support config-based parameter specification (no compiler needed).
- It is not possible to serialize closures.
Then there must be a way to encode the same functionality that Item::Params::Spec::field_from_map
provides, as something serializable.
Possibilities:
ToString
andFromStr
impls that represent the logic- Serialized form uses enum variants, and when deserializing, map that back to functions.
- Custom language.
Code Implications
From the implementor's perspective, item trait needs to change to support the above use cases.
The following snippets are here to show the changes that include the above concepts. These are:
- non-compilable.
- just enough to show where types are changed.
- show certain trait bounds (non-exhaustive).
- do not include the encoding / decoding of
field_from_map
concept.
Framework
// Traits in Peace Framework
trait Item {
type Params: Params;
fn setup(&self, resources);
fn try_state_current(fn_ctx, params_partial, data);
fn try_state_goal(fn_ctx, params_partial, data);
fn state_clean ( params_partial, data);
fn state_current (fn_ctx, params, data);
fn state_goal (fn_ctx, params, data);
fn apply_dry (fn_ctx, params, data, state_current, state_target, diff);
fn apply (fn_ctx, params, data, state_current, state_target, diff);
fn apply_check ( params_partial, data, state_current, state_target, diff);
fn state_diff ( params_partial, data, state_a, state_b);
// Once more, with types:
fn setup(&self, &mut Resources<Empty>);
fn try_state_current(FnCtx<'_>, Self::Params<'_>::Partial, Self::Data<'_>);
fn try_state_goal(FnCtx<'_>, Self::Params<'_>::Partial, Self::Data<'_>);
fn state_clean ( Self::Params<'_>::Partial, Self::Data<'_>);
fn state_current (FnCtx<'_>, Self::Params<'_> , Self::Data<'_>);
fn state_goal (FnCtx<'_>, Self::Params<'_> , Self::Data<'_>);
fn apply_dry (FnCtx<'_>, Self::Params<'_> , Self::Data<'_>, Self::State, Self::State, Self::StateDiff);
fn apply (FnCtx<'_>, Self::Params<'_> , Self::Data<'_>, Self::State, Self::State, Self::StateDiff);
fn apply_check ( Self::Params<'_>::Partial, Self::Data<'_>, Self::State, Self::State, Self::StateDiff);
fn state_diff ( Self::Params<'_>::Partial, Self::Data<'_>, Self::State, Self::State);
}
/// For Peace to access <Item::Params as Params>::Spec
trait Params {
type Spec: Serialize + Deserialize;
type SpecBuilder: SpecBuilder<Output = Self::Spec>;
type Partial: Serialize + Deserialize;
}
enum ValueSpec<T> {
Value(T),
From,
FromMap(Box<dyn Fn(&Resources) -> Option<T>>),
}
Also need to provide a Params
derive macro.
Design Note
The apply_check
and state_diff
functions usually are not expected to need params
and data
, but some items may use them, such as the ShCmdItem
which accesses params to determine the script to run.
Regarding params will be Params
or Params<'_>::Partial
for state_diff
, if we call state_diff
for a file upload item, we must have both the current state and goal state. Then we need to ask, does having both states imply Params
is fully resolvable?
If we call state_current
for a file upload:
- the destination server may not be there
- so params may not have the IP
- we may still return
Some(State::Empty)
- So params may still be partial, even if
State
isSome
.
If we call state_goal
for a file upload:
- the source file may not be there
- so params may not have the source content hash
- we may still return
Some(State::Empty)
- So params may still be partial, even if
State
isSome
.
Implementor
// Implementation
struct FileUploadItem;
impl Item for FileUploadItem {
type Params = FileUploadParams;
}
#[derive(Clone, Debug, Params, Serialize, Deserialize)]
struct FileUploadParams {
src: PathBuf,
dest_ip: IpAddr,
dest_path: PathBuf,
}
Auto generated by Params
derive:
impl Params for FileUploadParams {
type Spec = FileUploadParamsSpec;
type SpecBuilder = FileUploadParamsSpecBuilder;
type Partial = FileUploadParamsPartial;
}
// Serialize / Deserialize not needed.
struct FileUploadParamsPartial {
src: Option<PathBuf>,
dest_ip: Option<IpAddr>,
dest_path: Option<PathBuf>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct FileUploadParamsSpec {
src: ValueSpec<PathBuf>,
dest_ip: ValueSpec<IpAddr>,
dest_path: ValueSpec<PathBuf>,
}
#[derive(Clone, Debug)]
struct FileUploadParamsSpecBuilder {
src: Option<ValueSpec<PathBuf>>,
dest_ip: Option<ValueSpec<IpAddr>>,
dest_path: Option<ValueSpec<PathBuf>>,
}
See:
Implementation
See the params_derive
crate for code gen.
The following command helps to see what's been generated.
cargo expand --package peace_item_blank blank_params \
| sd -f cm '^ #\[doc\(hidden\)\][\n](^[ ]{4}[a-z# ].+[\n])+^[ ]{4}\};\n' '' \
| sd -f cm '^ #\[automatically_derived\][\n](^[ ]{4}[# a-z{].*[\n])+^[ ]{4}\{?\}\n' '' \
| sd -f cm '^ #\[allow\(unused_qualifications\)\][\n](^[ ]{4}[# a-z{].*[\n])+^[ ]{4}\}\n' '' \
| sd -f cm '^ #\[serde\(bound = ""\)\]' ' #[derive(Serialize, Deserialize)]\n #[serde(bound = "")]' \
| sd -f cm '^ extern crate test;[\n](^[ ]{4}.*[\n])+^\}' '}' \
| sd -f cm '^( pub struct [A-Za-z0-9_]+Partial)' ' #[derive(PartialEq, Eq)]\n$1' \
| sd -f cm '^( #\[derivative\()' ' #[derive(derivative::Derivative)]\n$1' \
| xclip -se c