#![deny(
warnings,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications,
missing_docs
)]
use std::path::Path;
mod internals;
mod parser;
mod value;
pub use value::Hocon;
mod error;
pub use error::Error;
pub(crate) mod helper;
mod loader_config;
pub(crate) use loader_config::*;
#[cfg(feature = "serde-support")]
mod serde;
#[cfg(feature = "serde-support")]
pub use crate::serde::de;
#[derive(Debug, Clone)]
pub struct HoconLoader {
config: HoconLoaderConfig,
internal: internals::HoconInternal,
}
impl Default for HoconLoader {
fn default() -> Self {
Self::new()
}
}
impl HoconLoader {
pub fn new() -> Self {
Self {
config: HoconLoaderConfig::default(),
internal: internals::HoconInternal::empty(),
}
}
pub fn no_system(&self) -> Self {
Self {
config: HoconLoaderConfig {
system: false,
..self.config.clone()
},
..self.clone()
}
}
#[cfg(feature = "url-support")]
pub fn no_url_include(&self) -> Self {
Self {
config: HoconLoaderConfig {
external_url: false,
..self.config.clone()
},
..self.clone()
}
}
pub fn strict(&self) -> Self {
Self {
config: HoconLoaderConfig {
strict: true,
..self.config.clone()
},
..self.clone()
}
}
pub fn max_include_depth(&self, new_max_depth: u8) -> Self {
Self {
config: HoconLoaderConfig {
max_include_depth: new_max_depth,
..self.config.clone()
},
..self.clone()
}
}
pub(crate) fn load_from_str_of_conf_file(self, s: FileRead) -> Result<Self, Error> {
Ok(Self {
internal: self.internal.add(self.config.parse_str_to_internal(s)?),
config: self.config,
})
}
pub fn load_str(self, s: &str) -> Result<Self, Error> {
self.load_from_str_of_conf_file(FileRead {
hocon: Some(String::from(s)),
..Default::default()
})
}
pub fn load_file<P: AsRef<Path>>(&self, path: P) -> Result<Self, Error> {
let mut file_path = path.as_ref().to_path_buf();
if !file_path.has_root() {
let mut current_path = std::env::current_dir().map_err(|_| Error::File {
path: String::from(path.as_ref().to_str().unwrap_or("invalid path")),
})?;
current_path.push(path.as_ref());
file_path = current_path;
}
let conf = self.config.with_file(file_path);
let contents = conf.read_file().map_err(|err| {
let path = match err {
Error::File { path } => path,
Error::Include { path } => path,
Error::Io { message } => message,
_ => "unmatched error".to_string(),
};
Error::File { path }
})?;
Self {
config: conf,
..self.clone()
}
.load_from_str_of_conf_file(contents)
}
pub fn hocon(self) -> Result<Hocon, Error> {
let config = &self.config;
self.internal.merge(config)?.finalize(config)
}
#[cfg(feature = "serde-support")]
pub fn resolve<'de, T>(self) -> Result<T, Error>
where
T: ::serde::Deserialize<'de>,
{
self.hocon()?.resolve()
}
}
#[cfg(test)]
mod tests {
use super::{ConfFileMeta, Hocon, HoconLoader, HoconLoaderConfig};
use std::path::Path;
#[test]
fn read_from_properties() {
let s = r#"a.b:c"#;
let loader = dbg!(HoconLoader {
config: HoconLoaderConfig {
file_meta: Some(ConfFileMeta::from_path(
Path::new("file.properties").to_path_buf()
)),
..Default::default()
},
..Default::default()
}
.load_str(s));
assert!(loader.is_ok());
let doc = loader.expect("during test").hocon().expect("during test");
assert_eq!(doc["a"]["b"].as_string(), Some(String::from("c")));
}
#[test]
fn read_from_hocon() {
let s = r#"a.b:c"#;
let loader = dbg!(HoconLoader {
config: HoconLoaderConfig {
file_meta: Some(ConfFileMeta::from_path(
Path::new("file.conf").to_path_buf()
)),
..Default::default()
},
..Default::default()
}
.load_str(s));
assert!(loader.is_ok());
let doc: Hocon = loader.expect("during test").hocon().expect("during test");
assert_eq!(doc["a"]["b"].as_string(), Some(String::from("c")));
}
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Simple {
#[allow(dead_code)]
int: i64,
#[allow(dead_code)]
float: f64,
#[allow(dead_code)]
option_int: Option<u64>,
}
#[derive(Deserialize, Debug)]
struct WithSubStruct {
#[allow(dead_code)]
vec_sub: Vec<Simple>,
#[allow(dead_code)]
int: i32,
#[allow(dead_code)]
float: f32,
#[allow(dead_code)]
boolean: bool,
#[allow(dead_code)]
string: String,
}
#[cfg(feature = "serde-support")]
#[test]
fn can_deserialize_struct() {
let doc = r#"{int:56, float:543.12, boolean:false, string: test,
vec_sub:[
{int:8, float:1.5, option_int:1919},
{int:8, float:0 },
{int:1, float:2, option_int:null},
]}"#;
let res: Result<WithSubStruct, _> = dbg!(HoconLoader::new().load_str(doc))
.expect("during test")
.resolve();
assert!(res.is_ok());
let res = res.expect("during test");
assert_eq!(res.int, 56);
assert_eq!(res.float, 543.12);
assert_eq!(res.boolean, false);
assert_eq!(res.string, "test");
assert_eq!(res.vec_sub[0].int, 8);
assert_eq!(res.vec_sub[0].float, 1.5);
assert_eq!(res.vec_sub[0].option_int, Some(1919));
assert_eq!(res.vec_sub[1].int, 8);
assert_eq!(res.vec_sub[1].float, 0.0);
assert_eq!(res.vec_sub[1].option_int, None);
assert_eq!(res.vec_sub[2].int, 1);
assert_eq!(res.vec_sub[2].float, 2.0);
assert_eq!(res.vec_sub[2].option_int, None);
}
#[cfg(feature = "serde-support")]
#[test]
fn can_deserialize_struct2() {
let doc = r#"{int:56, float:543.12, boolean:false, string: test,
vec_sub.1 = {int:8, float:1.5, option_int:1919},
vec_sub.5 = {int:8, float:0 },
vec_sub.8 = {int:1, float:2, option_int:null},
}"#;
let res: Result<WithSubStruct, _> = dbg!(HoconLoader::new().load_str(doc))
.expect("during test")
.resolve();
assert!(res.is_ok());
let res = res.expect("during test");
assert_eq!(res.int, 56);
assert_eq!(res.float, 543.12);
assert_eq!(res.boolean, false);
assert_eq!(res.string, "test");
assert_eq!(res.vec_sub[0].int, 8);
assert_eq!(res.vec_sub[0].float, 1.5);
assert_eq!(res.vec_sub[0].option_int, Some(1919));
assert_eq!(res.vec_sub[1].int, 8);
assert_eq!(res.vec_sub[1].float, 0.0);
assert_eq!(res.vec_sub[1].option_int, None);
assert_eq!(res.vec_sub[2].int, 1);
assert_eq!(res.vec_sub[2].float, 2.0);
assert_eq!(res.vec_sub[2].option_int, None);
}
#[cfg(feature = "serde-support")]
#[test]
fn error_deserializing_struct() {
let doc = r#"{
int:"not an int", float:543.12, boolean:false, string: test,
vec_sub:[]
}"#;
let res: Result<WithSubStruct, _> = dbg!(HoconLoader::new().load_str(doc))
.expect("during test")
.resolve();
assert!(res.is_err());
assert_eq!(
res.unwrap_err(),
super::Error::Deserialization {
message: String::from("int: Invalid type for field \"int\", expected integer")
}
);
}
#[cfg(feature = "url-support")]
#[test]
fn can_disable_url_include() {
let doc = dbg!(HoconLoader::new()
.no_url_include()
.load_file("tests/data/include_url.conf")
.unwrap()
.hocon())
.unwrap();
assert_eq!(doc["d"], Hocon::BadValue(super::Error::MissingKey));
assert_eq!(
doc["https://raw.githubusercontent.com/mockersf/hocon.rs/master/tests/data/basic.conf"],
Hocon::BadValue(
super::Error::Include {
path: String::from("https://raw.githubusercontent.com/mockersf/hocon.rs/master/tests/data/basic.conf")
}
)
);
}
}