modality_mutator_protocol/
descriptor.rs

1use crate::attrs::{AttrKey, AttrVal};
2
3/// Flat, infallible view on a mutator descriptor
4pub trait MutatorDescriptor {
5    /// Returned attribute iterator should not contain duplicate keys.
6    /// It is effectively a map of key-value pairs.
7    fn get_description_attributes(&self) -> Box<dyn Iterator<Item = (AttrKey, AttrVal)> + '_>;
8}
9
10pub mod owned {
11    use attrs::AttrType;
12
13    use super::*;
14    use crate::params_attributes::{
15        is_valid_param_key, MUTATOR_PARAMS_DEFAULT_VALUE_SUFFIX, MUTATOR_PARAMS_DESCRIPTION_SUFFIX,
16        MUTATOR_PARAMS_LEAST_EFFECT_VALUE_SUFFIX, MUTATOR_PARAMS_NAME_SUFFIX,
17        MUTATOR_PARAMS_PREFIX, MUTATOR_PARAMS_VALUE_DISTRIBUTION_KIND_SUFFIX,
18        MUTATOR_PARAMS_VALUE_DISTRIBUTION_OPTION_SET_INTERFIX,
19        MUTATOR_PARAMS_VALUE_DISTRIBUTION_SCALING_SUFFIX, MUTATOR_PARAMS_VALUE_MAX_SUFFIX,
20        MUTATOR_PARAMS_VALUE_MIN_SUFFIX, MUTATOR_PARAMS_VALUE_TYPE_SUFFIX,
21    };
22    use crate::{attrs, params_attributes::is_valid_single_key_segment_contents};
23    use std::collections::{BTreeMap, HashMap};
24
25    #[derive(Debug, Clone)]
26    pub struct OwnedMutatorDescriptor {
27        pub name: Option<String>,
28        pub description: Option<String>,
29        pub layer: Option<MutatorLayer>,
30        pub group: Option<String>,
31        pub operation: Option<MutatorOperation>,
32        pub statefulness: Option<MutatorStatefulness>,
33        pub organization_custom_metadata: Option<OrganizationCustomMetadata>,
34        /// The parameters for mutations injected with this mutator
35        pub params: Vec<OwnedMutatorParamDescriptor>,
36    }
37
38    impl OwnedMutatorDescriptor {
39        pub fn into_description_attributes(
40            self,
41        ) -> Box<dyn Iterator<Item = (AttrKey, AttrVal)> + 'static> {
42            let mut all_mutator_attrs: Vec<(AttrKey, AttrVal)> = vec![];
43            if let Some(mutator_name) = self.name.as_ref() {
44                all_mutator_attrs.push((attrs::mutator::NAME, mutator_name.into()))
45            }
46            if let Some(mutator_description) = self.description.as_ref() {
47                all_mutator_attrs.push((attrs::mutator::DESCRIPTION, mutator_description.into()))
48            }
49            if let Some(mutator_layer) = self.layer.as_ref() {
50                all_mutator_attrs.push((attrs::mutator::LAYER, mutator_layer.name().into()))
51            }
52            if let Some(mutator_group) = self.group.as_ref() {
53                all_mutator_attrs.push((attrs::mutator::GROUP, mutator_group.into()))
54            }
55            if let Some(mutator_operation) = self.operation.as_ref() {
56                all_mutator_attrs.push((attrs::mutator::OPERATION, mutator_operation.name().into()))
57            }
58            if let Some(organization_custom_metadata) = self.organization_custom_metadata.as_ref() {
59                let mut mutator_level_custom_metadata_prefix = "mutator.".to_string();
60                mutator_level_custom_metadata_prefix.push_str(
61                    organization_custom_metadata
62                        .organization_name_segment
63                        .as_str(),
64                );
65                mutator_level_custom_metadata_prefix.push('.');
66                for (k, v) in organization_custom_metadata.attributes.iter() {
67                    all_mutator_attrs.push((
68                        AttrKey::from(format!("{mutator_level_custom_metadata_prefix}{k}")),
69                        v.clone(),
70                    ));
71                }
72            }
73
74            for param in &self.params {
75                all_mutator_attrs.extend(param.mutator_params_param_key_prefixed_attributes());
76            }
77
78            Box::new(all_mutator_attrs.into_iter())
79        }
80
81        pub fn try_from_description_attributes(
82            i: impl Iterator<Item = (AttrKey, AttrVal)>,
83        ) -> Result<Self, ParamDescriptorFromAttrsError> {
84            let mut attrs_map: BTreeMap<AttrKey, AttrVal> = i.collect();
85            let mut d = OwnedMutatorDescriptor {
86                name: None,
87                description: None,
88                layer: None,
89                group: None,
90                operation: None,
91                statefulness: None,
92                organization_custom_metadata: None,
93                params: vec![],
94            };
95            if let Some(AttrVal::String(s)) = attrs_map.remove(&attrs::mutator::NAME) {
96                d.name = Some(s.to_string());
97            }
98            if let Some(AttrVal::String(s)) = attrs_map.remove(&attrs::mutator::DESCRIPTION) {
99                d.description = Some(s.to_string());
100            }
101            if let Some(AttrVal::String(s)) = attrs_map.remove(&attrs::mutator::GROUP) {
102                d.group = Some(s.to_string());
103            }
104            if let Some(AttrVal::String(s)) = attrs_map.remove(&attrs::mutator::LAYER) {
105                d.layer = match s.as_ref() {
106                    "implementational" => Some(MutatorLayer::Implementational),
107                    "operational" => Some(MutatorLayer::Operational),
108                    "environmental" => Some(MutatorLayer::Environmental),
109                    _ => None,
110                };
111            }
112            if let Some(AttrVal::String(s)) = attrs_map.remove(&attrs::mutator::OPERATION) {
113                d.operation = match s.as_ref() {
114                    "delay" => Some(MutatorOperation::Delay),
115                    "duplicate" => Some(MutatorOperation::Duplicate),
116                    "drop_fraction" => Some(MutatorOperation::DropFraction),
117                    "drop_positional" => Some(MutatorOperation::DropPositional),
118                    "disable" => Some(MutatorOperation::Disable),
119                    "enable" => Some(MutatorOperation::Enable),
120                    "corrupt" => Some(MutatorOperation::Corrupt),
121                    "set_to_value" => Some(MutatorOperation::SetToValue),
122                    "substitute_next_value" => Some(MutatorOperation::SubstituteNextValue),
123                    "reorder" => Some(MutatorOperation::Reorder),
124                    "stimulate" => Some(MutatorOperation::Stimulate),
125                    _ => None,
126                };
127            }
128            if let Some(AttrVal::String(s)) = attrs_map.remove(&attrs::mutator::STATEFULNESS) {
129                d.statefulness = match s.as_ref() {
130                    "permanent" => Some(MutatorStatefulness::Permanent),
131                    "intermittent" => Some(MutatorStatefulness::Intermittent),
132                    "transient" => Some(MutatorStatefulness::Transient),
133                    _ => None,
134                };
135            }
136            let _ = attrs_map.remove(&attrs::mutator::ID);
137            // N.B. When the owned descriptor type expands to include
138            // these, toss them in here.
139            let _ = attrs_map.remove(&attrs::mutator::SOURCE_FILE);
140            let _ = attrs_map.remove(&attrs::mutator::SOURCE_LINE);
141            let _ = attrs_map.remove(&attrs::mutator::SAFETY);
142
143            let mut custom_bucket: BTreeMap<AttrKey, AttrVal> = Default::default();
144            let mut param_key_to_pairs: BTreeMap<String, BTreeMap<AttrKey, AttrVal>> =
145                Default::default();
146            for (k, v) in attrs_map {
147                if let Some(rest) = k.as_ref().strip_prefix(MUTATOR_PARAMS_PREFIX) {
148                    if let Some((key, _post_key)) = rest.split_once('.') {
149                        let post_key_pairs = param_key_to_pairs.entry(key.to_string()).or_default();
150                        post_key_pairs.insert(k, v);
151                    } else {
152                        // Drop it
153                    }
154                } else {
155                    custom_bucket.insert(k, v);
156                }
157            }
158            for (pk, pairs) in param_key_to_pairs {
159                d.params.push(
160                    OwnedMutatorParamDescriptor::try_from_param_key_and_attributes(pk, pairs)?,
161                )
162            }
163
164            // TODO, later, - organization-custom-metadata using `custom_bucket`
165
166            Ok(d)
167        }
168    }
169    #[derive(Debug, thiserror::Error, Eq, PartialEq)]
170    pub enum ParamDescriptorFromAttrsError {
171        #[error("Missing the `mutator.params.<param-key>.name` attribute")]
172        MissingParameterNameAttribute,
173        #[error("Missing the `mutator.params.<param-key>.value_type` attribute")]
174        MissingValueTypeAttribute,
175        #[error("Invalid parameter key. Parameter keys must be ASCII with no periods.")]
176        InvalidParameterKey,
177    }
178
179    impl MutatorDescriptor for OwnedMutatorDescriptor {
180        fn get_description_attributes(&self) -> Box<dyn Iterator<Item = (AttrKey, AttrVal)> + '_> {
181            let mut all_mutator_attrs: Vec<(AttrKey, AttrVal)> = vec![];
182            if let Some(mutator_name) = self.name.as_ref() {
183                all_mutator_attrs.push((attrs::mutator::NAME, mutator_name.into()))
184            }
185            if let Some(mutator_description) = self.description.as_ref() {
186                all_mutator_attrs.push((attrs::mutator::DESCRIPTION, mutator_description.into()))
187            }
188            if let Some(mutator_layer) = self.layer.as_ref() {
189                all_mutator_attrs.push((attrs::mutator::LAYER, mutator_layer.name().into()))
190            }
191            if let Some(mutator_group) = self.group.as_ref() {
192                all_mutator_attrs.push((attrs::mutator::GROUP, mutator_group.into()))
193            }
194            if let Some(mutator_operation) = self.operation.as_ref() {
195                all_mutator_attrs.push((attrs::mutator::OPERATION, mutator_operation.name().into()))
196            }
197            if let Some(organization_custom_metadata) = self.organization_custom_metadata.as_ref() {
198                let mut mutator_level_custom_metadata_prefix = "mutator.".to_string();
199                mutator_level_custom_metadata_prefix.push_str(
200                    organization_custom_metadata
201                        .organization_name_segment
202                        .as_str(),
203                );
204                mutator_level_custom_metadata_prefix.push('.');
205                for (k, v) in organization_custom_metadata.attributes.iter() {
206                    all_mutator_attrs.push((
207                        AttrKey::from(format!("{mutator_level_custom_metadata_prefix}{k}")),
208                        v.clone(),
209                    ));
210                }
211            }
212
213            for param in &self.params {
214                all_mutator_attrs.extend(param.mutator_params_param_key_prefixed_attributes());
215            }
216
217            Box::new(all_mutator_attrs.into_iter())
218        }
219    }
220
221    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
222    pub enum MutatorLayer {
223        Implementational,
224        Operational,
225        Environmental,
226    }
227    impl MutatorLayer {
228        pub fn name(&self) -> &'static str {
229            match self {
230                MutatorLayer::Implementational => "implementational",
231                MutatorLayer::Operational => "operational",
232                MutatorLayer::Environmental => "environmental",
233            }
234        }
235    }
236    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
237    pub enum MutatorStatefulness {
238        /// Sticks. Has effect immediately and continuously. Stays until explicitly told to leave.
239        Permanent,
240        /// Sticks. Has effect zero or more times at some point in the future.
241        /// Stays a possibility until explicitly told to leave.
242        Intermittent,
243        /// Sticks for a bit, probably goes away on its own.
244        /// given regular system operations.
245        Transient,
246    }
247    impl MutatorStatefulness {
248        pub fn name(&self) -> &'static str {
249            match self {
250                MutatorStatefulness::Permanent => "permanent",
251                MutatorStatefulness::Intermittent => "intermittent",
252                MutatorStatefulness::Transient => "transient",
253            }
254        }
255    }
256
257    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
258    pub enum MutatorOperation {
259        Delay,
260        Duplicate,
261        DropFraction,
262        DropPositional,
263        Disable,
264        Enable,
265        Corrupt,
266        SetToValue,
267        SubstituteNextValue,
268        Reorder,
269        Stimulate,
270    }
271    impl MutatorOperation {
272        pub fn name(&self) -> &'static str {
273            match self {
274                MutatorOperation::Delay => "delay",
275                MutatorOperation::Duplicate => "duplicate",
276                MutatorOperation::DropFraction => "drop_fraction",
277                MutatorOperation::DropPositional => "drop_positional",
278                MutatorOperation::Disable => "disable",
279                MutatorOperation::Enable => "enable",
280                MutatorOperation::Corrupt => "corrupt",
281                MutatorOperation::SetToValue => "set_to_value",
282                MutatorOperation::SubstituteNextValue => "substitute_next_value",
283                MutatorOperation::Reorder => "reorder",
284                MutatorOperation::Stimulate => "stimulate",
285            }
286        }
287    }
288
289    #[derive(Debug, Clone)]
290    pub struct OrganizationCustomMetadata {
291        /// Expected to be ASCII and not contain any periods.
292        organization_name_segment: String,
293        /// Note that we do not expect the keys to be prefixed with anything in particular.
294        pub attributes: HashMap<String, AttrVal>,
295    }
296
297    impl OrganizationCustomMetadata {
298        pub fn empty(organization_name_segment: String) -> Option<Self> {
299            if is_valid_single_key_segment_contents(organization_name_segment.as_str()) {
300                Some(OrganizationCustomMetadata {
301                    organization_name_segment,
302                    attributes: Default::default(),
303                })
304            } else {
305                None
306            }
307        }
308        pub fn new(
309            organization_name_segment: String,
310            attributes: HashMap<String, AttrVal>,
311        ) -> Option<Self> {
312            if is_valid_single_key_segment_contents(organization_name_segment.as_str()) {
313                Some(OrganizationCustomMetadata {
314                    organization_name_segment,
315                    attributes,
316                })
317            } else {
318                None
319            }
320        }
321
322        pub fn organization_name_segment(&self) -> &str {
323            self.organization_name_segment.as_str()
324        }
325    }
326
327    #[derive(Debug, Clone)]
328    pub struct OwnedMutatorParamDescriptor {
329        pub value_type: AttrType,
330        /// This is used as the parameter key interfix for parameter-specific attributes
331        /// and as the value associated with the `mutator.params.<param-key>.name attribute`
332        pub name: String,
333        pub description: Option<String>,
334        pub value_min: Option<AttrVal>,
335        pub value_max: Option<AttrVal>,
336        pub default_value: Option<AttrVal>,
337        pub least_effect_value: Option<AttrVal>,
338        pub value_distribution_kind: Option<ValueDistributionKind>,
339        pub value_distribution_scaling: Option<ValueDistributionScaling>,
340        pub value_distribution_option_set: Option<BTreeMap<String, AttrVal>>,
341        pub organization_custom_metadata: Option<OrganizationCustomMetadata>,
342    }
343
344    impl OwnedMutatorParamDescriptor {
345        pub(crate) fn mutator_params_param_key_prefixed_attributes(
346            &self,
347        ) -> impl Iterator<Item = (AttrKey, AttrVal)> {
348            let mut param_attrs: Vec<(AttrKey, AttrVal)> = vec![];
349            let mut param_prefix = crate::params_attributes::MUTATOR_PARAMS_PREFIX.to_string();
350            param_prefix.push_str(self.name.as_str());
351            // The period delimiting the segment is in the various constant suffices, don't add yet
352            param_attrs.push((
353                AttrKey::from(format!(
354                    "{param_prefix}{}",
355                    crate::params_attributes::MUTATOR_PARAMS_NAME_SUFFIX
356                )),
357                self.name.as_str().into(),
358            ));
359            param_attrs.push((
360                AttrKey::from(format!(
361                    "{param_prefix}{}",
362                    crate::params_attributes::MUTATOR_PARAMS_VALUE_TYPE_SUFFIX
363                )),
364                self.value_type.to_string().into(),
365            ));
366
367            if let Some(param_description) = self.description.as_ref() {
368                param_attrs.push((
369                    AttrKey::from(format!(
370                        "{param_prefix}{}",
371                        crate::params_attributes::MUTATOR_PARAMS_DESCRIPTION_SUFFIX
372                    )),
373                    param_description.into(),
374                ));
375            }
376            if let Some(value_min) = self.value_min.as_ref() {
377                param_attrs.push((
378                    AttrKey::from(format!(
379                        "{param_prefix}{}",
380                        crate::params_attributes::MUTATOR_PARAMS_VALUE_MIN_SUFFIX
381                    )),
382                    value_min.clone(),
383                ));
384            }
385            if let Some(value_max) = self.value_max.as_ref() {
386                param_attrs.push((
387                    AttrKey::from(format!(
388                        "{param_prefix}{}",
389                        crate::params_attributes::MUTATOR_PARAMS_VALUE_MAX_SUFFIX
390                    )),
391                    value_max.clone(),
392                ));
393            }
394            if let Some(default_value) = self.default_value.as_ref() {
395                param_attrs.push((
396                    AttrKey::from(format!(
397                        "{param_prefix}{}",
398                        crate::params_attributes::MUTATOR_PARAMS_DEFAULT_VALUE_SUFFIX
399                    )),
400                    default_value.clone(),
401                ));
402            }
403            if let Some(least_effect_value) = self.least_effect_value.as_ref() {
404                param_attrs.push((
405                    AttrKey::from(format!(
406                        "{param_prefix}{}",
407                        crate::params_attributes::MUTATOR_PARAMS_LEAST_EFFECT_VALUE_SUFFIX
408                    )),
409                    least_effect_value.clone(),
410                ));
411            }
412
413            if let Some(value_distribution_kind) = self.value_distribution_kind.as_ref() {
414                param_attrs.push((
415                    AttrKey::from(format!(
416                        "{param_prefix}{}",
417                        crate::params_attributes::MUTATOR_PARAMS_VALUE_DISTRIBUTION_KIND_SUFFIX
418                    )),
419                    value_distribution_kind.name().into(),
420                ));
421            }
422            if let Some(value_distribution_scaling) = self.value_distribution_scaling.as_ref() {
423                param_attrs.push((
424                    AttrKey::from(format!(
425                        "{param_prefix}{}",
426                        crate::params_attributes::MUTATOR_PARAMS_VALUE_DISTRIBUTION_SCALING_SUFFIX
427                    )),
428                    value_distribution_scaling.name().into(),
429                ));
430            }
431            if let Some(option_set) = self.value_distribution_option_set.as_ref() {
432                // TODO - more correct-by-construction / insertion checking that the option set keys
433                // are correctly formed (no leading periods, no duplication of param prefix bits)
434                for (option_key_segment, option_val) in option_set {
435                    param_attrs.push((AttrKey::from(format!("{param_prefix}{}{}", crate::params_attributes::MUTATOR_PARAMS_VALUE_DISTRIBUTION_OPTION_SET_INTERFIX, option_key_segment)), option_val.clone()));
436                }
437            }
438
439            if let Some(organization_custom_metadata) = self.organization_custom_metadata.as_ref() {
440                let mut parameter_level_custom_metadata_prefix = param_prefix.clone();
441                parameter_level_custom_metadata_prefix.push_str(
442                    organization_custom_metadata
443                        .organization_name_segment
444                        .as_str(),
445                );
446                parameter_level_custom_metadata_prefix.push('.');
447                for (k, v) in organization_custom_metadata.attributes.iter() {
448                    param_attrs.push((
449                        AttrKey::from(format!("{parameter_level_custom_metadata_prefix}{k}")),
450                        v.clone(),
451                    ));
452                }
453            }
454
455            param_attrs.into_iter()
456        }
457
458        /// Assumes that the attributes are of the form `mutator.params.<param-key>.the.rest`
459        pub(crate) fn try_from_param_key_and_attributes(
460            param_key: String,
461            attributes: BTreeMap<AttrKey, AttrVal>,
462        ) -> Result<Self, ParamDescriptorFromAttrsError> {
463            if !is_valid_param_key(&param_key) {
464                return Err(ParamDescriptorFromAttrsError::InvalidParameterKey);
465            }
466            let mut value_type: Option<AttrType> = None;
467            let mut name: Option<String> = None;
468            let mut description: Option<String> = None;
469            let mut value_min: Option<AttrVal> = None;
470            let mut value_max: Option<AttrVal> = None;
471            let mut default_value: Option<AttrVal> = None;
472            let mut least_effect_value: Option<AttrVal> = None;
473            let mut value_distribution_kind: Option<ValueDistributionKind> = None;
474            let mut value_distribution_scaling: Option<ValueDistributionScaling> = None;
475            let mut value_distribution_option_set: Option<BTreeMap<String, AttrVal>> = None;
476            // TODO, later
477            let organization_custom_metadata: Option<OrganizationCustomMetadata> = None;
478
479            let params_prefix = format!("mutator.params.{param_key}");
480            for (k, v) in attributes {
481                if let Some(post_key_with_period) = k.as_ref().strip_prefix(&params_prefix) {
482                    if post_key_with_period == MUTATOR_PARAMS_NAME_SUFFIX {
483                        if let AttrVal::String(s) = v {
484                            name = Some(s.to_string());
485                        }
486                    } else if post_key_with_period == MUTATOR_PARAMS_VALUE_TYPE_SUFFIX {
487                        if let AttrVal::String(s) = v {
488                            value_type = match s.as_ref() {
489                                "TimelineId" => Some(AttrType::TimelineId),
490                                "String" => Some(AttrType::String),
491                                "Integer" => Some(AttrType::Integer),
492                                "BigInteger" => Some(AttrType::BigInt),
493                                "Float" => Some(AttrType::Float),
494                                "Bool" => Some(AttrType::Bool),
495                                "Nanoseconds" => Some(AttrType::Nanoseconds),
496                                "LogicalTime" => Some(AttrType::LogicalTime),
497                                "Any" => Some(AttrType::Any),
498                                "Coordinate" => Some(AttrType::EventCoordinate),
499                                _ => None,
500                            }
501                        }
502                    } else if post_key_with_period == MUTATOR_PARAMS_DESCRIPTION_SUFFIX {
503                        if let AttrVal::String(s) = v {
504                            description = Some(s.to_string());
505                        }
506                    } else if post_key_with_period == MUTATOR_PARAMS_VALUE_MIN_SUFFIX {
507                        value_min = Some(v);
508                    } else if post_key_with_period == MUTATOR_PARAMS_VALUE_MAX_SUFFIX {
509                        value_max = Some(v);
510                    } else if post_key_with_period == MUTATOR_PARAMS_DEFAULT_VALUE_SUFFIX {
511                        default_value = Some(v);
512                    } else if post_key_with_period == MUTATOR_PARAMS_LEAST_EFFECT_VALUE_SUFFIX {
513                        least_effect_value = Some(v);
514                    } else if post_key_with_period == MUTATOR_PARAMS_VALUE_DISTRIBUTION_KIND_SUFFIX
515                    {
516                        if let AttrVal::String(s) = v {
517                            value_distribution_kind = match s.as_ref() {
518                                "continuous" => Some(ValueDistributionKind::Continuous),
519                                "discrete" => Some(ValueDistributionKind::Discrete),
520                                _ => None,
521                            };
522                        }
523                    } else if post_key_with_period
524                        == MUTATOR_PARAMS_VALUE_DISTRIBUTION_SCALING_SUFFIX
525                    {
526                        if let AttrVal::String(s) = v {
527                            value_distribution_scaling = match s.as_ref() {
528                                "linear" => Some(ValueDistributionScaling::Linear),
529                                "complex" => Some(ValueDistributionScaling::Complex),
530                                "circular" => Some(ValueDistributionScaling::Circular),
531                                _ => None,
532                            };
533                        }
534                    } else if let Some(option_set_member_key) = post_key_with_period
535                        .strip_prefix(MUTATOR_PARAMS_VALUE_DISTRIBUTION_OPTION_SET_INTERFIX)
536                    {
537                        let mut option_set =
538                            value_distribution_option_set.take().unwrap_or_default();
539                        option_set.insert(option_set_member_key.to_string(), v);
540                        value_distribution_option_set = Some(option_set)
541                    }
542                }
543            }
544            // TODO, later:
545            // units
546
547            let d = OwnedMutatorParamDescriptor {
548                value_type: if let Some(vt) = value_type {
549                    vt
550                } else {
551                    return Err(ParamDescriptorFromAttrsError::MissingValueTypeAttribute);
552                },
553                name: if let Some(n) = name {
554                    n
555                } else {
556                    return Err(ParamDescriptorFromAttrsError::MissingParameterNameAttribute);
557                },
558                description,
559                value_min,
560                value_max,
561                default_value,
562                least_effect_value,
563                value_distribution_kind,
564                value_distribution_scaling,
565                value_distribution_option_set,
566                organization_custom_metadata,
567            };
568            Ok(d)
569        }
570    }
571
572    impl OwnedMutatorParamDescriptor {
573        /// `name` is used as the parameter key interfix for parameter-specific attributes
574        /// and as the value associated with the `mutator.params.<param-key>.name attribute`
575        /// and thus must be a valid single segment of an attribute key (ASCII, no periods).
576        pub fn new(value_type: AttrType, name: String) -> Option<Self> {
577            if is_valid_single_key_segment_contents(name.as_str()) {
578                Some(OwnedMutatorParamDescriptor {
579                    value_type,
580                    name,
581                    description: None,
582                    value_min: None,
583                    value_max: None,
584                    default_value: None,
585                    least_effect_value: None,
586                    value_distribution_kind: None,
587                    value_distribution_scaling: None,
588                    value_distribution_option_set: None,
589                    organization_custom_metadata: None,
590                })
591            } else {
592                None
593            }
594        }
595
596        pub fn with_description(mut self, s: &str) -> Self {
597            self.description = Some(s.to_owned());
598            self
599        }
600
601        pub fn with_value_min(mut self, val: impl Into<AttrVal>) -> Self {
602            self.value_min = Some(val.into());
603            self
604        }
605
606        pub fn with_value_max(mut self, val: impl Into<AttrVal>) -> Self {
607            self.value_max = Some(val.into());
608            self
609        }
610
611        pub fn with_default_value(mut self, val: impl Into<AttrVal>) -> Self {
612            self.default_value = Some(val.into());
613            self
614        }
615
616        pub fn with_least_effect_value(mut self, val: impl Into<AttrVal>) -> Self {
617            self.least_effect_value = Some(val.into());
618            self
619        }
620
621        pub fn with_value_distribution_kind(mut self, kind: ValueDistributionKind) -> Self {
622            self.value_distribution_kind = Some(kind);
623            self
624        }
625
626        pub fn with_value_distribution_scaling(
627            mut self,
628            scaling: ValueDistributionScaling,
629        ) -> Self {
630            self.value_distribution_scaling = Some(scaling);
631            self
632        }
633
634        pub fn with_value_distribution_option(mut self, key: &str, val: AttrVal) -> Self {
635            if self.value_distribution_option_set.is_none() {
636                self.value_distribution_option_set = Some(Default::default());
637            }
638
639            self.value_distribution_option_set
640                .as_mut()
641                .unwrap()
642                .insert(key.to_owned(), val);
643
644            self
645        }
646    }
647
648    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
649    pub enum ValueDistributionKind {
650        Continuous,
651        Discrete,
652    }
653    impl ValueDistributionKind {
654        pub fn name(&self) -> &'static str {
655            match self {
656                ValueDistributionKind::Continuous => "continuous",
657                ValueDistributionKind::Discrete => "discrete",
658            }
659        }
660    }
661    #[derive(Debug, Copy, Clone)]
662    pub enum ValueDistributionScaling {
663        Linear,
664        Complex,
665        Circular,
666    }
667    impl ValueDistributionScaling {
668        pub fn name(&self) -> &'static str {
669            match self {
670                ValueDistributionScaling::Linear => "linear",
671                ValueDistributionScaling::Complex => "complex",
672                ValueDistributionScaling::Circular => "circular",
673            }
674        }
675    }
676}