use crate::entity::ConfigurationEntity;
use serde::{de::IntoDeserializer, Deserialize, Deserializer, Serialize};
use std::fmt;
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
use url::Url;
pub mod closure;
#[cfg(feature = "env")]
pub mod env;
#[cfg(feature = "fs")]
pub mod fs;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{loader} configuration loader could not found {item} from URL `{url}`")]
NotFound {
loader: String,
url: Url,
item: Box<String>,
},
#[error("{loader} configuration loader has no access to load configuration from `{url}`")]
NoAccess { loader: String, url: Url },
#[error(
"{loader} configuration loader reached timeout `{timeout_in_seconds}s` to load `{url}`"
)]
Timeout {
loader: String,
url: Url,
timeout_in_seconds: usize,
},
#[error("{loader} configuration loader got invalid URL `{url}`")]
InvalidUrl {
loader: String,
url: String,
source: anyhow::Error,
},
#[error("Could not found configuration loader for scheme {scheme}")]
UrlSchemeNotFound { scheme: String },
#[error("{loader} configuration loader found duplicate configurations `{url}/{plugin}.({format_1}|{format_2})`")]
Duplicate {
loader: Box<String>,
url: Url,
plugin: Box<String>,
format_1: Box<String>,
format_2: Box<String>,
},
#[error("{loader} configuration loader could not {description} `{url}`")]
Load {
loader: String,
url: Url,
description: Box<String>,
source: anyhow::Error,
},
#[error("Could not found a loader that supports URL scheme `{scheme}` in given URL `{url}`")]
LoaderNotFound { scheme: String, url: Url },
#[error(transparent)]
Other(#[from] anyhow::Error),
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum SoftErrors<T> {
All,
List(Vec<T>),
}
struct SoftErrorsVisitor<T> {
_marker: PhantomData<T>,
}
pub trait Loader: Send + Sync + Debug + Display {
fn scheme_list(&self) -> Vec<String>;
fn load(
&self,
url: &Url,
maybe_whitelist: Option<&[String]>,
skip_soft_errors: bool,
) -> Result<Vec<(String, ConfigurationEntity)>, Error>;
}
#[cfg(feature = "qs")]
pub fn deserialize_query_string<T: serde::de::DeserializeOwned>(
loader_name: impl AsRef<str>,
url: &Url,
) -> Result<T, Error> {
serde_qs::from_str(url.query().unwrap_or_default()).map_err(|error| Error::InvalidUrl {
loader: loader_name.as_ref().to_string(),
source: error.into(),
url: url.to_string(),
})
}
impl<'de, T: Deserialize<'de>> SoftErrors<T> {
pub fn new_all() -> Self {
Self::All
}
pub fn new_list() -> Self {
Self::List(Vec::with_capacity(0))
}
pub fn skip_all(&self) -> bool {
matches!(self, Self::All)
}
pub fn add_soft_error(&mut self, soft_error: T) {
if let Self::List(soft_errors) = self {
soft_errors.push(soft_error);
}
}
pub fn with_soft_error(mut self, soft_error: T) -> Self {
self.add_soft_error(soft_error);
self
}
pub fn maybe_soft_error_list(&self) -> Option<&Vec<T>> {
if let Self::List(soft_errors) = self {
Some(soft_errors)
} else {
None
}
}
pub fn maybe_soft_error_list_mut(&mut self) -> Option<&mut Vec<T>> {
if let Self::List(soft_errors) = self {
Some(soft_errors)
} else {
None
}
}
}
impl<'de, T: Deserialize<'de> + PartialEq> SoftErrors<T> {
pub fn contains(&self, soft_error: &T) -> bool {
if let Self::List(soft_errors) = self {
soft_errors.contains(soft_error)
} else {
true
}
}
}
impl<'de, T: Deserialize<'de>> Default for SoftErrors<T> {
fn default() -> Self {
Self::new_list()
}
}
impl<'de, T> serde::de::Visitor<'de> for SoftErrorsVisitor<T>
where
T: Deserialize<'de>,
{
type Value = SoftErrors<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`all` or dot separated soft errors for configuration loader")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let parts: Vec<_> = v
.split('.')
.filter(|item| !item.is_empty())
.map(String::from)
.collect();
if parts.contains(&"all".to_string()) {
Ok(SoftErrors::All)
} else {
Ok(SoftErrors::List(Vec::deserialize(
parts.into_deserializer(),
)?))
}
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v.as_str())
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for SoftErrors<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(SoftErrorsVisitor {
_marker: PhantomData,
})
}
}