[ffx][config] Separate nested ops from value maps
This breaks up the logic around doing nested gets/sets/removes
from the logic that does mapping and string interpolation on
leaf Value files.
In the process, it removes the currying and cons-ish recursion
of the mapping functions, in favour of a more rust-y helper
trait nested_map that you can call on either an Option<Value>
or a ConfigValue, and then call with another mapping function.
I think it's really nice that this puts the order of the value
mapping operations in linear instead of reverse order to how
they actually run. I think it makes the code much less confusing.
The various nested_* things get moved again, this time to their
own module, as they no longer perform mapping operations
(other than NestedMap::nested_map, but I think it belongs with
the other nested functions).
Some of this restructuring may be useful to create a better
starting point for resolving fxb/101249, but this CL is not
intended to fix that on its own.
Another minor inclusion is adding a file processing argument
to the ffx-config-get arguments, which proved useful in finding
the last problems with this CL.
Bug: 101249
Change-Id: Ica85b352f3e7bb1add30d9ffbec12d88a00ee77f
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/690544
Reviewed-by: Andy Weiss <dragonbear@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Reviewed-by: Steven Grady <slgrady@google.com>
Fuchsia-Auto-Submit: Megan Batty <mgnb@google.com>
diff --git a/src/developer/ffx/config/BUILD.gn b/src/developer/ffx/config/BUILD.gn
index fe66711..57a2f81 100644
--- a/src/developer/ffx/config/BUILD.gn
+++ b/src/developer/ffx/config/BUILD.gn
@@ -48,9 +48,9 @@
"src/mapping/filter.rs",
"src/mapping/flatten.rs",
"src/mapping/home.rs",
- "src/mapping/identity.rs",
"src/mapping/mod.rs",
"src/mapping/runtime.rs",
+ "src/nested.rs",
"src/paths.rs",
"src/runtime.rs",
"src/sdk.rs",
diff --git a/src/developer/ffx/config/src/api/mod.rs b/src/developer/ffx/config/src/api/mod.rs
index 801eaaf..3a59c72 100644
--- a/src/developer/ffx/config/src/api/mod.rs
+++ b/src/developer/ffx/config/src/api/mod.rs
@@ -26,13 +26,10 @@
}
}
-pub(crate) async fn get_config<'a, T: Fn(Value) -> Option<Value>>(
- query: ConfigQuery<'a>,
- mapper: &T,
-) -> ConfigResult {
+pub(crate) async fn get_config<'a>(query: ConfigQuery<'a>) -> ConfigResult {
let config = load_config(&query.build_dir.map(String::from)).await?;
let read_guard = config.read().await;
- Ok((*read_guard).get(&query, mapper).into())
+ Ok((*read_guard).get(&query).into())
}
pub(crate) fn validate_type<T>(value: Value) -> Option<Value>
diff --git a/src/developer/ffx/config/src/api/query.rs b/src/developer/ffx/config/src/api/query.rs
index 341c8e5..df69620 100644
--- a/src/developer/ffx/config/src/api/query.rs
+++ b/src/developer/ffx/config/src/api/query.rs
@@ -13,6 +13,7 @@
All,
}
+#[derive(Debug, Clone)]
pub struct ConfigQuery<'a> {
pub name: Option<&'a str>,
pub level: Option<ConfigLevel>,
diff --git a/src/developer/ffx/config/src/api/value.rs b/src/developer/ffx/config/src/api/value.rs
index f9478cd..28fcff2 100644
--- a/src/developer/ffx/config/src/api/value.rs
+++ b/src/developer/ffx/config/src/api/value.rs
@@ -8,6 +8,7 @@
ConfigError,
},
crate::mapping::{filter::filter, flatten::flatten},
+ crate::nested::RecursiveMap,
anyhow::anyhow,
serde_json::{Map, Value},
std::{
@@ -24,11 +25,23 @@
#[derive(Debug)]
pub struct ConfigValue(pub(crate) Option<Value>);
+// See RecursiveMap for why the value version is the main implementation.
+impl RecursiveMap for ConfigValue {
+ type Output = ConfigValue;
+ fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> ConfigValue {
+ ConfigValue(self.0.recursive_map(mapper))
+ }
+}
+impl RecursiveMap for &ConfigValue {
+ type Output = ConfigValue;
+ fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> ConfigValue {
+ ConfigValue(self.0.clone()).recursive_map(mapper)
+ }
+}
+
pub trait ValueStrategy {
- fn handle_arrays<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
- ) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
- flatten(next)
+ fn handle_arrays(value: Value) -> Option<Value> {
+ flatten(value)
}
fn validate_query(query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
@@ -52,10 +65,8 @@
}
impl ValueStrategy for Value {
- fn handle_arrays<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
- ) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
- Box::new(move |value| -> Option<Value> { next(value) })
+ fn handle_arrays(value: Value) -> Option<Value> {
+ Some(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
@@ -72,10 +83,8 @@
}
impl ValueStrategy for Option<Value> {
- fn handle_arrays<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
- ) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
- Box::new(move |value| -> Option<Value> { next(value) })
+ fn handle_arrays(value: Value) -> Option<Value> {
+ Some(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
@@ -224,10 +233,8 @@
}
impl<T: TryFrom<ConfigValue>> ValueStrategy for Vec<T> {
- fn handle_arrays<'a, F: Fn(Value) -> Option<Value> + Sync>(
- next: &'a F,
- ) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
- filter(next)
+ fn handle_arrays(value: Value) -> Option<Value> {
+ filter(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
@@ -260,10 +267,8 @@
}
impl ValueStrategy for Map<String, Value> {
- fn handle_arrays<'a, F: Fn(Value) -> Option<Value> + Sync>(
- next: &'a F,
- ) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
- filter(next)
+ fn handle_arrays(value: Value) -> Option<Value> {
+ filter(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
diff --git a/src/developer/ffx/config/src/lib.rs b/src/developer/ffx/config/src/lib.rs
index f0af6ee..9a689e7 100644
--- a/src/developer/ffx/config/src/lib.rs
+++ b/src/developer/ffx/config/src/lib.rs
@@ -14,8 +14,9 @@
crate::environment::Environment,
crate::mapping::{
cache::cache, config::config, data::data, env_var::env_var, file_check::file_check,
- home::home, identity::identity, runtime::runtime,
+ home::home, runtime::runtime,
},
+ crate::nested::RecursiveMap,
crate::paths::get_default_user_file_path,
crate::storage::Config,
analytics::{is_opted_in, set_opt_in_status},
@@ -38,6 +39,7 @@
mod cache;
mod mapping;
+mod nested;
mod paths;
mod runtime;
mod storage;
@@ -84,7 +86,11 @@
{
let converted_query = query.into();
T::validate_query(&converted_query)?;
- get_config(converted_query, &validate_type::<T>).await.map_err(|e| e.into())?.try_into()
+ get_config(converted_query)
+ .await
+ .map_err(|e| e.into())?
+ .recursive_map(&validate_type::<T>)
+ .try_into()
}
pub async fn get<'a, T, U>(query: U) -> std::result::Result<T, T::Error>
@@ -95,14 +101,19 @@
{
let converted_query = query.into();
T::validate_query(&converted_query)?;
- let env_var_mapper = env_var(&validate_type::<T>);
- let home_mapper = home(&env_var_mapper);
- let config_mapper = config(&home_mapper);
- let data_mapper = data(&config_mapper);
- let cache_mapper = cache(&data_mapper);
- let runtime_mapper = runtime(&cache_mapper);
- let array_env_var_mapper = T::handle_arrays(&runtime_mapper);
- get_config(converted_query, &array_env_var_mapper).await.map_err(|e| e.into())?.try_into()
+
+ get_config(converted_query)
+ .await
+ .map_err(|e| e.into())?
+ .recursive_map(&runtime)
+ .recursive_map(&cache)
+ .recursive_map(&data)
+ .recursive_map(&config)
+ .recursive_map(&home)
+ .recursive_map(&env_var)
+ .recursive_map(&T::handle_arrays)
+ .recursive_map(&validate_type::<T>)
+ .try_into()
}
pub async fn file<'a, T, U>(query: U) -> std::result::Result<T, T::Error>
@@ -113,15 +124,18 @@
{
let converted_query = query.into();
T::validate_query(&converted_query)?;
- let file_check_mapper = file_check(&identity);
- let env_var_mapper = env_var(&file_check_mapper);
- let home_mapper = home(&env_var_mapper);
- let config_mapper = config(&home_mapper);
- let data_mapper = data(&config_mapper);
- let cache_mapper = cache(&data_mapper);
- let runtime_mapper = runtime(&cache_mapper);
- let array_env_var_mapper = T::handle_arrays(&runtime_mapper);
- get_config(converted_query, &array_env_var_mapper).await.map_err(|e| e.into())?.try_into()
+ get_config(converted_query)
+ .await
+ .map_err(|e| e.into())?
+ .recursive_map(&runtime)
+ .recursive_map(&cache)
+ .recursive_map(&data)
+ .recursive_map(&config)
+ .recursive_map(&home)
+ .recursive_map(&env_var)
+ .recursive_map(&T::handle_arrays)
+ .recursive_map(&file_check)
+ .try_into()
}
pub async fn set<'a, U: Into<ConfigQuery<'a>>>(query: U, value: Value) -> Result<()> {
@@ -218,7 +232,7 @@
check_config_files(&level, &config_query.build_dir.map(String::from))?;
let config = load_config(&config_query.build_dir.map(String::from)).await?;
let mut write_guard = config.write().await;
- let config_changed = if let Some(mut current) = (*write_guard).get(&config_query, &identity) {
+ let config_changed = if let Some(mut current) = (*write_guard).get(&config_query) {
if current.is_object() {
bail!("cannot add a value to a subtree");
} else {
diff --git a/src/developer/ffx/config/src/mapping/cache.rs b/src/developer/ffx/config/src/mapping/cache.rs
index e93c813..e770647 100644
--- a/src/developer/ffx/config/src/mapping/cache.rs
+++ b/src/developer/ffx/config/src/mapping/cache.rs
@@ -7,14 +7,12 @@
regex::Regex, serde_json::Value,
};
-pub(crate) fn cache<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
+pub(crate) fn cache(value: Value) -> Option<Value> {
lazy_static! {
static ref REGEX: Regex = Regex::new(r"\$(CACHE)").unwrap();
}
- replace(&*REGEX, get_cache_base_path, next)
+ replace(&*REGEX, get_cache_base_path, value)
}
////////////////////////////////////////////////////////////////////////////////
@@ -22,7 +20,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
fn cache_dir(default: &str) -> String {
match get_cache_base_path() {
@@ -35,19 +32,19 @@
fn test_mapper() {
let value = cache_dir("$CACHE");
let test = Value::String("$CACHE".to_string());
- assert_eq!(cache(&identity)(test), Some(Value::String(value.to_string())));
+ assert_eq!(cache(test), Some(Value::String(value.to_string())));
}
#[test]
fn test_mapper_multiple() {
let value = cache_dir("$CACHE");
let test = Value::String("$CACHE/$CACHE".to_string());
- assert_eq!(cache(&identity)(test), Some(Value::String(format!("{}/{}", value, value))));
+ assert_eq!(cache(test), Some(Value::String(format!("{}/{}", value, value))));
}
#[test]
fn test_mapper_returns_pass_through() {
let test = Value::String("$WHATEVER".to_string());
- assert_eq!(cache(&identity)(test), Some(Value::String("$WHATEVER".to_string())));
+ assert_eq!(cache(test), Some(Value::String("$WHATEVER".to_string())));
}
}
diff --git a/src/developer/ffx/config/src/mapping/config.rs b/src/developer/ffx/config/src/mapping/config.rs
index 247db06..49eb619 100644
--- a/src/developer/ffx/config/src/mapping/config.rs
+++ b/src/developer/ffx/config/src/mapping/config.rs
@@ -7,14 +7,12 @@
regex::Regex, serde_json::Value,
};
-pub(crate) fn config<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
+pub(crate) fn config(value: Value) -> Option<Value> {
lazy_static! {
static ref REGEX: Regex = Regex::new(r"\$(CONFIG)").unwrap();
}
- replace(&*REGEX, get_config_base_path, next)
+ replace(&*REGEX, get_config_base_path, value)
}
////////////////////////////////////////////////////////////////////////////////
@@ -22,7 +20,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
fn config_dir(default: &str) -> String {
match get_config_base_path() {
@@ -35,19 +32,19 @@
fn test_mapper() {
let value = config_dir("$CONFIG");
let test = Value::String("$CONFIG".to_string());
- assert_eq!(config(&identity)(test), Some(Value::String(value.to_string())));
+ assert_eq!(config(test), Some(Value::String(value.to_string())));
}
#[test]
fn test_mapper_multiple() {
let value = config_dir("$CONFIG");
let test = Value::String("$CONFIG/$CONFIG".to_string());
- assert_eq!(config(&identity)(test), Some(Value::String(format!("{}/{}", value, value))));
+ assert_eq!(config(test), Some(Value::String(format!("{}/{}", value, value))));
}
#[test]
fn test_mapper_returns_pass_through() {
let test = Value::String("$WHATEVER".to_string());
- assert_eq!(config(&identity)(test), Some(Value::String("$WHATEVER".to_string())));
+ assert_eq!(config(test), Some(Value::String("$WHATEVER".to_string())));
}
}
diff --git a/src/developer/ffx/config/src/mapping/data.rs b/src/developer/ffx/config/src/mapping/data.rs
index 538fba1..fcd81a5 100644
--- a/src/developer/ffx/config/src/mapping/data.rs
+++ b/src/developer/ffx/config/src/mapping/data.rs
@@ -7,14 +7,12 @@
regex::Regex, serde_json::Value,
};
-pub(crate) fn data<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
+pub(crate) fn data(value: Value) -> Option<Value> {
lazy_static! {
static ref REGEX: Regex = Regex::new(r"\$(DATA)").unwrap();
}
- replace(&*REGEX, get_data_base_path, next)
+ replace(&*REGEX, get_data_base_path, value)
}
////////////////////////////////////////////////////////////////////////////////
@@ -22,7 +20,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
fn data_dir(default: &str) -> String {
match get_data_base_path() {
@@ -35,19 +32,19 @@
fn test_mapper() {
let value = data_dir("$DATA");
let test = Value::String("$DATA".to_string());
- assert_eq!(data(&identity)(test), Some(Value::String(value.to_string())));
+ assert_eq!(data(test), Some(Value::String(value.to_string())));
}
#[test]
fn test_mapper_multiple() {
let value = data_dir("$DATA");
let test = Value::String("$DATA/$DATA".to_string());
- assert_eq!(data(&identity)(test), Some(Value::String(format!("{}/{}", value, value))));
+ assert_eq!(data(test), Some(Value::String(format!("{}/{}", value, value))));
}
#[test]
fn test_mapper_returns_pass_through() {
let test = Value::String("$WHATEVER".to_string());
- assert_eq!(data(&identity)(test), Some(Value::String("$WHATEVER".to_string())));
+ assert_eq!(data(test), Some(Value::String("$WHATEVER".to_string())));
}
}
diff --git a/src/developer/ffx/config/src/mapping/env_var.rs b/src/developer/ffx/config/src/mapping/env_var.rs
index a559160..4f40f3c 100644
--- a/src/developer/ffx/config/src/mapping/env_var.rs
+++ b/src/developer/ffx/config/src/mapping/env_var.rs
@@ -28,29 +28,22 @@
true
}
-pub(crate) fn env_var<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
+pub(crate) fn env_var(value: Value) -> Option<Value> {
lazy_static! {
static ref REGEX: Regex = Regex::new(r"\$([A-Z][A-Z0-9_]*)").unwrap();
}
- Box::new(move |value| -> Option<Value> {
- let env_string = preprocess(&value);
- if let Some(ref e) = env_string {
- if !check(e, &*REGEX) {
- return None;
- }
+ let env_string = preprocess(&value);
+ if let Some(ref e) = env_string {
+ if !check(e, &*REGEX) {
+ return None;
}
- match env_string
- .as_ref()
- .map(|s| replace(s, &*REGEX, |v| env::var(v).map_err(|_| anyhow!(""))))
- .map(postprocess)
- {
- Some(v) => next(v),
- None => next(value),
- }
- })
+ }
+ env_string
+ .as_ref()
+ .map(|s| replace(s, &*REGEX, |v| env::var(v).map_err(|_| anyhow!(""))))
+ .map(postprocess)
+ .or(Some(value))
}
////////////////////////////////////////////////////////////////////////////////
@@ -58,7 +51,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
fn setup_test(env_vars: Vec<(&'static str, &'static str)>) -> Box<dyn FnOnce() -> ()> {
env_vars.iter().for_each(|(var, val)| env::set_var(var, val));
@@ -72,7 +64,7 @@
let cleanup: Box<dyn FnOnce() -> ()> =
setup_test(vec![("FFX_TEST_ENV_VAR_MAPPER", "test")]);
let test = Value::String("$FFX_TEST_ENV_VAR_MAPPER".to_string());
- assert_eq!(env_var(&identity)(test), Some(Value::String("test".to_string())));
+ assert_eq!(env_var(test), Some(Value::String("test".to_string())));
cleanup();
}
@@ -83,14 +75,14 @@
let test = Value::String(
"$FFX_TEST_ENV_VAR_MAPPER_MULTIPLE/$FFX_TEST_ENV_VAR_MAPPER_MULTIPLE".to_string(),
);
- assert_eq!(env_var(&identity)(test), Some(Value::String(format!("{}/{}", "test", "test"))));
+ assert_eq!(env_var(test), Some(Value::String(format!("{}/{}", "test", "test"))));
cleanup();
}
#[test]
fn test_env_var_mapper_returns_none() {
let test = Value::String("$ENVIRONMENT_VARIABLE_THAT_DOES_NOT_EXIST".to_string());
- assert_eq!(env_var(&identity)(test), None);
+ assert_eq!(env_var(test), None);
}
#[test]
@@ -98,19 +90,19 @@
let cleanup: Box<dyn FnOnce() -> ()> =
setup_test(vec![("FFX_TEST_ENV_VAR_EXISTS", "test")]);
let test = Value::String("$HOME/$ENVIRONMENT_VARIABLE_THAT_DOES_NOT_EXIST".to_string());
- assert_eq!(env_var(&identity)(test), None);
+ assert_eq!(env_var(test), None);
cleanup();
}
#[test]
fn test_env_var_mapper_escapes_dollar_sign() {
let test = Value::String("$$HOME".to_string());
- assert_eq!(env_var(&identity)(test), Some(Value::String("$HOME".to_string())));
+ assert_eq!(env_var(test), Some(Value::String("$HOME".to_string())));
}
#[test]
fn test_env_var_returns_value_if_not_string() {
let test = Value::Bool(false);
- assert_eq!(env_var(&identity)(test), Some(Value::Bool(false)));
+ assert_eq!(env_var(test), Some(Value::Bool(false)));
}
}
diff --git a/src/developer/ffx/config/src/mapping/file_check.rs b/src/developer/ffx/config/src/mapping/file_check.rs
index dec63e8..e65e3a1 100644
--- a/src/developer/ffx/config/src/mapping/file_check.rs
+++ b/src/developer/ffx/config/src/mapping/file_check.rs
@@ -4,18 +4,15 @@
use {serde_json::Value, std::path::PathBuf};
-pub(crate) fn file_check<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + 'a + Send + Sync> {
- Box::new(move |value| -> Option<Value> {
- value.as_str().map(|s| s.to_string()).and_then(|s| {
- if PathBuf::from(s.clone()).exists() {
- next(value)
- } else {
- None
- }
- })
- })
+/// Filters for config values that map to files that are reachable. Returns None
+/// for strings that don't correspond to files discoverable by [`PathBuf::exists`],
+/// but maps to the same value for anything else.
+pub(crate) fn file_check(value: Value) -> Option<Value> {
+ match &value {
+ Value::String(s) if PathBuf::from(s).exists() => Some(value),
+ Value::String(_) => None, // filter out strings that don't correspond to existing files.
+ _ => Some(value), // but let any other type through.
+ }
}
////////////////////////////////////////////////////////////////////////////////
@@ -23,7 +20,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
use anyhow::{bail, Result};
use serde_json::json;
use tempfile::NamedTempFile;
@@ -33,7 +29,7 @@
let file = NamedTempFile::new()?;
if let Some(path) = file.path().to_str() {
let test = Value::String(path.to_string());
- assert_eq!(file_check(&identity)(test), Some(Value::String(path.to_string())));
+ assert_eq!(file_check(test), Some(Value::String(path.to_string())));
Ok(())
} else {
bail!("Unable to get temp file path");
@@ -43,7 +39,7 @@
#[test]
fn test_file_mapper_returns_none() -> Result<()> {
let test = json!("/fake_path/should_not_exist");
- assert_eq!(file_check(&identity)(test), None);
+ assert_eq!(file_check(test), None);
Ok(())
}
}
diff --git a/src/developer/ffx/config/src/mapping/filter.rs b/src/developer/ffx/config/src/mapping/filter.rs
index 53ec9f0..8679a89 100644
--- a/src/developer/ffx/config/src/mapping/filter.rs
+++ b/src/developer/ffx/config/src/mapping/filter.rs
@@ -4,22 +4,14 @@
use serde_json::Value;
-pub(crate) fn filter<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + 'a + Send + Sync> {
- Box::new(move |value| -> Option<Value> {
- if let Value::Array(values) = value {
- let result: Vec<Value> =
- values.iter().filter_map(|inner_v| next(inner_v.clone())).collect();
- if result.len() == 0 {
- None
- } else {
- Some(Value::Array(result))
- }
- } else {
- next(value)
+/// Maps values that are empty (ie. empty arrays) to None.
+pub(crate) fn filter(value: Value) -> Option<Value> {
+ if let Value::Array(values) = &value {
+ if values.len() == 0 {
+ return None;
}
- })
+ }
+ Some(value)
}
////////////////////////////////////////////////////////////////////////////////
@@ -27,27 +19,19 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
use serde_json::json;
#[test]
fn test_returns_all() {
let test = json!(["test1", "test2"]);
- let result = filter(&identity)(test);
+ let result = filter(test);
assert_eq!(result, Some(json!(["test1", "test2"])));
}
#[test]
fn test_returns_value_if_not_array() {
let test = Value::Bool(false);
- let result = filter(&identity)(test);
+ let result = filter(test);
assert_eq!(result, Some(Value::Bool(false)));
}
-
- #[test]
- fn test_returns_none() {
- let test = Value::Bool(false);
- let result = filter(&|_| -> Option<Value> { None })(test);
- assert_eq!(result, None);
- }
}
diff --git a/src/developer/ffx/config/src/mapping/flatten.rs b/src/developer/ffx/config/src/mapping/flatten.rs
index 58ef3c5..603d800 100644
--- a/src/developer/ffx/config/src/mapping/flatten.rs
+++ b/src/developer/ffx/config/src/mapping/flatten.rs
@@ -4,16 +4,13 @@
use serde_json::Value;
-pub(crate) fn flatten<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + 'a + Send + Sync> {
- Box::new(move |value| -> Option<Value> {
- if let Value::Array(values) = value {
- values.iter().find_map(|inner_v| next(inner_v.clone()))
- } else {
- next(value)
- }
- })
+/// Pick the first element of Value if it's an array.
+pub(crate) fn flatten(value: Value) -> Option<Value> {
+ if let Value::Array(values) = value {
+ values.into_iter().next()
+ } else {
+ Some(value)
+ }
}
////////////////////////////////////////////////////////////////////////////////
@@ -21,7 +18,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
#[test]
fn test_returns_first() {
@@ -29,14 +25,14 @@
Value::String("test1".to_string()),
Value::String("test2".to_string()),
]);
- let result = flatten(&identity)(test);
+ let result = flatten(test);
assert_eq!(result, Some(Value::String("test1".to_string())));
}
#[test]
fn test_returns_value_if_not_string() {
let test = Value::Bool(false);
- let result = flatten(&identity)(test);
+ let result = flatten(test);
assert_eq!(result, Some(Value::Bool(false)));
}
}
diff --git a/src/developer/ffx/config/src/mapping/home.rs b/src/developer/ffx/config/src/mapping/home.rs
index 5f0121c..57e87f2 100644
--- a/src/developer/ffx/config/src/mapping/home.rs
+++ b/src/developer/ffx/config/src/mapping/home.rs
@@ -9,29 +9,22 @@
serde_json::Value,
};
-pub(crate) fn home<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
+pub(crate) fn home(value: Value) -> Option<Value> {
lazy_static! {
static ref REGEX: Regex = Regex::new(r"\$(HOME)").unwrap();
}
- Box::new(move |value| -> Option<Value> {
- match preprocess(&value)
- .as_ref()
- .map(|s| {
- replace(s, &*REGEX, |v| {
- Ok(home::home_dir().map_or(v.to_string(), |home_path| {
- home_path.to_str().map_or(v.to_string(), |s| s.to_string())
- }))
- })
+ preprocess(&value)
+ .as_ref()
+ .map(|s| {
+ replace(s, &*REGEX, |v| {
+ Ok(home::home_dir().map_or(v.to_string(), |home_path| {
+ home_path.to_str().map_or(v.to_string(), |s| s.to_string())
+ }))
})
- .map(postprocess)
- {
- Some(v) => next(v),
- None => next(value),
- }
- })
+ })
+ .map(postprocess)
+ .or(Some(value))
}
////////////////////////////////////////////////////////////////////////////////
@@ -39,7 +32,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
fn home_dir(default: &str) -> String {
home::home_dir().map_or(default.to_string(), |home_path| {
@@ -51,19 +43,19 @@
fn test_mapper() {
let value = home_dir("$HOME");
let test = Value::String("$HOME".to_string());
- assert_eq!(home(&identity)(test), Some(Value::String(value.to_string())));
+ assert_eq!(home(test), Some(Value::String(value.to_string())));
}
#[test]
fn test_mapper_multiple() {
let value = home_dir("$HOME");
let test = Value::String("$HOME/$HOME".to_string());
- assert_eq!(home(&identity)(test), Some(Value::String(format!("{}/{}", value, value))));
+ assert_eq!(home(test), Some(Value::String(format!("{}/{}", value, value))));
}
#[test]
fn test_mapper_returns_pass_through() {
let test = Value::String("$WHATEVER".to_string());
- assert_eq!(home(&identity)(test), Some(Value::String("$WHATEVER".to_string())));
+ assert_eq!(home(test), Some(Value::String("$WHATEVER".to_string())));
}
}
diff --git a/src/developer/ffx/config/src/mapping/identity.rs b/src/developer/ffx/config/src/mapping/identity.rs
deleted file mode 100644
index aee67a96..0000000
--- a/src/developer/ffx/config/src/mapping/identity.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2020 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-pub(crate) fn identity(value: serde_json::Value) -> Option<serde_json::Value> {
- Some(value)
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// tests
-#[cfg(test)]
-mod test {
- use super::*;
- use serde_json::Value;
-
- #[test]
- fn test_returns_first() {
- let test = Value::String("test1".to_string());
- let result = identity(test);
- assert_eq!(result, Some(Value::String("test1".to_string())));
- }
-}
diff --git a/src/developer/ffx/config/src/mapping/mod.rs b/src/developer/ffx/config/src/mapping/mod.rs
index e8c773e..f10a7ea 100644
--- a/src/developer/ffx/config/src/mapping/mod.rs
+++ b/src/developer/ffx/config/src/mapping/mod.rs
@@ -3,9 +3,9 @@
// found in the LICENSE file.
use {
- anyhow::{anyhow, bail, Result},
+ anyhow::Result,
regex::{Captures, Regex},
- serde_json::{Map, Value},
+ serde_json::Value,
std::path::PathBuf,
};
@@ -17,7 +17,6 @@
pub(crate) mod filter;
pub(crate) mod flatten;
pub(crate) mod home;
-pub(crate) mod identity;
pub(crate) mod runtime;
// Negative lookbehind (or lookahead for that matter) is not supported in Rust's regex.
@@ -48,138 +47,20 @@
.into_owned()
}
-pub(crate) fn replace<'a, T, P>(
- regex: &'a Regex,
- base_path: P,
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a>
+pub(crate) fn replace<'a, P>(regex: &'a Regex, base_path: P, value: Value) -> Option<Value>
where
- T: Fn(Value) -> Option<Value> + Sync,
P: Fn() -> Result<PathBuf> + Sync + Send + 'a,
{
- Box::new(move |value| -> Option<Value> {
- match preprocess(&value)
- .as_ref()
- .map(|s| {
- replace_regex(s, regex, |v| {
- match base_path() {
- Ok(p) => Ok(p.to_str().map_or(v.to_string(), |s| s.to_string())),
- Err(_) => Ok(v.to_string()), //just pass through
- }
- })
+ preprocess(&value)
+ .as_ref()
+ .map(|s| {
+ replace_regex(s, regex, |v| {
+ match base_path() {
+ Ok(p) => Ok(p.to_str().map_or(v.to_string(), |s| s.to_string())),
+ Err(_) => Ok(v.to_string()), //just pass through
+ }
})
- .map(postprocess)
- {
- Some(v) => next(v),
- None => next(value),
- }
- })
-}
-
-pub(crate) fn nested_map<T: Fn(Value) -> Option<Value>>(
- cur: Option<Value>,
- mapper: &T,
-) -> Option<Value> {
- cur.and_then(|c| {
- if let Value::Object(map) = c {
- let mut result = Map::new();
- for (key, value) in map.iter() {
- let new_value = if value.is_object() {
- nested_map(map.get(key).cloned(), mapper)
- } else {
- map.get(key).cloned().and_then(|v| mapper(v))
- };
- if let Some(new_value) = new_value {
- result.insert(key.to_string(), new_value);
- }
- }
- if result.len() == 0 {
- None
- } else {
- Some(Value::Object(result))
- }
- } else {
- mapper(c)
- }
- })
-}
-
-pub(crate) fn nested_get<T: Fn(Value) -> Option<Value>>(
- cur: &Option<Value>,
- key: &str,
- remaining_keys: &[&str],
- mapper: &T,
-) -> Option<Value> {
- cur.as_ref().and_then(|c| {
- if remaining_keys.len() == 0 {
- nested_map(c.get(key).cloned(), mapper)
- } else {
- nested_get(&c.get(key).cloned(), remaining_keys[0], &remaining_keys[1..], mapper)
- }
- })
-}
-
-pub(crate) fn nested_set(
- cur: &mut Map<String, Value>,
- key: &str,
- remaining_keys: &[&str],
- value: Value,
-) -> bool {
- if remaining_keys.len() == 0 {
- // Exit early if the value hasn't changed.
- if let Some(old_value) = cur.get(key) {
- if old_value == &value {
- return false;
- }
- }
- cur.insert(key.to_string(), value);
- true
- } else {
- match cur.get(key) {
- Some(value) => {
- if !value.is_object() {
- // Any literals will be overridden.
- cur.insert(key.to_string(), Value::Object(Map::new()));
- }
- }
- None => {
- cur.insert(key.to_string(), Value::Object(Map::new()));
- }
- }
- // Just ensured this would be the case.
- let next_map = cur
- .get_mut(key)
- .expect("unable to get configuration")
- .as_object_mut()
- .expect("Unable to set configuration value as map");
- nested_set(next_map, remaining_keys[0], &remaining_keys[1..], value)
- }
-}
-
-pub(crate) fn nested_remove(
- cur: &mut Map<String, Value>,
- key: &str,
- remaining_keys: &[&str],
-) -> Result<()> {
- if remaining_keys.len() == 0 {
- cur.remove(&key.to_string()).ok_or(anyhow!("Config key not found")).map(|_| ())
- } else {
- match cur.get(key) {
- Some(value) => {
- if !value.is_object() {
- bail!("Configuration literal found when expecting a map.")
- }
- }
- None => {
- bail!("Configuration key not found.");
- }
- }
- // Just ensured this would be the case.
- let next_map = cur
- .get_mut(key)
- .expect("unable to get configuration")
- .as_object_mut()
- .expect("Unable to set configuration value as map");
- nested_remove(next_map, remaining_keys[0], &remaining_keys[1..])
- }
+ })
+ .map(postprocess)
+ .or(Some(value))
}
diff --git a/src/developer/ffx/config/src/mapping/runtime.rs b/src/developer/ffx/config/src/mapping/runtime.rs
index 0f689a1..8775d00 100644
--- a/src/developer/ffx/config/src/mapping/runtime.rs
+++ b/src/developer/ffx/config/src/mapping/runtime.rs
@@ -7,14 +7,12 @@
regex::Regex, serde_json::Value,
};
-pub(crate) fn runtime<'a, T: Fn(Value) -> Option<Value> + Sync>(
- next: &'a T,
-) -> Box<dyn Fn(Value) -> Option<Value> + Send + Sync + 'a> {
+pub(crate) fn runtime(value: Value) -> Option<Value> {
lazy_static! {
static ref REGEX: Regex = Regex::new(r"\$(RUNTIME)").unwrap();
}
- replace(&*REGEX, get_runtime_base_path, next)
+ replace(&*REGEX, get_runtime_base_path, value)
}
////////////////////////////////////////////////////////////////////////////////
@@ -22,7 +20,6 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
fn runtime_dir(default: &str) -> String {
match get_runtime_base_path() {
@@ -35,19 +32,19 @@
fn test_mapper() {
let value = runtime_dir("$RUNTIME");
let test = Value::String("$RUNTIME".to_string());
- assert_eq!(runtime(&identity)(test), Some(Value::String(value.to_string())));
+ assert_eq!(runtime(test), Some(Value::String(value.to_string())));
}
#[test]
fn test_mapper_multiple() {
let value = runtime_dir("$RUNTIME");
let test = Value::String("$RUNTIME/$RUNTIME".to_string());
- assert_eq!(runtime(&identity)(test), Some(Value::String(format!("{}/{}", value, value))));
+ assert_eq!(runtime(test), Some(Value::String(format!("{}/{}", value, value))));
}
#[test]
fn test_mapper_returns_pass_through() {
let test = Value::String("$WHATEVER".to_string());
- assert_eq!(runtime(&identity)(test), Some(Value::String("$WHATEVER".to_string())));
+ assert_eq!(runtime(test), Some(Value::String("$WHATEVER".to_string())));
}
}
diff --git a/src/developer/ffx/config/src/nested.rs b/src/developer/ffx/config/src/nested.rs
new file mode 100644
index 0000000..e3a034a
--- /dev/null
+++ b/src/developer/ffx/config/src/nested.rs
@@ -0,0 +1,166 @@
+// Copyright 2020 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Functions for interacting with nested json objects in a recursive way.
+
+use {
+ anyhow::{Context, Result},
+ serde_json::{map::Entry, Map, Value},
+ std::iter::FromIterator,
+};
+
+/// A trait that adds a recursive mapping function to a nested json value tree.
+///
+/// Note that implementations of this should be on value types and not references,
+/// with perhaps an additional impl for a reference that clones. This is because most
+/// of the time most values won't change, so it makes sense to copy the whole thing up
+/// front and only have to build a new item if necessary.
+///
+/// This also makes it so you can efficiently chain [`RecursiveMap::recursive_map`]
+/// calls together, since they will continue to use the same Value items until one
+/// is overwritten.
+pub(crate) trait RecursiveMap {
+ /// The type of output that the filtering will return. That type must implement
+ /// this trait as well.
+ type Output: RecursiveMap;
+
+ /// Filters values recursively through the function provided.
+ fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output;
+}
+
+impl RecursiveMap for Value {
+ type Output = Option<Value>;
+ fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Option<Value> {
+ match self {
+ Value::Object(map) => {
+ let mut result = Map::new();
+ for (key, value) in map.into_iter() {
+ let new_value = if value.is_object() {
+ value.clone().recursive_map(mapper)
+ } else {
+ mapper(value.clone())
+ };
+ if let Some(new_value) = new_value.clone() {
+ result.insert(key.clone(), new_value);
+ }
+ }
+ if result.len() == 0 {
+ None
+ } else {
+ mapper(Value::Object(result))
+ }
+ }
+ Value::Array(arr) => {
+ let result =
+ Vec::from_iter(arr.into_iter().filter_map(|value| value.recursive_map(mapper)));
+ if result.len() == 0 {
+ None
+ } else {
+ mapper(Value::Array(result))
+ }
+ }
+ other => mapper(other),
+ }
+ }
+}
+
+impl RecursiveMap for &Value {
+ type Output = Option<Value>;
+ fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output {
+ self.clone().recursive_map(mapper)
+ }
+}
+impl RecursiveMap for Option<&Value> {
+ type Output = Option<Value>;
+ fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output {
+ self.and_then(|value| value.clone().recursive_map(mapper))
+ }
+}
+impl RecursiveMap for Option<Value> {
+ type Output = Option<Value>;
+ fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output {
+ self.and_then(|value| value.recursive_map(mapper))
+ }
+}
+
+/// Search the given nested json value for the given `key`, and then recurse through
+/// the rest of the tree for the `remaining_keys`. Returns the value at that position if found.
+pub(crate) fn nested_get<'a>(
+ cur: Option<&'a Value>,
+ key: &str,
+ remaining_keys: &[&str],
+) -> Option<&'a Value> {
+ cur.and_then(|cur| {
+ if remaining_keys.len() == 0 {
+ cur.get(key)
+ } else {
+ nested_get(cur.get(key), remaining_keys[0], &remaining_keys[1..])
+ }
+ })
+}
+
+/// Find `key` in `cur`, then recurisively search for the `remaining_keys` through the nested
+/// object for the position to insert, creating Object entries as it goes if necessary (including
+/// overwriting leaf values in the way). Sets the value if not already set to the same value,
+/// and returns true if it did (or false if it already existed as the same value).
+pub(crate) fn nested_set(
+ cur: &mut Map<String, Value>,
+ key: &str,
+ remaining_keys: &[&str],
+ value: Value,
+) -> bool {
+ if remaining_keys.len() == 0 {
+ // Exit early if the value hasn't changed.
+ if let Some(old_value) = cur.get(key) {
+ if old_value == &value {
+ return false;
+ }
+ }
+ cur.insert(key.to_string(), value);
+ true
+ } else {
+ if let Entry::Occupied(mut occupied) = cur.entry(key) {
+ let val = occupied.get_mut();
+ if let Value::Object(ref mut next_map) = val {
+ nested_set(next_map, remaining_keys[0], &remaining_keys[1..], value)
+ } else {
+ let mut next_map = Map::new();
+ nested_set(&mut next_map, remaining_keys[0], &remaining_keys[1..], value);
+ *val = Value::Object(next_map);
+ // since we're creating the rest of the tree, we know we either succeeded and inserted or failed and crashed.
+ true
+ }
+ } else {
+ let mut next_map = Map::new();
+ nested_set(&mut next_map, remaining_keys[0], &remaining_keys[1..], value);
+ cur.insert(key.to_string(), Value::Object(next_map));
+ // since we're creating the rest of the tree, we know we either succeeded and inserted or failed and crashed.
+ true
+ }
+ }
+}
+
+/// Searches the nested object to find the given `key`, then recursively through the `remaining_keys`,
+/// and removes it if found. If the key or any of its parent keys don't exist, it will return an error.
+pub(crate) fn nested_remove(
+ cur: &mut Map<String, Value>,
+ key: &str,
+ remaining_keys: &[&str],
+) -> Result<()> {
+ if remaining_keys.len() == 0 {
+ cur.remove(&key.to_string()).context("Config key not found").map(|_| ())
+ } else {
+ // Just ensured this would be the case.
+ let next_map = cur
+ .get_mut(key)
+ .context("Configuration key not found.")?
+ .as_object_mut()
+ .context("Configuration literal found when expecting a map.")?;
+ nested_remove(next_map, remaining_keys[0], &remaining_keys[1..])?;
+ if next_map.len() == 0 {
+ cur.remove(key).context("Current key not found trying to recursively remove")?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/developer/ffx/config/src/runtime.rs b/src/developer/ffx/config/src/runtime.rs
index 4138429..7699aa2 100644
--- a/src/developer/ffx/config/src/runtime.rs
+++ b/src/developer/ffx/config/src/runtime.rs
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::mapping;
+use crate::nested::nested_set;
use anyhow::{anyhow, bail, Context, Result};
use serde_json::{Map, Value};
use std::{fs::File, io::BufReader, path::Path};
@@ -13,7 +13,7 @@
let s: Vec<&str> = pair.trim().split('=').collect();
if s.len() == 2 {
let key_vec: Vec<&str> = s[0].split('.').collect();
- mapping::nested_set(
+ nested_set(
&mut runtime_config,
key_vec[0],
&key_vec[1..],
diff --git a/src/developer/ffx/config/src/storage.rs b/src/developer/ffx/config/src/storage.rs
index 585410c..fb6cc63 100644
--- a/src/developer/ffx/config/src/storage.rs
+++ b/src/developer/ffx/config/src/storage.rs
@@ -5,7 +5,7 @@
use {
crate::api::query::SelectMode,
crate::environment::Environment,
- crate::mapping::{nested_get, nested_map, nested_remove, nested_set},
+ crate::nested::{nested_get, nested_remove, nested_set},
crate::{ConfigLevel, ConfigQuery},
anyhow::{bail, Context, Result},
config_macros::include_default,
@@ -18,6 +18,7 @@
},
};
+#[derive(Debug, Clone)]
pub struct Config {
default: Option<Value>,
build: Option<Value>,
@@ -169,11 +170,7 @@
})
}
- pub fn get<T: Fn(Value) -> Option<Value>>(
- &self,
- key: &ConfigQuery<'_>,
- mapper: &T,
- ) -> Option<Value> {
+ pub fn get(&self, key: &ConfigQuery<'_>) -> Option<Value> {
if let Some(name) = key.name {
// Check for nested config values if there's a '.' in the key
let key_vec: Vec<&str> = name.split('.').collect();
@@ -185,16 +182,18 @@
ConfigLevel::Global => &self.global,
ConfigLevel::Default => &self.default,
};
- nested_get(config, key_vec[0], &key_vec[1..], mapper)
+ nested_get(config.as_ref(), key_vec[0], &key_vec[1..]).cloned()
} else {
match key.select {
- SelectMode::First => {
- self.iter().find_map(|c| nested_get(c, key_vec[0], &key_vec[1..], mapper))
- }
+ SelectMode::First => self
+ .iter()
+ .find_map(|c| nested_get(c.as_ref(), key_vec[0], &key_vec[1..]))
+ .cloned(),
SelectMode::All => {
let result: Vec<Value> = self
.iter()
- .filter_map(|c| nested_get(c, key_vec[0], &key_vec[1..], mapper))
+ .filter_map(|c| nested_get(c.as_ref(), key_vec[0], &key_vec[1..]))
+ .cloned()
.collect();
if result.len() > 0 {
Some(Value::Array(result))
@@ -213,7 +212,7 @@
ConfigLevel::Global => &self.global,
ConfigLevel::Default => &self.default,
};
- nested_map(config.clone(), mapper)
+ config.clone()
} else {
// Not really supported now. Maybe in the future.
None
@@ -330,7 +329,7 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::mapping::identity::identity;
+ use crate::nested::RecursiveMap;
use regex::Regex;
use serde_json::json;
use std::io::{BufReader, BufWriter};
@@ -398,7 +397,7 @@
None,
);
- let value = persistent_config.get(&"name".into(), &identity);
+ let value = persistent_config.get(&"name".into());
assert!(value.is_some());
assert_eq!(value.unwrap(), Value::String(String::from("User")));
@@ -477,7 +476,7 @@
runtime: None,
};
- let value = test.get(&"name".into(), &identity);
+ let value = test.get(&"name".into());
assert!(value.is_some());
assert_eq!(value.unwrap(), Value::String(String::from("User")));
@@ -489,7 +488,7 @@
runtime: None,
};
- let value_build = test_build.get(&"name".into(), &identity);
+ let value_build = test_build.get(&"name".into());
assert!(value_build.is_some());
assert_eq!(value_build.unwrap(), Value::String(String::from("Build")));
@@ -501,7 +500,7 @@
runtime: None,
};
- let value_global = test_global.get(&"name".into(), &identity);
+ let value_global = test_global.get(&"name".into());
assert!(value_global.is_some());
assert_eq!(value_global.unwrap(), Value::String(String::from("Global")));
@@ -513,14 +512,14 @@
runtime: None,
};
- let value_default = test_default.get(&"name".into(), &identity);
+ let value_default = test_default.get(&"name".into());
assert!(value_default.is_some());
assert_eq!(value_default.unwrap(), Value::String(String::from("Default")));
let test_none =
Config { user: None, build: None, global: None, default: None, runtime: None };
- let value_none = test_none.get(&"name".into(), &identity);
+ let value_none = test_none.get(&"name".into());
assert!(value_none.is_none());
Ok(())
}
@@ -535,7 +534,7 @@
runtime: None,
};
test.set(&("name", &ConfigLevel::User).into(), Value::String(String::from("whatever")))?;
- let value = test.get(&"name".into(), &identity);
+ let value = test.get(&"name".into());
assert_eq!(value, Some(Value::String(String::from("whatever"))));
Ok(())
}
@@ -549,7 +548,7 @@
default: Some(serde_json::from_str(DEFAULT)?),
runtime: None,
};
- let value = test.get(&"field that does not exist".into(), &identity);
+ let value = test.get(&"field that does not exist".into());
assert!(value.is_none());
Ok(())
}
@@ -564,7 +563,7 @@
runtime: None,
};
test.set(&("name", &ConfigLevel::User).into(), Value::String(String::from("user-test")))?;
- let value = test.get(&"name".into(), &identity);
+ let value = test.get(&"name".into());
assert!(value.is_some());
assert_eq!(value.unwrap(), Value::String(String::from("user-test")));
Ok(())
@@ -583,28 +582,19 @@
&("name", &ConfigLevel::User).into(),
Value::String(String::from("user-test1"))
)?);
- assert_eq!(
- test.get(&"name".into(), &identity).unwrap(),
- Value::String(String::from("user-test1"))
- );
+ assert_eq!(test.get(&"name".into()).unwrap(), Value::String(String::from("user-test1")));
assert!(!test.set(
&("name", &ConfigLevel::User).into(),
Value::String(String::from("user-test1"))
)?);
- assert_eq!(
- test.get(&"name".into(), &identity).unwrap(),
- Value::String(String::from("user-test1"))
- );
+ assert_eq!(test.get(&"name".into()).unwrap(), Value::String(String::from("user-test1")));
assert!(test.set(
&("name", &ConfigLevel::User).into(),
Value::String(String::from("user-test2"))
)?);
- assert_eq!(
- test.get(&"name".into(), &identity).unwrap(),
- Value::String(String::from("user-test2"))
- );
+ assert_eq!(test.get(&"name".into()).unwrap(), Value::String(String::from("user-test2")));
Ok(())
}
@@ -613,26 +603,26 @@
fn test_set_build_from_none() -> Result<()> {
let mut test =
Config { user: None, build: None, global: None, default: None, runtime: None };
- let value_none = test.get(&"name".into(), &identity);
+ let value_none = test.get(&"name".into());
assert!(value_none.is_none());
let error_set = test
.set(&("name", &ConfigLevel::Default).into(), Value::String(String::from("default")));
assert!(error_set.is_err(), "Should not be able to set default values at runtime");
- let value_default = test.get(&"name".into(), &identity);
+ let value_default = test.get(&"name".into());
assert!(
value_default.is_none(),
"Default value should be unset after failed attempt to set it"
);
test.set(&("name", &ConfigLevel::Global).into(), Value::String(String::from("global")))?;
- let value_global = test.get(&"name".into(), &identity);
+ let value_global = test.get(&"name".into());
assert!(value_global.is_some());
assert_eq!(value_global.unwrap(), Value::String(String::from("global")));
test.set(&("name", &ConfigLevel::Build).into(), Value::String(String::from("build")))?;
- let value_build = test.get(&"name".into(), &identity);
+ let value_build = test.get(&"name".into());
assert!(value_build.is_some());
assert_eq!(value_build.unwrap(), Value::String(String::from("build")));
test.set(&("name", &ConfigLevel::User).into(), Value::String(String::from("user")))?;
- let value_user = test.get(&"name".into(), &identity);
+ let value_user = test.get(&"name".into());
assert!(value_user.is_some());
assert_eq!(value_user.unwrap(), Value::String(String::from("user")));
Ok(())
@@ -648,20 +638,20 @@
runtime: None,
};
test.remove(&("name", &ConfigLevel::User).into())?;
- let user_value = test.get(&"name".into(), &identity);
+ let user_value = test.get(&"name".into());
assert!(user_value.is_some());
assert_eq!(user_value.unwrap(), Value::String(String::from("Build")));
test.remove(&("name", &ConfigLevel::Build).into())?;
- let global_value = test.get(&"name".into(), &identity);
+ let global_value = test.get(&"name".into());
assert!(global_value.is_some());
assert_eq!(global_value.unwrap(), Value::String(String::from("Global")));
test.remove(&("name", &ConfigLevel::Global).into())?;
- let default_value = test.get(&"name".into(), &identity);
+ let default_value = test.get(&"name".into());
assert!(default_value.is_some());
assert_eq!(default_value.unwrap(), Value::String(String::from("Default")));
let error_removed = test.remove(&("name", &ConfigLevel::Default).into());
assert!(error_removed.is_err(), "Should not be able to remove a default value");
- let default_value = test.get(&"name".into(), &identity);
+ let default_value = test.get(&"name".into());
assert_eq!(
default_value,
Some(Value::String(String::from("Default"))),
@@ -674,7 +664,7 @@
#[test]
fn test_default() {
let test = Config::new(None, None, None, None);
- let default_value = test.get(&"log.enabled".into(), &identity);
+ let default_value = test.get(&"log.enabled".into());
assert_eq!(
default_value.unwrap(),
Value::Array(vec![Value::String("$FFX_LOG_ENABLED".to_string()), Value::Bool(true)])
@@ -704,11 +694,13 @@
}
fn test_map(value: Value) -> Option<Value> {
- if value == "TEST_MAP".to_string() {
- Some(Value::String("passed".to_string()))
- } else {
- Some(Value::String("failed".to_string()))
- }
+ value
+ .as_str()
+ .map(|s| match s {
+ "TEST_MAP" => Value::String("passed".to_string()),
+ _ => Value::String("failed".to_string()),
+ })
+ .or(Some(value))
}
#[test]
@@ -722,9 +714,9 @@
};
let test_mapping = "TEST_MAP".to_string();
let test_passed = "passed".to_string();
- let mapped_value = test.get(&"name".into(), &test_map);
+ let mapped_value = test.get(&"name".into()).as_ref().recursive_map(&test_map);
assert_eq!(mapped_value, Some(Value::String(test_passed)));
- let identity_value = test.get(&"name".into(), &identity);
+ let identity_value = test.get(&"name".into());
assert_eq!(identity_value, Some(Value::String(test_mapping)));
Ok(())
}
@@ -738,7 +730,7 @@
default: None,
runtime: Some(serde_json::from_str(NESTED)?),
};
- let value = test.get(&"name.nested".into(), &identity);
+ let value = test.get(&"name.nested".into());
assert_eq!(value, Some(Value::String("Nested".to_string())));
Ok(())
}
@@ -752,7 +744,7 @@
default: Some(serde_json::from_str(DEFAULT)?),
runtime: Some(serde_json::from_str(NESTED)?),
};
- let value = test.get(&"name".into(), &identity);
+ let value = test.get(&"name".into());
assert_eq!(value, Some(serde_json::from_str("{\"nested\": \"Nested\"}")?));
Ok(())
}
@@ -766,7 +758,7 @@
default: Some(serde_json::from_str(NESTED)?),
runtime: Some(serde_json::from_str(RUNTIME)?),
};
- let value = test.get(&"name.nested".into(), &identity);
+ let value = test.get(&"name.nested".into());
assert_eq!(value, Some(Value::String("Nested".to_string())));
Ok(())
}
@@ -780,7 +772,7 @@
default: Some(serde_json::from_str(NESTED)?),
runtime: Some(serde_json::from_str(DEEP)?),
};
- let value = test.get(&"name.nested".into(), &test_map);
+ let value = test.get(&"name.nested".into()).as_ref().recursive_map(&test_map);
assert_eq!(value, Some(serde_json::from_str("{\"deep\": {\"name\": \"passed\"}}")?));
Ok(())
}
@@ -790,7 +782,7 @@
let mut test =
Config { user: None, build: None, global: None, default: None, runtime: None };
test.set(&("name.nested", &ConfigLevel::User).into(), Value::Bool(false))?;
- let nested_value = test.get(&"name".into(), &identity);
+ let nested_value = test.get(&"name".into());
assert_eq!(nested_value, Some(serde_json::from_str("{\"nested\": false}")?));
Ok(())
}
@@ -809,7 +801,7 @@
"nested": "Nested",
"updated": true
});
- let nested_value = test.get(&"name".into(), &identity);
+ let nested_value = test.get(&"name".into());
assert_eq!(nested_value, Some(expected));
Ok(())
}
@@ -827,10 +819,10 @@
let expected = json!({
"updated": true
});
- let nested_value = test.get(&"name".into(), &identity);
+ let nested_value = test.get(&"name".into());
assert_eq!(nested_value, Some(expected));
test.set(&("name.updated", &ConfigLevel::User).into(), serde_json::from_str(NESTED)?)?;
- let nested_value = test.get(&"name.updated.name.nested".into(), &identity);
+ let nested_value = test.get(&"name.updated.name.nested".into());
assert_eq!(nested_value, Some(Value::String(String::from("Nested"))));
Ok(())
}
@@ -868,7 +860,7 @@
runtime: None,
};
test.remove(&("name.nested.deep.name", &ConfigLevel::User).into())?;
- let value = test.get(&"name".into(), &identity);
+ let value = test.get(&"name".into());
assert_eq!(value, None);
Ok(())
}
@@ -883,7 +875,7 @@
runtime: None,
};
test.remove(&("name.nested", &ConfigLevel::User).into())?;
- let value = test.get(&"name".into(), &identity);
+ let value = test.get(&"name".into());
assert_eq!(value, None);
Ok(())
}
@@ -897,7 +889,7 @@
default: Some(serde_json::from_str(DEFAULT)?),
runtime: Some(serde_json::from_str(RUNTIME)?),
};
- let value = test.get(&("name", &SelectMode::All).into(), &identity);
+ let value = test.get(&("name", &SelectMode::All).into());
match value {
Some(Value::Array(v)) => {
assert_eq!(v.len(), 5);
diff --git a/src/developer/ffx/plugins/config/src/args.rs b/src/developer/ffx/plugins/config/src/args.rs
index 00eda08..060377a 100644
--- a/src/developer/ffx/plugins/config/src/args.rs
+++ b/src/developer/ffx/plugins/config/src/args.rs
@@ -58,6 +58,7 @@
pub enum MappingMode {
Raw,
Substitute,
+ File,
}
#[derive(FromArgs, Debug, PartialEq)]
@@ -78,8 +79,8 @@
default = "MappingMode::Substitute",
short = 'p'
)]
- /// how to process results. Possible values are "r/raw", and "s/sub/substitute". Defaults
- /// to "substitute". Currently only supported if a name is given.
+ /// how to process results. Possible values are "r/raw", "s/sub/substitute", or "f/file".
+ /// Defaults to "substitute". Currently only supported if a name is given.
pub process: MappingMode,
#[argh(option, from_str_fn(parse_mode), default = "SelectMode::First", short = 's')]
@@ -203,7 +204,10 @@
match value {
"r" | "raw" => Ok(MappingMode::Raw),
"s" | "sub" | "substitute" => Ok(MappingMode::Substitute),
- _ => Err(String::from("Unrecognized value. Possible values are \"raw\", \"sub\".")),
+ "f" | "file" => Ok(MappingMode::File),
+ _ => Err(String::from(
+ "Unrecognized value. Possible values are \"raw\", \"sub\", or \"file\".",
+ )),
}
}
diff --git a/src/developer/ffx/plugins/config/src/lib.rs b/src/developer/ffx/plugins/config/src/lib.rs
index f84fbd6..c2b8813 100644
--- a/src/developer/ffx/plugins/config/src/lib.rs
+++ b/src/developer/ffx/plugins/config/src/lib.rs
@@ -6,8 +6,8 @@
anyhow::{anyhow, Context, Result},
errors::{ffx_bail, ffx_bail_with_code},
ffx_config::{
- add, api::query::ConfigQuery, api::ConfigError, env_file, environment::Environment, get,
- print_config, raw, remove, set, set_metrics_status, show_metrics_status, ConfigLevel,
+ add, api::query::ConfigQuery, api::ConfigError, env_file, environment::Environment, file,
+ get, print_config, raw, remove, set, set_metrics_status, show_metrics_status, ConfigLevel,
},
ffx_config_plugin_args::{
AddCommand, AnalyticsCommand, AnalyticsControlCommand, ConfigCommand, EnvAccessCommand,
@@ -78,6 +78,10 @@
let value: std::result::Result<Vec<Value>, _> = get(query).await;
output_array(writer, value)
}
+ MappingMode::File => {
+ let value = file(query).await?;
+ output(writer, value)
+ }
},
None => print_config(writer, &get_cmd.build_dir).await,
}