Params Framework Support

For ergonomic usage and accurate data modelling, the following types need to exist for each item:

  • Params
  • ParamsPartial
  • ParamsSpec
  • ParamsSpecBuilder

Since Params is defined by the item implementor, the other three structs can only be defined after it, and can be generated by a proc macro.

Questions:

  • How does Peace know what fields to fill in, and how?

    The fields are known by the ParamsSpec impl, though it resides in the implementor's crate.

    Peace can reference the ParamsSpec through a Params::Spec associated type.

  • Does Peace need to know about the field, or can it be in the generated impl?

    It needs to go, for each field on the ParamsSpec, fill in the ParamsPartial. This could be implemented by ParamsSpec::partial_from(&Resources) -> ParamsPartial:

    impl ParamsSpec {
        fn partial_from(resources: &Resources) -> ParamsPartial {
            // for each field (generated by proc macro)
            let value_0 = value_0_spec.try_value_from(resources);
            let value_1 = value_1_spec.try_value_from(resources);
    
            ParamsPartial { value_0, value_1 }
        }
    }
    
    enum ValueSpec<T> {
        Value(T),
        From,
        FromMap(Box<dyn Fn(&Resources) -> Option<T>>),
    }
    
    impl<T> ValueSpec<T> {
        fn try_value_from(&self, resources: &Resources) -> Option<T> {
            match self {
                Self::Value(t) => Some(t.clone()),
                Self::From => resources.try_borrow::<T>().cloned(),
                Self::FromMap(f) => f(resources),
            }
        }
    }
  • How does ValueSpec::FromMap get instantiated:

    Probably something like this:

    impl ParamsSpecBuilder {
        // for each field
        fn field_from_map<F, U>(mut self, f: F) -> Self
        where F: &U -> Field {
            let from_map_boxed = Box::new(move |resources| {
                resources.try_borrow::<Field>().map(f)
            });
            self.field_spec = ValueSpec::FromMap(from_map_boxed);
            self
        }
    }

To allow Peace to reference those types, and for Peace to expose that to users, we need the following traits:

  • dyn Params: To know what type the ParamsPartial, ParamsSpec, and ParamsSpecBuilder are.
  • dyn ParamsSpec: Peace needs the spec to generate a ParamsPartial from Resources.

Implications On Serialization and Flow Params

It is inconvenient to have to register item params types as flow params, because the presence of the item could automatically tell Peace its params type.

However, users:

  • Must provide the param values / spec for each item for the first execution of a flow.
  • May have different preferences whether they have to provide the values / spec for subsequent executions, and whether it implicitly uses what was used before, or is explicit for each field.

What we need:

  • Flow params are serialized separately. Flow params may be values that control how things are presented, but not affect the actual functional execution, so they may be log verbosity.
  • Item params spec must be deserialized, and overwritten by provided values.
  • Item params spec must be serialized before executing a command.
  • Item params must be serialized as execution happens.
  • Item params partials do not need to be serialized, only presented, as they can be re-calculated from state for each discover / read command execution.