1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
use crate::config::errors::ConfigError;
use crate::config::parser::*;
use miette::{SourceOffset, SourceSpan};
use serde::de::DeserializeOwned;
pub use crate::format::Format;
fn create_span(content: &str, line: usize, column: usize) -> SourceSpan {
let offset = SourceOffset::from_location(content, line, column).offset();
let length = 0;
(offset, length).into()
}
impl Format {
/// Detects a format from a provided value, either a file path or URL, by
/// checking for a supported file extension.
pub fn detect(value: &str) -> Result<Format, ConfigError> {
let mut available: Vec<&str> = vec![];
#[cfg(feature = "json")]
{
available.push("JSON");
if value.ends_with(".json") {
return Ok(Format::Json);
}
}
#[cfg(feature = "toml")]
{
available.push("TOML");
if value.ends_with(".toml") {
return Ok(Format::Toml);
}
}
#[cfg(feature = "yaml")]
{
available.push("YAML");
if value.ends_with(".yaml") || value.ends_with(".yml") {
return Ok(Format::Yaml);
}
}
Err(ConfigError::UnsupportedFormat(
value.to_owned(),
available.join(", "),
))
}
/// Parse the provided content in the defined format into a partial configuration struct.
/// On failure, will attempt to extract the path to the problematic field and source
/// code spans (for use in `miette`).
pub fn parse<D>(&self, content: String, _location: &str) -> Result<D, ParserError>
where
D: DeserializeOwned,
{
let data: D = match self {
Format::None => {
unreachable!();
}
#[cfg(feature = "json")]
Format::Json => {
let content = if content.is_empty() {
"{}".to_owned()
} else {
content
};
let de = &mut serde_json::Deserializer::from_str(&content);
serde_path_to_error::deserialize(de).map_err(|error| ParserError {
// content: NamedSource::new(location, content.to_owned()),
content: content.to_owned(),
path: error.path().to_string(),
span: Some(create_span(
&content,
error.inner().line(),
error.inner().column(),
)),
message: error.inner().to_string(),
})?
}
#[cfg(feature = "toml")]
Format::Toml => {
let de = toml::Deserializer::new(&content);
serde_path_to_error::deserialize(de).map_err(|error| ParserError {
// content: NamedSource::new(location, content.to_owned()),
content: content.to_owned(),
path: error.path().to_string(),
span: error.inner().span().map(|s| s.into()),
message: error.inner().message().to_owned(),
})?
}
#[cfg(feature = "yaml")]
Format::Yaml => {
use serde::de::IntoDeserializer;
// First pass, convert string to value
let de = serde_yaml::Deserializer::from_str(&content);
let mut result: serde_yaml::Value =
serde_path_to_error::deserialize(de).map_err(|error| ParserError {
// content: NamedSource::new(location, content.to_owned()),
content: content.to_owned(),
path: error.path().to_string(),
span: error
.inner()
.location()
.map(|s| create_span(&content, s.line(), s.column())),
message: error.inner().to_string(),
})?;
// Applies anchors/aliases/references
result.apply_merge().map_err(|error| ParserError {
// content: NamedSource::new(location, content.to_owned()),
content: content.to_owned(),
path: String::new(),
span: error.location().map(|s| (s.line(), s.column()).into()),
message: error.to_string(),
})?;
// Second pass, convert value to struct
let de = result.into_deserializer();
serde_path_to_error::deserialize(de).map_err(|error| ParserError {
// content: NamedSource::new(location, content.to_owned()),
content: content.to_owned(),
path: error.path().to_string(),
span: error
.inner()
.location()
.map(|s| create_span(&content, s.line(), s.column())),
message: error.inner().to_string(),
})?
}
};
Ok(data)
}
}