blob: 28fcff20cd20f60cdc4829e1bf1975f4d75d7d2e [file] [log] [blame]
// 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.
use {
crate::api::{
query::{ConfigQuery, SelectMode},
ConfigError,
},
crate::mapping::{filter::filter, flatten::flatten},
crate::nested::RecursiveMap,
anyhow::anyhow,
serde_json::{Map, Value},
std::{
convert::{From, TryFrom, TryInto},
path::PathBuf,
},
};
const ADDITIVE_RETURN_ERR: &str =
"Additive mode can only be used with an array or Value return type.";
const _ADDITIVE_LEVEL_ERR: &str =
"Additive mode can only be used if config level is not specified.";
#[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(value: Value) -> Option<Value> {
flatten(value)
}
fn validate_query(query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
match query.select {
SelectMode::First => Ok(()),
SelectMode::All => Err(anyhow!(ADDITIVE_RETURN_ERR).into()),
}
}
}
impl From<ConfigValue> for Option<Value> {
fn from(value: ConfigValue) -> Self {
value.0
}
}
impl From<Option<Value>> for ConfigValue {
fn from(value: Option<Value>) -> Self {
ConfigValue(value)
}
}
impl ValueStrategy for Value {
fn handle_arrays(value: Value) -> Option<Value> {
Some(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
Ok(())
}
}
impl TryFrom<ConfigValue> for Value {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value.0.ok_or(anyhow!("no value").into())
}
}
impl ValueStrategy for Option<Value> {
fn handle_arrays(value: Value) -> Option<Value> {
Some(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
Ok(())
}
}
impl ValueStrategy for String {}
impl TryFrom<ConfigValue> for String {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| v.as_str().map(|s| s.to_string()))
.ok_or(anyhow!("no configuration String value found").into())
}
}
impl ValueStrategy for Option<String> {}
impl TryFrom<ConfigValue> for Option<String> {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
Ok(value.0.and_then(|v| v.as_str().map(|s| s.to_string())))
}
}
impl ValueStrategy for usize {}
impl TryFrom<ConfigValue> for usize {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| {
v.as_u64().and_then(|v| usize::try_from(v).ok()).or_else(|| {
if let Value::String(s) = v {
s.parse().ok()
} else {
None
}
})
})
.ok_or(anyhow!("no configuration usize value found").into())
}
}
impl ValueStrategy for u64 {}
impl TryFrom<ConfigValue> for u64 {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| {
v.as_u64().or_else(|| if let Value::String(s) = v { s.parse().ok() } else { None })
})
.ok_or(anyhow!("no configuration Number value found").into())
}
}
impl ValueStrategy for Option<u64> {}
impl TryFrom<ConfigValue> for Option<u64> {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
Ok(value.0.and_then(|v| {
v.as_u64().or_else(|| if let Value::String(s) = v { s.parse().ok() } else { None })
}))
}
}
impl ValueStrategy for u16 {}
impl TryFrom<ConfigValue> for u16 {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| {
v.as_u64().or_else(|| if let Value::String(s) = v { s.parse().ok() } else { None })
})
.and_then(|v| u16::try_from(v).ok())
.ok_or(anyhow!("no configuration Number value found").into())
}
}
impl ValueStrategy for i64 {}
impl TryFrom<ConfigValue> for i64 {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| {
v.as_i64().or_else(|| if let Value::String(s) = v { s.parse().ok() } else { None })
})
.ok_or(anyhow!("no configuration Number value found").into())
}
}
impl ValueStrategy for bool {}
impl TryFrom<ConfigValue> for bool {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| {
v.as_bool().or_else(|| if let Value::String(s) = v { s.parse().ok() } else { None })
})
.ok_or(anyhow!("no configuration Boolean value found").into())
}
}
impl ValueStrategy for PathBuf {}
impl TryFrom<ConfigValue> for PathBuf {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| v.as_str().map(|s| PathBuf::from(s.to_string())))
.ok_or(anyhow!("no configuration value found").into())
}
}
impl ValueStrategy for Option<PathBuf> {}
impl TryFrom<ConfigValue> for Option<PathBuf> {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
Ok(value.0.and_then(|v| v.as_str().map(|s| PathBuf::from(s.to_string()))))
}
}
impl<T: TryFrom<ConfigValue>> ValueStrategy for Vec<T> {
fn handle_arrays(value: Value) -> Option<Value> {
filter(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
Ok(())
}
}
impl<T: TryFrom<ConfigValue>> TryFrom<ConfigValue> for Vec<T> {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|val| match val.as_array() {
Some(v) => {
let result: Vec<T> = v
.iter()
.filter_map(|i| ConfigValue(Some(i.clone())).try_into().ok())
.collect();
if result.len() > 0 {
Some(result)
} else {
None
}
}
None => ConfigValue(Some(val)).try_into().map(|x| vec![x]).ok(),
})
.ok_or(anyhow!("no configuration value found").into())
}
}
impl ValueStrategy for Map<String, Value> {
fn handle_arrays(value: Value) -> Option<Value> {
filter(value)
}
fn validate_query(_query: &ConfigQuery<'_>) -> std::result::Result<(), ConfigError> {
Ok(())
}
}
impl TryFrom<ConfigValue> for Map<String, Value> {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|x| x.as_object().cloned())
.ok_or(anyhow!("no configuration value found").into())
}
}
impl ValueStrategy for f64 {}
impl TryFrom<ConfigValue> for f64 {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
value
.0
.and_then(|v| {
v.as_f64().or_else(|| if let Value::String(s) = v { s.parse().ok() } else { None })
})
.ok_or(anyhow!("no configuration Number value found").into())
}
}
impl ValueStrategy for Option<f64> {}
impl TryFrom<ConfigValue> for Option<f64> {
type Error = ConfigError;
fn try_from(value: ConfigValue) -> std::result::Result<Self, Self::Error> {
Ok(value.0.and_then(|v| {
v.as_f64().or_else(|| if let Value::String(s) = v { s.parse().ok() } else { None })
}))
}
}
/// Merge's `Value` b into `Value` a.
pub fn merge(a: &mut Value, b: &Value) {
match (a, b) {
(&mut Value::Object(ref mut a), &Value::Object(ref b)) => {
for (k, v) in b.iter() {
self::merge(a.entry(k.clone()).or_insert(Value::Null), v);
}
}
(a, b) => *a = b.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_merge() {
let mut proto = Value::Null;
let a = json!({
"list": [ "first", "second" ],
"string": "This is a string",
"object" : {
"foo" : "foo-prime",
"bar" : "bar-prime"
}
});
let b = json!({
"list": [ "third" ],
"title": "This is a title",
"otherObject" : {
"yourHonor" : "I object!"
}
});
merge(&mut proto, &a);
assert_eq!(proto["list"].as_array().unwrap()[0].as_str().unwrap(), "first");
assert_eq!(proto["list"].as_array().unwrap()[1].as_str().unwrap(), "second");
assert_eq!(proto["string"].as_str().unwrap(), "This is a string");
assert_eq!(proto["object"]["foo"].as_str().unwrap(), "foo-prime");
assert_eq!(proto["object"]["bar"].as_str().unwrap(), "bar-prime");
merge(&mut proto, &b);
assert_eq!(proto["list"].as_array().unwrap()[0].as_str().unwrap(), "third");
assert_eq!(proto["title"].as_str().unwrap(), "This is a title");
assert_eq!(proto["string"].as_str().unwrap(), "This is a string");
assert_eq!(proto["object"]["foo"].as_str().unwrap(), "foo-prime");
assert_eq!(proto["object"]["bar"].as_str().unwrap(), "bar-prime");
assert_eq!(proto["otherObject"]["yourHonor"].as_str().unwrap(), "I object!");
}
}