1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
45use std::path::Path;
67use anyhow::Result;
8use nimbus_fml::intermediate_representation::FeatureManifest;
9use serde_json::Value;
1011use crate::{
12 sources::{ExperimentSource, ManifestSource},
13 value_utils::{self, CliUtils},
14};
1516impl ManifestSource {
17pub(crate) fn print_defaults<P>(
18&self,
19 feature_id: Option<&String>,
20 output: Option<P>,
21 ) -> Result<bool>
22where
23P: AsRef<Path>,
24 {
25let manifest: FeatureManifest = self.try_into()?;
26let json = self.get_defaults_json(&manifest, feature_id)?;
27 value_utils::write_to_file_or_print(output, &json)?;
28Ok(true)
29 }
3031fn get_defaults_json(
32&self,
33 fm: &FeatureManifest,
34 feature_id: Option<&String>,
35 ) -> Result<Value> {
36Ok(match feature_id {
37Some(id) => {
38let (_, feature) = fm.find_feature(id).ok_or_else(|| {
39 anyhow::Error::msg(format!("Feature '{id}' does not exist in this manifest"))
40 })?;
41 feature.default_json()
42 }
43_ => fm.default_json(),
44 })
45 }
46}
4748impl ExperimentSource {
49#[allow(clippy::too_many_arguments)]
50pub(crate) fn print_features<P>(
51&self,
52 branch: &String,
53 manifest_source: &ManifestSource,
54 feature_id: Option<&String>,
55 validate: bool,
56 multi: bool,
57 output: Option<P>,
58 ) -> Result<bool>
59where
60P: AsRef<Path>,
61 {
62let json = self.get_features_json(manifest_source, feature_id, branch, validate, multi)?;
63 value_utils::write_to_file_or_print(output, &json)?;
64Ok(true)
65 }
6667fn get_features_json(
68&self,
69 manifest_source: &ManifestSource,
70 feature_id: Option<&String>,
71 branch: &String,
72 validate: bool,
73 multi: bool,
74 ) -> Result<Value> {
75let value = self.try_into()?;
7677// Find the named branch.
78let branches = value_utils::try_find_branches_from_experiment(&value)?;
79let b = branches
80 .iter()
81 .find(|b| b.get_str("slug").unwrap() == branch)
82 .ok_or_else(|| anyhow::format_err!("Branch '{branch}' does not exist"))?;
8384// Find the features for this branch: there may be more than one.
85let feature_values = value_utils::try_find_features_from_branch(b)?;
8687// Now extract the relevant features out of the branches.
88let mut result = serde_json::value::Map::new();
89for f in feature_values {
90let id = f.get_str("featureId")?;
91let value = f
92 .get("value")
93 .ok_or_else(|| anyhow::format_err!("Branch {branch} feature {id} has no value"))?;
94match feature_id {
95None => {
96// If the user hasn't specified a feature, then just add it.
97result.insert(id.to_string(), value.clone());
98 }
99Some(feature_id) if feature_id == id => {
100// If the user has specified a feature, and this is it, then also add it.
101result.insert(id.to_string(), value.clone());
102 }
103// Otherwise, the user has specified a feature, and this wasn't it.
104_ => continue,
105 }
106 }
107108// By now: we have all the features that we need, and no more.
109110 // If validating, then we should merge with the defaults from the manifest.
111 // If not, then nothing more is needed to be done: we're delivering the partial feature configuration.
112if validate {
113let fm: FeatureManifest = manifest_source.try_into()?;
114let mut new = serde_json::value::Map::new();
115for (id, value) in result {
116let def = fm.validate_feature_config(&id, value)?;
117 new.insert(id.to_owned(), def.default_json());
118 }
119 result = new;
120 }
121122Ok(if !multi && result.len() == 1 {
123// By default, if only a single feature is being displayed,
124 // we can output just the feature config.
125match (result.values().find(|_| true), feature_id) {
126 (Some(v), _) => v.to_owned(),
127 (_, Some(id)) => anyhow::bail!(
128"The '{id}' feature is not involved in '{branch}' branch of '{self}'"
129),
130 (_, _) => {
131anyhow::bail!("No features available in '{branch}' branch of '{self}'")
132 }
133 }
134 } else {
135// Otherwise, we can output the `{ featureId: featureValue }` in its entirety.
136Value::Object(result)
137 })
138 }
139}