use std::path::Path;
use std::path::PathBuf;
use std::path::absolute;
use serde_json::Value as JsonValue;
use serde_yaml_ng::Value as YamlValue;
use thiserror::Error;
use wdl_engine::JsonMap;
use crate::Inputs;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error("an input file cannot be read from directory `{0}`")]
InvalidDir(PathBuf),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("input file `{0}` did not contain a map from strings to values at the root")]
NonMapRoot(PathBuf),
#[error(
"unsupported file extension `{0}`: the supported formats are JSON (`.json`) or YAML \
(`.yaml` and `.yml`)"
)]
UnsupportedFileExt(String),
#[error(transparent)]
Yaml(#[from] serde_yaml_ng::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct InputFile;
impl InputFile {
pub fn read<P: AsRef<Path>>(path: P) -> Result<Inputs> {
fn map_to_inputs(map: JsonMap, origin: &Path) -> Inputs {
let mut inputs = Inputs::default();
for (key, value) in map.iter() {
inputs.insert(key.to_owned(), (origin.to_path_buf(), value.clone()));
}
inputs
}
let path = path.as_ref();
if path.is_dir() {
return Err(Error::InvalidDir(path.to_path_buf()));
}
let content: String = std::fs::read_to_string(path)?;
let origin = absolute(path)?;
let origin = origin.parent().unwrap();
match path.extension().and_then(|ext| ext.to_str()) {
Some("json") => serde_json::from_str::<JsonValue>(&content)
.map_err(Error::from)
.and_then(|value| match value {
JsonValue::Object(object) => Ok(map_to_inputs(object, origin)),
_ => Err(Error::NonMapRoot(path.to_path_buf())),
}),
Some("yml") | Some("yaml") => serde_yaml_ng::from_str::<YamlValue>(&content)
.map_err(Error::from)
.and_then(|value| match &value {
YamlValue::Mapping(_) => {
let value = serde_json::to_value(value).unwrap();
if let JsonValue::Object(map) = value {
return Ok(map_to_inputs(map, origin));
}
unreachable!(
"a YAML mapping must always coerce to a JSON object, found `{value}`"
)
}
_ => Err(Error::NonMapRoot(path.to_path_buf())),
}),
ext => Err(Error::UnsupportedFileExt(ext.unwrap_or("").to_owned())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nonmap_root() {
let err = InputFile::read(Path::new("./tests/fixtures/nonmap_inputs.json")).unwrap_err();
assert!(matches!(
err,
Error::NonMapRoot(path) if path.to_str().unwrap() == "./tests/fixtures/nonmap_inputs.json"
));
let err = InputFile::read(Path::new("./tests/fixtures/nonmap_inputs.yml")).unwrap_err();
assert!(matches!(
err,
Error::NonMapRoot(path) if path.to_str().unwrap() == "./tests/fixtures/nonmap_inputs.yml"
));
}
}