diff options
author | David Orchard <if_coding@fastmail.com> | 2021-08-01 00:03:10 -0700 |
---|---|---|
committer | David Orchard <if_coding@fastmail.com> | 2021-08-15 10:31:58 -0700 |
commit | cc0530a7716779f954b1756905a9f4ceb7eb6d62 (patch) | |
tree | 311b2ca44a282bbc629a08123f53a855cba38c1c | |
parent | 622efaca17256fb42dafc61c8df1f3037c1fb772 (diff) |
Move order preservation under a feature gate
41 files changed, 358 insertions, 277 deletions
@@ -21,6 +21,7 @@ yaml = ["yaml-rust"] hjson = ["serde-hjson"] ini = ["rust-ini"] json5 = ["json5_rs"] +preserve_order = ["linked-hash-map", "toml/preserve_order", "serde_json/preserve_order", "ron/indexmap"] [dependencies] async-trait = "0.1.50" @@ -28,14 +29,14 @@ lazy_static = "1.0" serde = "1.0.8" nom = "6" -toml = { version = "0.5", features = ["preserve_order"], optional = true } -serde_json = { version = "1.0.2", features = ["std", "preserve_order"], optional = true } +toml = { version = "0.5", optional = true } +serde_json = { version = "1.0.2", optional = true } yaml-rust = { version = "0.4", optional = true } serde-hjson = { version = "0.9", default-features = false, optional = true } rust-ini = { version = "0.17", optional = true } -ron = { version = "0.6", features = ["indexmap"], optional = true } +ron = { version = "0.6", optional = true } json5_rs = { version = "0.3", optional = true, package = "json5" } -linked-hash-map = { version = "0.5.4", features = ["serde_impl"] } +linked-hash-map = { version = "0.5.4", optional = true, features = ["serde_impl"] } [dev-dependencies] serde_derive = "1.0.8" @@ -45,3 +46,6 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util warp = "0.3.1" futures = "0.3.15" reqwest = "0.11.3" +# Workaround to activate non-default features for tests: +# https://github.com/rust-lang/cargo/issues/2911 +config = { path = ".", features = ["preserve_order"] } diff --git a/examples/async_source/main.rs b/examples/async_source/main.rs index 6c836a3..5134822 100644 --- a/examples/async_source/main.rs +++ b/examples/async_source/main.rs @@ -1,7 +1,6 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; -use config::{builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat}; +use config::{builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat, MapImpl}; use async_trait::async_trait; use futures::{select, FutureExt}; @@ -57,7 +56,7 @@ struct HttpSource { #[async_trait] impl AsyncSource for HttpSource { - async fn collect(&self) -> Result<LinkedHashMap<String, config::Value>, ConfigError> { + async fn collect(&self) -> Result<MapImpl<String, config::Value>, ConfigError> { reqwest::get(&self.uri) .await .map_err(|e| ConfigError::Foreign(Box::new(e)))? // error conversion is possible from custom AsyncSource impls diff --git a/examples/glob/src/main.rs b/examples/glob/src/main.rs index c85f86e..d429917 100644 --- a/examples/glob/src/main.rs +++ b/examples/glob/src/main.rs @@ -1,5 +1,5 @@ use std::path::Path; -use std::collections::LinkedHashMap; +use std::collections::MapImpl; use config::*; use glob::glob; @@ -14,9 +14,9 @@ fn main() { .merge(File::from(Path::new("conf/05-some.yml"))).unwrap() .merge(File::from(Path::new("conf/99-extra.json"))).unwrap(); - // Print out our settings (as a LinkedHashMap) + // Print out our settings (as a MapImpl) println!("\n{:?} \n\n-----------", - settings.try_into::<LinkedHashMap<String, String>>().unwrap()); + settings.try_into::<MapImpl<String, String>>().unwrap()); // Option 2 // -------- @@ -28,9 +28,9 @@ fn main() { File::from(Path::new("conf/99-extra.json"))]) .unwrap(); - // Print out our settings (as a LinkedHashMap) + // Print out our settings (as a MapImpl) println!("\n{:?} \n\n-----------", - settings.try_into::<LinkedHashMap<String, String>>().unwrap()); + settings.try_into::<MapImpl<String, String>>().unwrap()); // Option 3 // -------- @@ -43,7 +43,7 @@ fn main() { .collect::<Vec<_>>()) .unwrap(); - // Print out our settings (as a LinkedHashMap) + // Print out our settings (as a MapImpl) println!("\n{:?} \n\n-----------", - settings.try_into::<LinkedHashMap<String, String>>().unwrap()); + settings.try_into::<MapImpl<String, String>>().unwrap()); } diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index 698401a..5d4c3bc 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -1,4 +1,4 @@ -use std::collections::LinkedHashMap; +use std::collections::MapImpl; fn main() { let mut settings = config::Config::default(); @@ -9,7 +9,7 @@ fn main() { // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key .merge(config::Environment::with_prefix("APP")).unwrap(); - // Print out our settings (as a LinkedHashMap) + // Print out our settings (as a MapImpl) println!("{:?}", - settings.try_into::<LinkedHashMap<String, String>>().unwrap()); + settings.try_into::<MapImpl<String, String>>().unwrap()); } diff --git a/examples/watch/src/main.rs b/examples/watch/src/main.rs index 9e676ad..fed4ed0 100644 --- a/examples/watch/src/main.rs +++ b/examples/watch/src/main.rs @@ -1,5 +1,5 @@ use config::*; -use std::collections::LinkedHashMap; +use std::collections::MapImpl; use std::sync::RwLock; use notify::{RecommendedWatcher, DebouncedEvent, Watcher, RecursiveMode}; use std::sync::mpsc::channel; @@ -20,7 +20,7 @@ fn show() { .read() .unwrap() .clone() - .try_into::<LinkedHashMap<String, String>>() + .try_into::<MapImpl<String, String>>() .unwrap()); } diff --git a/src/builder.rs b/src/builder.rs index 65f49ae..5100f5f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,8 +1,9 @@ -use linked_hash_map::LinkedHashMap; use std::iter::IntoIterator; use std::str::FromStr; + use crate::error::Result; +use crate::map::MapImpl; use crate::source::AsyncSource; use crate::{config::Config, path::Expression, source::Source, value::Value}; @@ -88,8 +89,8 @@ use crate::{config::Config, path::Expression, source::Source, value::Value}; /// ``` #[derive(Debug, Clone, Default)] pub struct ConfigBuilder<St: BuilderState> { - defaults: LinkedHashMap<Expression, Value>, - overrides: LinkedHashMap<Expression, Value>, + defaults: MapImpl<Expression, Value>, + overrides: MapImpl<Expression, Value>, state: St, } @@ -121,8 +122,8 @@ pub struct DefaultState { /// Refer to [`ConfigBuilder`] for similar API sample usage or to the examples folder of the crate, where such a source is implemented. #[derive(Debug, Clone, Default)] pub struct AsyncConfigBuilder { - defaults: LinkedHashMap<Expression, Value>, - overrides: LinkedHashMap<Expression, Value>, + defaults: MapImpl<Expression, Value>, + overrides: MapImpl<Expression, Value>, sources: Vec<SourceType>, } @@ -245,11 +246,11 @@ impl ConfigBuilder<DefaultState> { } fn build_internal( - defaults: LinkedHashMap<Expression, Value>, - overrides: LinkedHashMap<Expression, Value>, + defaults: MapImpl<Expression, Value>, + overrides: MapImpl<Expression, Value>, sources: &[Box<dyn Source + Send + Sync>], ) -> Result<Config> { - let mut cache: Value = LinkedHashMap::<String, Value>::new().into(); + let mut cache: Value = MapImpl::<String, Value>::new().into(); // Add defaults for (key, val) in defaults.into_iter() { @@ -323,11 +324,11 @@ impl ConfigBuilder<AsyncState> { } async fn build_internal( - defaults: LinkedHashMap<Expression, Value>, - overrides: LinkedHashMap<Expression, Value>, + defaults: MapImpl<Expression, Value>, + overrides: MapImpl<Expression, Value>, sources: &[SourceType], ) -> Result<Config> { - let mut cache: Value = LinkedHashMap::<String, Value>::new().into(); + let mut cache: Value = MapImpl::<String, Value>::new().into(); // Add defaults for (key, val) in defaults.into_iter() { diff --git a/src/config.rs b/src/config.rs index 6302c45..8c410f0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,3 @@ -use linked_hash_map::LinkedHashMap; use std::fmt::Debug; use crate::builder::{ConfigBuilder, DefaultState}; @@ -6,6 +5,7 @@ use serde::de::Deserialize; use serde::ser::Serialize; use crate::error::*; +use crate::map::MapImpl; use crate::path; use crate::ser::ConfigSerializer; use crate::source::Source; @@ -16,8 +16,8 @@ use crate::value::{Table, Value}; /// them according to the source's priority. #[derive(Clone, Debug)] pub struct Config { - defaults: LinkedHashMap<path::Expression, Value>, - overrides: LinkedHashMap<path::Expression, Value>, + defaults: MapImpl<path::Expression, Value>, + overrides: MapImpl<path::Expression, Value>, sources: Vec<Box<dyn Source + Send + Sync>>, /// Root of the cached configuration. @@ -83,7 +83,7 @@ impl Config { #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn refresh(&mut self) -> Result<&mut Config> { self.cache = { - let mut cache: Value = LinkedHashMap::<String, Value>::new().into(); + let mut cache: Value = MapImpl::<String, Value>::new().into(); // Add defaults for (key, val) in self.defaults.iter() { @@ -181,7 +181,7 @@ impl Config { self.get(key).and_then(Value::into_bool) } - pub fn get_table(&self, key: &str) -> Result<LinkedHashMap<String, Value>> { + pub fn get_table(&self, key: &str) -> Result<MapImpl<String, Value>> { self.get(key).and_then(Value::into_table) } @@ -212,7 +212,7 @@ impl Source for Config { Box::new((*self).clone()) } - fn collect(&self) -> Result<LinkedHashMap<String, Value>> { + fn collect(&self) -> Result<MapImpl<String, Value>> { self.cache.clone().into_table() } } @@ -1,4 +1,3 @@ -use linked_hash_map::LinkedHashMap; use std::collections::VecDeque; use std::iter::Enumerate; @@ -6,6 +5,7 @@ use serde::de; use crate::config::Config; use crate::error::*; +use crate::map::MapImpl; use crate::value::{Table, Value, ValueKind}; impl<'de> de::Deserializer<'de> for Value { @@ -200,7 +200,7 @@ struct MapAccess { } impl MapAccess { - fn new(table: LinkedHashMap<String, Value>) -> Self { + fn new(table: MapImpl<String, Value>) -> Self { MapAccess { elements: table.into_iter().collect(), } @@ -1,7 +1,7 @@ -use linked_hash_map::LinkedHashMap; use std::env; use crate::error::*; +use crate::map::MapImpl; use crate::source::Source; use crate::value::{Value, ValueKind}; @@ -79,8 +79,8 @@ impl Source for Environment { Box::new((*self).clone()) } - fn collect(&self) -> Result<LinkedHashMap<String, Value>> { - let mut m = LinkedHashMap::new(); + fn collect(&self) -> Result<MapImpl<String, Value>> { + let mut m = MapImpl::new(); let uri: String = "the environment".into(); let separator = self.separator.as_deref().unwrap_or(""); diff --git a/src/file/format/hjson.rs b/src/file/format/hjson.rs index a61afef..68b9c9c 100644 --- a/src/file/format/hjson.rs +++ b/src/file/format/hjson.rs @@ -1,19 +1,19 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; pub fn parse( uri: Option<&String>, text: &str, -) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { +) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { // Parse a JSON object value from the text // TODO: Have a proper error fire if the root of a file is ever not a Table let value = from_hjson_value(uri, &serde_hjson::from_str(text)?); match value.kind { ValueKind::Table(map) => Ok(map), - _ => Ok(LinkedHashMap::new()), + _ => Ok(MapImpl::new()), } } @@ -30,7 +30,7 @@ fn from_hjson_value(uri: Option<&String>, value: &serde_hjson::Value) -> Value { serde_hjson::Value::Bool(value) => Value::new(uri, ValueKind::Boolean(value)), serde_hjson::Value::Object(ref table) => { - let mut m = LinkedHashMap::new(); + let mut m = MapImpl::new(); for (key, value) in table { m.insert(key.clone(), from_hjson_value(uri, value)); diff --git a/src/file/format/ini.rs b/src/file/format/ini.rs index 47f3499..b5f742d 100644 --- a/src/file/format/ini.rs +++ b/src/file/format/ini.rs @@ -1,20 +1,20 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; use ini::Ini; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; pub fn parse( uri: Option<&String>, text: &str, -) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { - let mut map: LinkedHashMap<String, Value> = LinkedHashMap::new(); +) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { + let mut map: MapImpl<String, Value> = MapImpl::new(); let i = Ini::load_from_str(text)?; for (sec, prop) in i.iter() { match sec { Some(sec) => { - let mut sec_map: LinkedHashMap<String, Value> = LinkedHashMap::new(); + let mut sec_map: MapImpl<String, Value> = MapImpl::new(); for (k, v) in prop.iter() { sec_map.insert( k.to_owned(), diff --git a/src/file/format/json.rs b/src/file/format/json.rs index a6a9443..c4895fb 100644 --- a/src/file/format/json.rs +++ b/src/file/format/json.rs @@ -1,19 +1,19 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; pub fn parse( uri: Option<&String>, text: &str, -) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { +) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { // Parse a JSON object value from the text // TODO: Have a proper error fire if the root of a file is ever not a Table let value = from_json_value(uri, &serde_json::from_str(text)?); match value.kind { ValueKind::Table(map) => Ok(map), - _ => Ok(LinkedHashMap::new()), + _ => Ok(MapImpl::new()), } } @@ -34,7 +34,7 @@ fn from_json_value(uri: Option<&String>, value: &serde_json::Value) -> Value { serde_json::Value::Bool(value) => Value::new(uri, ValueKind::Boolean(value)), serde_json::Value::Object(ref table) => { - let mut m = LinkedHashMap::new(); + let mut m = MapImpl::new(); for (key, value) in table { m.insert(key.clone(), from_json_value(uri, value)); diff --git a/src/file/format/json5.rs b/src/file/format/json5.rs index 952b265..69432c0 100644 --- a/src/file/format/json5.rs +++ b/src/file/format/json5.rs @@ -1,7 +1,7 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; use crate::error::{ConfigError, Unexpected}; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; #[derive(serde::Deserialize, Debug)] @@ -13,13 +13,13 @@ pub enum Val { Float(f64), String(String), Array(Vec<Val>), - Object(LinkedHashMap<String, Val>), + Object(MapImpl<String, Val>), } pub fn parse( uri: Option<&String>, text: &str, -) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { +) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { match json5_rs::from_str::<Val>(text)? { Val::String(ref value) => Err(Unexpected::Str(value.clone())), Val::Integer(value) => Err(Unexpected::Integer(value)), @@ -29,7 +29,7 @@ pub fn parse( Val::Null => Err(Unexpected::Unit), Val::Object(o) => match from_json5_value(uri, Val::Object(o)).kind { ValueKind::Table(map) => Ok(map), - _ => Ok(LinkedHashMap::new()), + _ => Ok(MapImpl::new()), }, } .map_err(|err| ConfigError::invalid_root(uri, err)) diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs index 55d1c7a..df59ca6 100644 --- a/src/file/format/mod.rs +++ b/src/file/format/mod.rs @@ -2,9 +2,9 @@ // BUG: ? For some reason this doesn't do anything if I try and function scope this #![allow(unused_mut)] -use linked_hash_map::LinkedHashMap; use std::error::Error; +use crate::map::MapImpl; use crate::value::Value; #[cfg(feature = "toml")] @@ -62,8 +62,8 @@ pub enum FileFormat { lazy_static! { #[doc(hidden)] // #[allow(unused_mut)] ? - pub static ref ALL_EXTENSIONS: LinkedHashMap<FileFormat, Vec<&'static str>> = { - let mut formats: LinkedHashMap<FileFormat, Vec<_>> = LinkedHashMap::new(); + pub static ref ALL_EXTENSIONS: MapImpl<FileFormat, Vec<&'static str>> = { + let mut formats: MapImpl<FileFormat, Vec<_>> = MapImpl::new(); #[cfg(feature = "toml")] formats.insert(FileFormat::Toml, vec!["toml"]); @@ -107,7 +107,7 @@ impl FileFormat { self, uri: Option<&String>, text: &str, - ) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { + ) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { match self { #[cfg(feature = "toml")] FileFormat::Toml => toml::parse(uri, text), diff --git a/src/file/format/ron.rs b/src/file/format/ron.rs index a527d6f..769fd53 100644 --- a/src/file/format/ron.rs +++ b/src/file/format/ron.rs @@ -1,17 +1,17 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; pub fn parse( uri: Option<&String>, text: &str, -) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { +) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { let value = from_ron_value(uri, ron::from_str(text)?)?; match value.kind { ValueKind::Table(map) => Ok(map), - _ => Ok(LinkedHashMap::new()), + _ => Ok(MapImpl::new()), } } @@ -56,7 +56,7 @@ fn from_ron_value( Ok((key, value)) }) - .collect::<Result<LinkedHashMap<_, _>, _>>()?; + .collect::<Result<MapImpl<_, _>, _>>()?; ValueKind::Table(map) } diff --git a/src/file/format/toml.rs b/src/file/format/toml.rs index 5468d97..a4e16b2 100644 --- a/src/file/format/toml.rs +++ b/src/file/format/toml.rs @@ -1,19 +1,19 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; pub fn parse( uri: Option<&String>, text: &str, -) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { +) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { // Parse a TOML value from the provided text // TODO: Have a proper error fire if the root of a file is ever not a Table let value = from_toml_value(uri, &toml::from_str(text)?); match value.kind { ValueKind::Table(map) => Ok(map), - _ => Ok(LinkedHashMap::new()), + _ => Ok(MapImpl::new()), } } @@ -25,7 +25,7 @@ fn from_toml_value(uri: Option<&String>, value: &toml::Value) -> Value { toml::Value::Boolean(value) => Value::new(uri, value), toml::Value::Table(ref table) => { - let mut m = LinkedHashMap::new(); + let mut m = MapImpl::new(); for (key, value) in table { m.insert(key.clone(), from_toml_value(uri, value)); diff --git a/src/file/format/yaml.rs b/src/file/format/yaml.rs index 246758a..f725b1f 100644 --- a/src/file/format/yaml.rs +++ b/src/file/format/yaml.rs @@ -1,16 +1,16 @@ -use linked_hash_map::LinkedHashMap; use std::error::Error; use std::fmt; use std::mem; use yaml_rust as yaml; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; pub fn parse( uri: Option<&String>, text: &str, -) -> Result<LinkedHashMap<String, Value>, Box<dyn Error + Send + Sync>> { +) -> Result<MapImpl<String, Value>, Box<dyn Error + Send + Sync>> { // Parse a YAML object from file let mut docs = yaml::YamlLoader::load_from_str(text)?; let root = match docs.len() { @@ -26,7 +26,7 @@ pub fn parse( match value.kind { ValueKind::Table(map) => Ok(map), - _ => Ok(LinkedHashMap::new()), + _ => Ok(MapImpl::new()), } } @@ -40,7 +40,7 @@ fn from_yaml_value(uri: Option<&String>, value: &yaml::Yaml) -> Value { yaml::Yaml::Integer(value) => Value::new(uri, ValueKind::Integer(value)), yaml::Yaml::Boolean(value) => Value::new(uri, ValueKind::Boolean(value)), yaml::Yaml::Hash(ref table) => { - let mut m = LinkedHashMap::new(); + let mut m = MapImpl::new(); for (key, value) in table { if let Some(k) = key.as_str() { m.insert(k.to_owned(), from_yaml_value(uri, value)); diff --git a/src/file/mod.rs b/src/file/mod.rs index 02fd51f..d5744f4 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -1,10 +1,10 @@ mod format; pub mod source; -use linked_hash_map::LinkedHashMap; use std::path::{Path, PathBuf}; use crate::error::*; +use crate::map::MapImpl; use crate::source::Source; use crate::value::Value; @@ -99,7 +99,7 @@ where Box::new((*self).clone()) } - fn collect(&self) -> Result<LinkedHashMap<String, Value>> { + fn collect(&self) -> Result<MapImpl<String, Value>> { // Coerce the file contents to a string let (uri, contents, format) = match self .source @@ -110,7 +110,7 @@ where Err(error) => { if !self.required { - return Ok(LinkedHashMap::new()); + return Ok(MapImpl::new()); } return Err(error); @@ -59,6 +59,7 @@ mod de; mod env; mod error; mod file; +mod map; mod path; mod ser; mod source; @@ -70,6 +71,7 @@ pub use crate::config::Config; pub use crate::env::Environment; pub use crate::error::ConfigError; pub use crate::file::{File, FileFormat, FileSourceFile, FileSourceString}; +pub use crate::map::MapImpl; pub use crate::source::AsyncSource; pub use crate::source::Source; pub use crate::value::Value; diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..ae41155 --- /dev/null +++ b/src/map.rs @@ -0,0 +1,4 @@ +#[cfg(not(feature = "preserve_order"))] +pub type MapImpl<K, V> = std::collections::HashMap<K, V>; +#[cfg(feature = "preserve_order")] +pub type MapImpl<K, V> = linked_hash_map::LinkedHashMap<K, V>; diff --git a/src/path/mod.rs b/src/path/mod.rs index b056467..8ce51f9 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -1,7 +1,7 @@ -use linked_hash_map::LinkedHashMap; use std::str::FromStr; use crate::error::*; +use crate::map::MapImpl; use crate::value::{Value, ValueKind}; mod parser; @@ -135,7 +135,7 @@ impl Expression { ), _ => { - *value = LinkedHashMap::<String, Value>::new().into(); |