nimbus_fml/backends/
experimenter_manifest.rs
1use std::collections::{BTreeMap, BTreeSet};
6use std::fmt::Display;
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 command_line::commands::GenerateExperimenterManifestCmd,
12 error::{FMLError, Result},
13 intermediate_representation::{FeatureDef, FeatureManifest, PropDef, TargetLanguage, TypeRef},
14};
15
16pub(crate) type ExperimenterManifest = BTreeMap<String, ExperimenterFeature>;
17
18#[derive(Debug, Default, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub(crate) struct ExperimenterFeature {
21 description: String,
22 has_exposure: bool,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 exposure_description: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 is_early_startup: Option<bool>,
27 variables: BTreeMap<String, ExperimenterFeatureProperty>,
28}
29
30#[derive(Debug, Default, Clone, Serialize, Deserialize)]
31pub(crate) struct ExperimenterFeatureProperty {
32 #[serde(rename = "type")]
33 property_type: String,
34 description: String,
35
36 #[serde(rename = "enum")]
37 #[serde(skip_serializing_if = "Option::is_none")]
38 variants: Option<BTreeSet<String>>,
39}
40
41impl TryFrom<FeatureManifest> for ExperimenterManifest {
42 type Error = crate::error::FMLError;
43 fn try_from(fm: FeatureManifest) -> Result<Self> {
44 fm.iter_all_feature_defs()
45 .map(|(fm, f)| Ok((f.name(), fm.create_experimenter_feature(f)?)))
46 .collect()
47 }
48}
49
50impl FeatureManifest {
51 fn create_experimenter_feature(&self, feature: &FeatureDef) -> Result<ExperimenterFeature> {
52 Ok(ExperimenterFeature {
53 description: feature.doc(),
54 has_exposure: true,
55 is_early_startup: None,
56 exposure_description: Some("".into()),
59 variables: self.props_to_variables(&feature.props)?,
60 })
61 }
62
63 fn props_to_variables(
64 &self,
65 props: &[PropDef],
66 ) -> Result<BTreeMap<String, ExperimenterFeatureProperty>> {
67 let mut map = BTreeMap::new();
71 props.iter().try_for_each(|prop| -> Result<()> {
72 let typ = ExperimentManifestPropType::from(prop.typ()).to_string();
73
74 let yaml_prop = if let TypeRef::Enum(e) = prop.typ() {
75 let enum_def = self
76 .find_enum(&e)
77 .ok_or(FMLError::InternalError("Found enum with no definition"))?;
78
79 let variants = enum_def
80 .variants
81 .iter()
82 .map(|variant| variant.name())
83 .collect::<BTreeSet<String>>();
84
85 ExperimenterFeatureProperty {
86 variants: Some(variants),
87 description: prop.doc(),
88 property_type: typ,
89 }
90 } else {
91 ExperimenterFeatureProperty {
92 variants: None,
93 description: prop.doc(),
94 property_type: typ,
95 }
96 };
97 map.insert(prop.name(), yaml_prop);
98 Ok(())
99 })?;
100 Ok(map)
101 }
102}
103
104enum ExperimentManifestPropType {
105 Json,
106 Boolean,
107 Int,
108 String,
109}
110
111impl Display for ExperimentManifestPropType {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 let s = match self {
114 ExperimentManifestPropType::Boolean => "boolean",
115 ExperimentManifestPropType::Int => "int",
116 ExperimentManifestPropType::Json => "json",
117 ExperimentManifestPropType::String => "string",
118 };
119 write!(f, "{}", s)
120 }
121}
122
123impl From<TypeRef> for ExperimentManifestPropType {
124 fn from(typ: TypeRef) -> Self {
125 match typ {
126 TypeRef::Object(_)
127 | TypeRef::EnumMap(_, _)
128 | TypeRef::StringMap(_)
129 | TypeRef::List(_) => Self::Json,
130 TypeRef::Boolean => Self::Boolean,
131 TypeRef::Int => Self::Int,
132 TypeRef::String
133 | TypeRef::BundleImage
134 | TypeRef::BundleText
135 | TypeRef::StringAlias(_)
136 | TypeRef::Enum(_) => Self::String,
137 TypeRef::Option(inner) => Self::from(inner),
138 }
139 }
140}
141
142impl From<Box<TypeRef>> for ExperimentManifestPropType {
143 fn from(typ: Box<TypeRef>) -> Self {
144 (*typ).into()
145 }
146}
147
148pub(crate) fn generate_manifest(
149 ir: FeatureManifest,
150 cmd: &GenerateExperimenterManifestCmd,
151) -> Result<()> {
152 let experiment_manifest: ExperimenterManifest = ir.try_into()?;
153 let output_str = match cmd.language {
154 TargetLanguage::ExperimenterJSON => serde_json::to_string_pretty(&experiment_manifest)?,
155 TargetLanguage::ExperimenterYAML => serde_yaml::to_string(&experiment_manifest)?,
160
161 _ => serde_json::to_string(&experiment_manifest)?,
163 };
164
165 std::fs::write(&cmd.output, output_str)?;
166 Ok(())
167}