#![cfg_attr(docsrs, feature(doc_cfg))]
use serde::{Serialize, de::DeserializeOwned};
#[allow(unused_imports)]
use serde_path_to_error::{Error as PathError, deserialize as depath, serialize as serpath};
use std::fs::File;
use std::io::{self, Write};
use std::path::Path;
use strum::{Display, EnumIter, EnumString};
use thiserror::Error;
#[cfg(feature = "ron")]
use ron::ser::PrettyConfig;
#[derive(
Clone, Copy, Debug, Display, EnumIter, EnumString, Eq, Hash, Ord, PartialEq, PartialOrd,
)]
#[strum(ascii_case_insensitive, serialize_all = "UPPERCASE")]
#[non_exhaustive]
pub enum Format {
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
Json,
#[cfg(feature = "json5")]
#[cfg_attr(docsrs, doc(cfg(feature = "json5")))]
Json5,
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
Ron,
#[cfg(feature = "toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
Toml,
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
Yaml,
}
impl Format {
pub fn iter() -> FormatIter {
<Format as strum::IntoEnumIterator>::iter()
}
#[cfg_attr(all(feature = "json", feature = "yaml"), doc = concat!(
"# Example\n",
"\n",
"```\n",
"use cfgfifo::Format;\n",
"\n",
"assert_eq!(Format::Json.extensions(), &[\"json\"]);\n",
"assert_eq!(Format::Yaml.extensions(), &[\"yaml\", \"yml\"]);\n",
"```\n",
))]
pub fn extensions(&self) -> &'static [&'static str] {
match self {
#[cfg(feature = "json")]
Format::Json => &["json"],
#[cfg(feature = "json5")]
Format::Json5 => &["json5"],
#[cfg(feature = "ron")]
Format::Ron => &["ron"],
#[cfg(feature = "toml")]
Format::Toml => &["toml"],
#[cfg(feature = "yaml")]
Format::Yaml => &["yaml", "yml"],
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
#[cfg_attr(feature = "json", doc = concat!(
"# Example\n",
"\n",
"```\n",
"use cfgfifo::Format;\n",
"\n",
"assert!(Format::Json.has_extension(\".json\"));\n",
"assert!(Format::Json.has_extension(\"JSON\"));\n",
"assert!(!Format::Json.has_extension(\"cfg\"));\n",
"```\n",
))]
pub fn has_extension(&self, ext: &str) -> bool {
let ext = ext.strip_prefix('.').unwrap_or(ext);
self.extensions()
.iter()
.any(|x| x.eq_ignore_ascii_case(ext))
}
#[cfg_attr(all(feature = "json", feature = "yaml"), doc = concat!(
"# Example\n",
"\n",
"```\n",
"use cfgfifo::Format;\n",
"\n",
"assert_eq!(Format::from_extension(\".json\"), Some(Format::Json));\n",
"assert_eq!(Format::from_extension(\"YML\"), Some(Format::Yaml));\n",
"assert_eq!(Format::from_extension(\"cfg\"), None);\n",
"```\n",
))]
pub fn from_extension(ext: &str) -> Option<Format> {
Format::iter().find(|f| f.has_extension(ext))
}
#[cfg_attr(all(feature = "json", feature = "ron"), doc = concat!(
"# Example\n",
"\n",
"```\n",
"use cfgfifo::Format;\n",
"\n",
"assert_eq!(Format::identify(\"path/to/file.json\").unwrap(), Format::Json);\n",
"assert_eq!(Format::identify(\"path/to/file.RON\").unwrap(), Format::Ron);\n",
"assert!(Format::identify(\"path/to/file.cfg\").is_err());\n",
"assert!(Format::identify(\"path/to/file\").is_err());\n",
"```\n",
))]
pub fn identify<P: AsRef<Path>>(path: P) -> Result<Format, IdentifyError> {
let ext = get_ext(path.as_ref())?;
Format::from_extension(ext).ok_or_else(|| IdentifyError::Unknown(ext.to_owned()))
}
#[cfg_attr(feature = "json", doc = concat!(
"# Example\n",
"\n",
"```\n",
"use cfgfifo::Format;\n",
"use serde::Serialize;\n",
"\n",
"#[derive(Clone, Debug, Eq, PartialEq, Serialize)]\n",
"struct Data {\n",
" name: String,\n",
" size: u32,\n",
" enabled: bool,\n",
"}\n",
"\n",
"let datum = Data {\n",
" name: String::from(\"Example\"),\n",
" size: 42,\n",
" enabled: true,\n",
"};\n",
"\n",
"let s = Format::Json.dump_to_string(&datum).unwrap();\n",
"\n",
"assert_eq!(\n",
" s,\n",
" concat!(\n",
" \"{\\n\",\n",
" \" \\\"name\\\": \\\"Example\\\",\\n\",\n",
" \" \\\"size\\\": 42,\\n\",\n",
" \" \\\"enabled\\\": true\\n\",\n",
" \"}\"\n",
" )\n",
");\n",
"```\n",
))]
#[allow(unused_variables)]
pub fn dump_to_string<T: Serialize>(&self, value: &T) -> Result<String, SerializeError> {
match self {
#[cfg(feature = "json")]
Format::Json => {
let mut buffer = Vec::new();
let mut ser = serde_json::Serializer::pretty(&mut buffer);
serpath(value, &mut ser)?;
let Ok(s) = String::from_utf8(buffer) else {
unreachable!("serialized JSON should be valid UTF-8");
};
Ok(s)
}
#[cfg(feature = "json5")]
Format::Json5 => {
let mut buffer = Vec::new();
let mut ser = serde_json::Serializer::pretty(&mut buffer);
serpath(value, &mut ser)?;
let Ok(s) = String::from_utf8(buffer) else {
unreachable!("serialized JSON should be valid UTF-8");
};
Ok(s)
}
#[cfg(feature = "ron")]
Format::Ron => {
let mut buffer = String::new();
let mut ser = ron::Serializer::new(&mut buffer, Some(ron_config()))
.map_err(SerializeError::RonStart)?;
serpath(value, &mut ser)?;
Ok(buffer)
}
#[cfg(feature = "toml")]
Format::Toml => {
let mut buff = toml::ser::Buffer::new();
let ser = toml::Serializer::pretty(&mut buff);
serpath(value, ser)?;
Ok(buff.to_string())
}
#[cfg(feature = "yaml")]
Format::Yaml => {
let mut buffer = Vec::new();
self.dump_to_writer(&mut buffer, value)?;
let Ok(s) = String::from_utf8(buffer) else {
unreachable!("serialized YAML should be valid UTF-8");
};
Ok(s)
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
#[cfg_attr(feature = "yaml", doc = concat!(
"# Example\n",
"\n",
"```\n",
"use cfgfifo::Format;\n",
"use serde::Deserialize;\n",
"\n",
"#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]\n",
"struct Data {\n",
" name: String,\n",
" size: u32,\n",
" enabled: bool,\n",
"}\n",
"\n",
"let s = concat!(\n",
" \"name: Example\\n\",\n",
" \"size: 42\\n\",\n",
" \"enabled: true\\n\",\n",
");\n",
"\n",
"let datum: Data = Format::Yaml.load_from_str(s).unwrap();\n",
"\n",
"assert_eq!(\n",
" datum,\n",
" Data {\n",
" name: String::from(\"Example\"),\n",
" size: 42,\n",
" enabled: true,\n",
" }\n",
");\n",
"```\n",
))]
#[allow(unused_variables)]
pub fn load_from_str<T: DeserializeOwned>(&self, s: &str) -> Result<T, DeserializeError> {
match self {
#[cfg(feature = "json")]
Format::Json => {
let mut de = serde_json::Deserializer::from_str(s);
let value = depath(&mut de)?;
de.end().map_err(DeserializeError::JsonEnd)?;
Ok(value)
}
#[cfg(feature = "json5")]
Format::Json5 => {
let mut de = json5::Deserializer::from_str(s);
depath(&mut de).map_err(Into::into)
}
#[cfg(feature = "ron")]
Format::Ron => {
let mut de = ron::Deserializer::from_str(s).map_err(DeserializeError::RonStart)?;
let value = match depath(&mut de) {
Ok(value) => value,
Err(e) => {
let path = e.path().clone();
let inner = e.into_inner();
let ron_e = de.span_error(inner);
return Err(PathError::new(path, ron_e).into());
}
};
de.end()
.map_err(|e| DeserializeError::RonEnd(de.span_error(e)))?;
Ok(value)
}
#[cfg(feature = "toml")]
Format::Toml => {
let de = toml::Deserializer::parse(s).map_err(DeserializeError::TomlParse)?;
depath(de).map_err(Into::into)
}
#[cfg(feature = "yaml")]
Format::Yaml => {
let de = serde_yaml::Deserializer::from_str(s);
depath(de).map_err(Into::into)
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
#[allow(unused_mut, unused_variables)]
pub fn dump_to_writer<W: Write, T: Serialize>(
&self,
mut writer: W,
value: &T,
) -> Result<(), SerializeError> {
match self {
#[cfg(feature = "json")]
Format::Json => {
let mut ser = serde_json::Serializer::pretty(&mut writer);
serpath(value, &mut ser)?;
writer.write_all(b"\n")?;
Ok(())
}
#[cfg(feature = "json5")]
Format::Json5 => {
let mut ser = serde_json::Serializer::pretty(&mut writer);
serpath(value, &mut ser)?;
writer.write_all(b"\n")?;
Ok(())
}
#[cfg(feature = "ron")]
Format::Ron => {
struct Adapter<W: Write> {
writer: W,
error: io::Result<()>,
}
impl<T: Write> std::fmt::Write for Adapter<T> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
match self.writer.write_all(s.as_bytes()) {
Ok(()) => Ok(()),
Err(e) => {
self.error = Err(e);
Err(std::fmt::Error)
}
}
}
}
let mut adapter = Adapter {
writer,
error: Ok(()),
};
let mut ser = ron::Serializer::new(&mut adapter, Some(ron_config()))
.map_err(SerializeError::RonStart)?;
serpath(value, &mut ser)?;
adapter.writer.write_all(b"\n")?;
Ok(())
}
#[cfg(feature = "toml")]
Format::Toml => {
let s = self.dump_to_string(value)?;
writer.write_all(s.as_bytes())?;
Ok(())
}
#[cfg(feature = "yaml")]
Format::Yaml => {
let mut ser = serde_yaml::Serializer::new(writer);
serpath(value, &mut ser).map_err(Into::into)
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
#[allow(unused_variables)]
pub fn load_from_reader<R: io::Read, T: DeserializeOwned>(
&self,
reader: R,
) -> Result<T, DeserializeError> {
match self {
#[cfg(feature = "json")]
Format::Json => {
let mut de = serde_json::Deserializer::from_reader(reader);
let value = depath(&mut de)?;
de.end().map_err(DeserializeError::JsonEnd)?;
Ok(value)
}
#[cfg(feature = "json5")]
Format::Json5 => {
let s = io::read_to_string(reader)?;
self.load_from_str(&s)
}
#[cfg(feature = "ron")]
Format::Ron => {
let s = io::read_to_string(reader)?;
self.load_from_str(&s)
}
#[cfg(feature = "toml")]
Format::Toml => {
let s = io::read_to_string(reader)?;
self.load_from_str(&s)
}
#[cfg(feature = "yaml")]
Format::Yaml => {
let de = serde_yaml::Deserializer::from_reader(reader);
depath(de).map_err(Into::into)
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
pub fn load<T: DeserializeOwned, P: AsRef<Path>>(path: P) -> Result<T, LoadError> {
Cfgfifo::default().load(path)
}
pub fn dump<P: AsRef<Path>, T: Serialize>(path: P, value: &T) -> Result<(), DumpError> {
Cfgfifo::default().dump(path, value)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Cfgfifo {
formats: Vec<Format>,
fallback: Option<Format>,
}
impl Cfgfifo {
pub fn new() -> Cfgfifo {
Cfgfifo {
formats: Format::iter().collect(),
fallback: None,
}
}
pub fn formats<I: IntoIterator<Item = Format>>(mut self, iter: I) -> Self {
self.formats = iter.into_iter().collect();
self
}
pub fn fallback(mut self, fallback: Option<Format>) -> Self {
self.fallback = fallback;
self
}
#[cfg_attr(all(feature = "json", feature = "yaml"), doc = concat!(
"# Example\n",
"\n",
"```\n",
"use cfgfifo::{Cfgfifo, Format};\n",
"\n",
"let cfgfifo = Cfgfifo::new()\n",
" .formats([Format::Json, Format::Yaml])\n",
" .fallback(Some(Format::Json));\n",
"\n",
"assert_eq!(cfgfifo.identify(\"path/to/file.json\").unwrap(), Format::Json);\n",
"assert_eq!(cfgfifo.identify(\"path/to/file.YML\").unwrap(), Format::Yaml);\n",
"assert_eq!(cfgfifo.identify(\"path/to/file.ron\").unwrap(), Format::Json);\n",
"assert_eq!(cfgfifo.identify(\"path/to/file.cfg\").unwrap(), Format::Json);\n",
"assert_eq!(cfgfifo.identify(\"path/to/file\").unwrap(), Format::Json);\n",
"```\n",
))]
pub fn identify<P: AsRef<Path>>(&self, path: P) -> Result<Format, IdentifyError> {
let ext = match (get_ext(path.as_ref()), self.fallback) {
(Ok(ext), _) => ext,
#[allow(unreachable_patterns)]
(Err(_), Some(f)) => return Ok(f),
(Err(e), _) => return Err(e),
};
self.formats
.iter()
.find(|f| f.has_extension(ext))
.copied()
.or(self.fallback)
.ok_or_else(|| IdentifyError::Unknown(ext.to_owned()))
}
pub fn load<T: DeserializeOwned, P: AsRef<Path>>(&self, path: P) -> Result<T, LoadError> {
let fmt = self.identify(&path)?;
let fp = io::BufReader::new(File::open(path).map_err(LoadError::Open)?);
fmt.load_from_reader(fp).map_err(Into::into)
}
pub fn dump<P: AsRef<Path>, T: Serialize>(&self, path: P, value: &T) -> Result<(), DumpError> {
let fmt = self.identify(&path)?;
let mut fp = io::BufWriter::new(File::create(path).map_err(DumpError::Open)?);
fmt.dump_to_writer(&mut fp, value)?;
fp.flush().map_err(DumpError::Flush)
}
}
impl Default for Cfgfifo {
fn default() -> Cfgfifo {
Cfgfifo::new()
}
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum IdentifyError {
#[error("unknown file extension: {0:?}")]
Unknown(
String,
),
#[error("file extension is not valid Unicode")]
NotUnicode,
#[error("file does not have a file extension")]
NoExtension,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum SerializeError {
#[error(transparent)]
Io(#[from] io::Error),
#[cfg(any(feature = "json", feature = "json5"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "json", feature = "json5"))))]
#[error(transparent)]
Json(#[from] PathError<serde_json::Error>),
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
#[error(transparent)]
RonStart(ron::error::Error),
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
#[error(transparent)]
Ron(#[from] PathError<ron::error::Error>),
#[cfg(feature = "toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
#[error(transparent)]
Toml(#[from] PathError<toml::ser::Error>),
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
#[error(transparent)]
Yaml(#[from] PathError<serde_yaml::Error>),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DeserializeError {
#[error(transparent)]
Io(#[from] io::Error),
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
#[error(transparent)]
Json(#[from] PathError<serde_json::Error>),
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
#[error(transparent)]
JsonEnd(serde_json::Error),
#[cfg(feature = "json5")]
#[cfg_attr(docsrs, doc(cfg(feature = "json5")))]
#[error(transparent)]
Json5(#[from] PathError<json5::Error>),
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
#[error(transparent)]
RonStart(ron::error::SpannedError),
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
#[error(transparent)]
Ron(Box<PathError<ron::error::SpannedError>>),
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
#[error(transparent)]
RonEnd(ron::error::SpannedError),
#[cfg(feature = "toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
#[error(transparent)]
TomlParse(toml::de::Error),
#[cfg(feature = "toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
#[error(transparent)]
Toml(#[from] PathError<toml::de::Error>),
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
#[error(transparent)]
Yaml(#[from] PathError<serde_yaml::Error>),
}
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
impl From<PathError<ron::error::SpannedError>> for DeserializeError {
fn from(e: PathError<ron::error::SpannedError>) -> DeserializeError {
DeserializeError::Ron(Box::new(e))
}
}
#[derive(Debug, Error)]
pub enum LoadError {
#[error("failed to identify file format")]
Identify(#[from] IdentifyError),
#[error("failed to open file for reading")]
Open(#[source] io::Error),
#[error("failed to deserialize file contents")]
Deserialize(#[from] DeserializeError),
}
#[derive(Debug, Error)]
pub enum DumpError {
#[error("failed to identify file format")]
Identify(#[from] IdentifyError),
#[error("failed to open file for writing")]
Open(#[source] io::Error),
#[error("failed to serialize structure")]
Serialize(#[from] SerializeError),
#[error("failed to flush output file")]
Flush(#[source] io::Error),
}
#[cfg(feature = "ron")]
fn ron_config() -> PrettyConfig {
PrettyConfig::default().new_line(String::from("\n"))
}
fn get_ext(path: &Path) -> Result<&str, IdentifyError> {
path.extension()
.ok_or(IdentifyError::NoExtension)?
.to_str()
.ok_or(IdentifyError::NotUnicode)
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case("file.ini", "ini")]
#[case("file.xml", "xml")]
#[case("file.cfg", "cfg")]
#[case("file.jsn", "jsn")]
#[case("file.tml", "tml")]
fn identify_unknown(#[case] path: &str, #[case] ext: String) {
assert_eq!(
Format::identify(path),
Err(IdentifyError::Unknown(ext.clone()))
);
assert_eq!(
Cfgfifo::default().identify(path),
Err(IdentifyError::Unknown(ext))
);
}
#[cfg(unix)]
#[test]
fn identify_not_unicode() {
use std::os::unix::ffi::OsStrExt;
let path = std::ffi::OsStr::from_bytes(b"file.js\xF6n");
assert_eq!(Format::identify(path), Err(IdentifyError::NotUnicode));
assert_eq!(
Cfgfifo::default().identify(path),
Err(IdentifyError::NotUnicode)
);
}
#[cfg(windows)]
#[test]
fn identify_not_unicode() {
use std::os::windows::ffi::OsStringExt;
let path = std::ffi::OsString::from_wide(&[
0x66, 0x69, 0x6C, 0x65, 0x2E, 0x6A, 0xDC00, 0x73, 0x6E,
]);
assert_eq!(Format::identify(&path), Err(IdentifyError::NotUnicode));
assert_eq!(
Cfgfifo::default().identify(path),
Err(IdentifyError::NotUnicode)
);
}
#[test]
fn identify_no_ext() {
assert_eq!(Format::identify("file"), Err(IdentifyError::NoExtension));
assert_eq!(
Cfgfifo::default().identify("file"),
Err(IdentifyError::NoExtension)
);
}
#[cfg(feature = "json")]
mod json {
use super::*;
#[test]
fn basics() {
let f = Format::Json;
assert_eq!(f.to_string(), "JSON");
assert_eq!(f.extensions(), ["json"]);
assert_eq!("json".parse::<Format>().unwrap(), f);
assert_eq!("JSON".parse::<Format>().unwrap(), f);
assert_eq!("Json".parse::<Format>().unwrap(), f);
assert!(Format::iter().any(|f2| f == f2));
}
#[rstest]
#[case("json")]
#[case(".json")]
#[case("JSON")]
#[case(".JSON")]
fn from_extension(#[case] ext: &str) {
assert!(Format::Json.has_extension(ext));
assert_eq!(Format::from_extension(ext).unwrap(), Format::Json);
}
#[rstest]
#[case("file.json")]
#[case("dir/file.JSON")]
#[case("/dir/file.Json")]
fn identify(#[case] path: &str) {
assert_eq!(Format::identify(path).unwrap(), Format::Json);
}
}
#[cfg(not(feature = "json"))]
mod not_json {
use super::*;
#[test]
fn not_variant() {
assert!(!Format::iter().any(|f| f.to_string() == "JSON"));
}
#[test]
fn identify() {
assert_eq!(
Format::identify("file.json"),
Err(IdentifyError::Unknown(String::from("json")))
);
}
}
#[cfg(feature = "json5")]
mod json5 {
use super::*;
#[test]
fn basics() {
let f = Format::Json5;
assert_eq!(f.to_string(), "JSON5");
assert_eq!(f.extensions(), ["json5"]);
assert_eq!("json5".parse::<Format>().unwrap(), f);
assert_eq!("JSON5".parse::<Format>().unwrap(), f);
assert_eq!("Json5".parse::<Format>().unwrap(), f);
assert!(Format::iter().any(|f2| f == f2));
}
#[rstest]
#[case("json5")]
#[case(".json5")]
#[case("JSON5")]
#[case(".JSON5")]
fn from_extension(#[case] ext: &str) {
assert!(Format::Json5.has_extension(ext));
assert_eq!(Format::from_extension(ext).unwrap(), Format::Json5);
}
#[rstest]
#[case("file.json5")]
#[case("dir/file.JSON5")]
#[case("/dir/file.Json5")]
fn identify(#[case] path: &str) {
assert_eq!(Format::identify(path).unwrap(), Format::Json5);
}
}
#[cfg(not(feature = "json5"))]
mod not_json5 {
use super::*;
#[test]
fn not_variant() {
assert!(!Format::iter().any(|f| f.to_string() == "JSON5"));
}
#[test]
fn identify() {
assert_eq!(
Format::identify("file.json5"),
Err(IdentifyError::Unknown(String::from("json5")))
);
}
}
#[cfg(feature = "ron")]
mod ron {
use super::*;
#[test]
fn basics() {
let f = Format::Ron;
assert_eq!(f.to_string(), "RON");
assert_eq!(f.extensions(), ["ron"]);
assert_eq!("ron".parse::<Format>().unwrap(), f);
assert_eq!("RON".parse::<Format>().unwrap(), f);
assert_eq!("Ron".parse::<Format>().unwrap(), f);
assert!(Format::iter().any(|f2| f == f2));
}
#[rstest]
#[case("ron")]
#[case(".ron")]
#[case("RON")]
#[case(".RON")]
fn from_extension(#[case] ext: &str) {
assert!(Format::Ron.has_extension(ext));
assert_eq!(Format::from_extension(ext).unwrap(), Format::Ron);
}
#[rstest]
#[case("file.ron")]
#[case("dir/file.RON")]
#[case("/dir/file.Ron")]
fn identify(#[case] path: &str) {
assert_eq!(Format::identify(path).unwrap(), Format::Ron);
}
}
#[cfg(not(feature = "ron"))]
mod not_ron {
use super::*;
#[test]
fn not_variant() {
assert!(!Format::iter().any(|f| f.to_string() == "RON"));
}
#[test]
fn identify() {
assert_eq!(
Format::identify("file.ron"),
Err(IdentifyError::Unknown(String::from("ron")))
);
}
}
#[cfg(feature = "toml")]
mod toml {
use super::*;
#[test]
fn basics() {
let f = Format::Toml;
assert_eq!(f.to_string(), "TOML");
assert_eq!(f.extensions(), ["toml"]);
assert_eq!("toml".parse::<Format>().unwrap(), f);
assert_eq!("TOML".parse::<Format>().unwrap(), f);
assert_eq!("Toml".parse::<Format>().unwrap(), f);
assert!(Format::iter().any(|f2| f == f2));
}
#[rstest]
#[case("toml")]
#[case(".toml")]
#[case("TOML")]
#[case(".TOML")]
fn from_extension(#[case] ext: &str) {
assert!(Format::Toml.has_extension(ext));
assert_eq!(Format::from_extension(ext).unwrap(), Format::Toml);
}
#[rstest]
#[case("file.toml")]
#[case("dir/file.TOML")]
#[case("/dir/file.Toml")]
fn identify(#[case] path: &str) {
assert_eq!(Format::identify(path).unwrap(), Format::Toml);
}
}
#[cfg(not(feature = "toml"))]
mod not_toml {
use super::*;
#[test]
fn not_variant() {
assert!(!Format::iter().any(|f| f.to_string() == "TOML"));
}
#[test]
fn identify() {
assert_eq!(
Format::identify("file.toml"),
Err(IdentifyError::Unknown(String::from("toml")))
);
}
}
#[cfg(feature = "yaml")]
mod yaml {
use super::*;
#[test]
fn basics() {
let f = Format::Yaml;
assert_eq!(f.to_string(), "YAML");
assert_eq!(f.extensions(), ["yaml", "yml"]);
assert_eq!("yaml".parse::<Format>().unwrap(), f);
assert_eq!("YAML".parse::<Format>().unwrap(), f);
assert_eq!("Yaml".parse::<Format>().unwrap(), f);
assert!(Format::iter().any(|f2| f == f2));
}
#[rstest]
#[case("yaml")]
#[case(".yaml")]
#[case("YAML")]
#[case(".YAML")]
#[case("yml")]
#[case(".yml")]
#[case("YML")]
#[case(".YML")]
fn from_extension(#[case] ext: &str) {
assert!(Format::Yaml.has_extension(ext));
assert_eq!(Format::from_extension(ext).unwrap(), Format::Yaml);
}
#[rstest]
#[case("file.yaml")]
#[case("dir/file.YAML")]
#[case("/dir/file.Yaml")]
#[case("file.yml")]
#[case("dir/file.YML")]
#[case("/dir/file.Yml")]
fn identify(#[case] path: &str) {
assert_eq!(Format::identify(path).unwrap(), Format::Yaml);
}
}
#[cfg(not(feature = "yaml"))]
mod not_yaml {
use super::*;
#[test]
fn not_variant() {
assert!(!Format::iter().any(|f| f.to_string() == "YAML"));
}
#[test]
fn identify() {
assert_eq!(
Format::identify("file.yaml"),
Err(IdentifyError::Unknown(String::from("yaml")))
);
}
}
mod cfgfifo {
#[allow(unused_imports)]
use super::*;
#[cfg(all(
feature = "json",
feature = "json5",
feature = "ron",
feature = "toml",
feature = "yaml"
))]
#[test]
fn default() {
let cfg = Cfgfifo::default();
assert_eq!(cfg.identify("file.json").unwrap(), Format::Json);
assert_eq!(cfg.identify("file.json5").unwrap(), Format::Json5);
assert_eq!(cfg.identify("file.Ron").unwrap(), Format::Ron);
assert_eq!(cfg.identify("file.toml").unwrap(), Format::Toml);
assert_eq!(cfg.identify("file.YML").unwrap(), Format::Yaml);
assert!(cfg.identify("file.cfg").is_err());
assert!(cfg.identify("file").is_err());
}
#[cfg(all(
feature = "json",
feature = "json5",
feature = "ron",
feature = "toml",
feature = "yaml"
))]
#[test]
fn fallback() {
let cfg = Cfgfifo::new().fallback(Some(Format::Json));
assert_eq!(cfg.identify("file.json").unwrap(), Format::Json);
assert_eq!(cfg.identify("file.json5").unwrap(), Format::Json5);
assert_eq!(cfg.identify("file.Ron").unwrap(), Format::Ron);
assert_eq!(cfg.identify("file.toml").unwrap(), Format::Toml);
assert_eq!(cfg.identify("file.YML").unwrap(), Format::Yaml);
assert_eq!(cfg.identify("file.cfg").unwrap(), Format::Json);
assert_eq!(cfg.identify("file").unwrap(), Format::Json);
}
#[cfg(all(feature = "json", feature = "toml"))]
#[test]
fn formats() {
let cfg = Cfgfifo::new().formats([Format::Json, Format::Toml]);
assert_eq!(cfg.identify("file.json").unwrap(), Format::Json);
assert!(cfg.identify("file.json5").is_err());
assert!(cfg.identify("file.Ron").is_err());
assert_eq!(cfg.identify("file.toml").unwrap(), Format::Toml);
assert!(cfg.identify("file.YML").is_err());
assert!(cfg.identify("file.cfg").is_err());
assert!(cfg.identify("file").is_err());
}
#[cfg(all(feature = "json", feature = "toml", feature = "yaml"))]
#[test]
fn formats_fallback() {
let cfg = Cfgfifo::new()
.formats([Format::Json, Format::Toml])
.fallback(Some(Format::Yaml));
assert_eq!(cfg.identify("file.json").unwrap(), Format::Json);
assert_eq!(cfg.identify("file.json5").unwrap(), Format::Yaml);
assert_eq!(cfg.identify("file.Ron").unwrap(), Format::Yaml);
assert_eq!(cfg.identify("file.toml").unwrap(), Format::Toml);
assert_eq!(cfg.identify("file.YML").unwrap(), Format::Yaml);
assert_eq!(cfg.identify("file.cfg").unwrap(), Format::Yaml);
assert_eq!(cfg.identify("file").unwrap(), Format::Yaml);
}
}
}