use super::error::ConfigError;
use crate::helpers::*;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum Source {
Code { path: PathBuf, code: String },
File { path: PathBuf, required: bool },
#[cfg(feature = "url")]
Url { url: String },
}
impl Source {
pub fn new(value: &str, parent_source: Option<&Source>) -> Result<Source, ConfigError> {
#[cfg(feature = "url")]
if is_url_like(value) {
return Source::url(value);
}
if is_file_like(value) {
let value = if let Some(stripped) = value.strip_prefix("file://") {
stripped
} else {
value
};
return match parent_source {
None => Source::file(value, true),
Some(Source::File {
path: parent_path, ..
}) => {
let mut path = PathBuf::from(value);
if !path.has_root() {
path = parent_path.parent().unwrap().join(path);
}
Source::file(path, true)
}
Some(_) => Err(ConfigError::ExtendsFromParentFileOnly),
};
}
Err(ConfigError::ExtendsFromNoCode)
}
pub fn code<T: TryInto<String>, P: TryInto<PathBuf>>(
code: T,
path: P,
) -> Result<Source, ConfigError> {
let path: PathBuf = path.try_into().map_err(|_| ConfigError::InvalidFile)?;
let code: String = code.try_into().map_err(|_| ConfigError::InvalidCode)?;
Ok(Source::Code { path, code })
}
pub fn file<P: TryInto<PathBuf>>(path: P, required: bool) -> Result<Source, ConfigError> {
let path: PathBuf = path.try_into().map_err(|_| ConfigError::InvalidFile)?;
Ok(Source::File { path, required })
}
#[cfg(feature = "url")]
pub fn url<T: TryInto<String>>(url: T) -> Result<Source, ConfigError> {
let url: String = url.try_into().map_err(|_| ConfigError::InvalidUrl)?;
Ok(Source::Url { url })
}
pub fn get_file_ext(&self) -> Option<&str> {
match self {
Self::Code { path, .. } | Self::File { path, .. } => {
path.extension().and_then(|name| name.to_str())
}
#[cfg(feature = "url")]
Self::Url { url, .. } => extract_file_ext(url),
}
}
pub fn get_file_name(&self) -> &str {
match self {
Self::Code { path, .. } | Self::File { path, .. } => path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("unknown"),
#[cfg(feature = "url")]
Self::Url { url, .. } => extract_file_name(url),
}
}
pub fn as_str(&self) -> &str {
match self {
Source::Code { path, .. } | Source::File { path, .. } => {
path.to_str().unwrap_or_default()
}
#[cfg(feature = "url")]
Source::Url { url, .. } => url,
}
}
}
pub trait SourceFormat<T: DeserializeOwned> {
fn should_parse(&self, source: &Source) -> bool;
fn parse(
&self,
source: &Source,
content: &str,
cache_path: Option<&Path>,
) -> Result<T, ConfigError>;
}