Change parse_env error type from Debug to impl std::error::Error

This is the more appropriate trait I think and should work well for
most real world use cases.
This commit is contained in:
Lukas Kalbertodt
2022-11-06 11:23:21 +01:00
parent 1e74330f00
commit e2dded17fa
5 changed files with 50 additions and 37 deletions

View File

@@ -6,7 +6,7 @@ use confique::{
},
Config,
};
use std::{collections::HashSet, num::NonZeroU64, path::PathBuf, str::FromStr};
use std::{collections::HashSet, num::NonZeroU64, path::PathBuf, str::FromStr, convert::Infallible};
#[derive(Debug, Config)]
struct Conf {
@@ -45,10 +45,7 @@ enum Format {
Yaml,
}
#[derive(Debug)]
enum Error {}
fn parse_formats(input: &str) -> Result<Vec<Format>, Error> {
fn parse_formats(input: &str) -> Result<Vec<Format>, Infallible> {
let mut result = Vec::new();
if input.contains("toml") {

View File

@@ -156,10 +156,11 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
confique::internal::from_env(#key, #field)?
},
(None, Some(deserialize_with)) => quote! {
confique::internal::deserialize_from_env_with(#key, #field, #deserialize_with)?
confique::internal::from_env_with_deserializer(
#key, #field, #deserialize_with)?
},
(Some(parse_env), None) | (Some(parse_env), Some(_)) => quote! {
confique::internal::parse_from_env_with(#key, #field, #parse_env)?
(Some(parse_env), _) => quote! {
confique::internal::from_env_with_parser(#key, #field, #parse_env)?
},
}
}

View File

@@ -50,6 +50,13 @@ pub(crate) enum ErrorInner {
msg: String,
},
/// When a custom `parse_env` function fails.
EnvParseError {
field: String,
key: String,
err: Box<dyn std::error::Error + Send + Sync>,
},
/// Returned by the [`Source`] impls for `Path` and `PathBuf` if the file
/// extension is not supported by confique or if the corresponding Cargo
/// feature of confique was not enabled.
@@ -71,6 +78,7 @@ impl std::error::Error for Error {
ErrorInner::MissingValue(_) => None,
ErrorInner::EnvNotUnicode { .. } => None,
ErrorInner::EnvDeserialization { .. } => None,
ErrorInner::EnvParseError { err, .. } => Some(&**err),
ErrorInner::UnsupportedFileFormat { .. } => None,
ErrorInner::MissingFileExtension { .. } => None,
ErrorInner::MissingRequiredFile { .. } => None,
@@ -107,6 +115,10 @@ impl fmt::Display for Error {
std::write!(f, "failed to deserialize value `{field}` from \
environment variable `{key}`: {msg}")
}
ErrorInner::EnvParseError { field, key, err } => {
std::write!(f, "failed to parse environment variable `{key}` into \
field `{field}`: {err}")
}
ErrorInner::UnsupportedFileFormat { path } => {
std::write!(f,
"unknown configuration file format/extension: '{}'",

View File

@@ -2,8 +2,6 @@
//! intended to be used directly. None of this is covered by semver! Do not use
//! any of this directly.
use std::fmt::Debug;
use crate::{error::ErrorInner, Error};
pub fn deserialize_default<I, O>(src: I) -> Result<O, serde::de::value::Error>
@@ -38,48 +36,53 @@ pub fn map_err_prefix_path<T>(res: Result<T, Error>, prefix: &str) -> Result<T,
})
}
macro_rules! get_env_var {
($key:expr, $field:expr) => {
match std::env::var($key) {
Err(std::env::VarError::NotPresent) => return Ok(None),
Err(std::env::VarError::NotUnicode(_)) => {
let err = ErrorInner::EnvNotUnicode {
key: $key.into(),
field: $field.into(),
};
return Err(err.into());
}
Ok(s) => s,
}
};
}
pub fn from_env<'de, T: serde::Deserialize<'de>>(
key: &str,
field: &str,
) -> Result<Option<T>, Error> {
deserialize_from_env_with(key, field, |de| T::deserialize(de))
from_env_with_deserializer(key, field, |de| T::deserialize(de))
}
pub fn parse_from_env_with<T, E: Debug>(
pub fn from_env_with_parser<T, E: std::error::Error + Send + Sync + 'static>(
key: &str,
field: &str,
parse: fn(&str) -> Result<T, E>,
) -> Result<Option<T>, Error> {
from_env::<String>(key, field)?
.as_deref()
.map(parse)
.transpose()
let v = get_env_var!(key, field);
parse(&v)
.map(Some)
.map_err(|err| {
ErrorInner::EnvDeserialization {
ErrorInner::EnvParseError {
field: field.to_owned(),
key: key.to_owned(),
msg: format!("Error while parse: {:?}", err),
}
.into()
err: Box::new(err),
}.into()
})
}
pub fn deserialize_from_env_with<T>(
pub fn from_env_with_deserializer<T>(
key: &str,
field: &str,
deserialize: fn(crate::env::Deserializer) -> Result<T, crate::env::DeError>,
) -> Result<Option<T>, Error> {
let s = match std::env::var(key) {
Err(std::env::VarError::NotPresent) => return Ok(None),
Err(std::env::VarError::NotUnicode(_)) => {
let err = ErrorInner::EnvNotUnicode {
key: key.into(),
field: field.into(),
};
return Err(err.into());
}
Ok(s) => s,
};
let s = get_env_var!(key, field);
match deserialize(crate::env::Deserializer::new(s)) {
Ok(v) => Ok(Some(v)),

View File

@@ -1,4 +1,4 @@
use std::{collections::HashMap, net::IpAddr, path::PathBuf};
use std::{collections::HashMap, net::IpAddr, path::PathBuf, convert::Infallible};
use pretty_assertions::assert_eq;
use serde::Deserialize;
@@ -114,7 +114,7 @@ mod full {
optional: Option<PathBuf>,
#[config(env = "ENV_TEST_FULL_4", parse_env = parse_dummy_collection)]
env_collection: DummyCollection<','>,
env_collection: DummyCollection,
}
}
@@ -294,11 +294,11 @@ where
}
#[derive(Debug, PartialEq, Deserialize)]
struct DummyCollection<const SEPARATOR: char>(Vec<String>);
struct DummyCollection(Vec<String>);
pub(crate) fn parse_dummy_collection<const SEPARATOR: char>(input: &str) -> Result<DummyCollection<SEPARATOR>, String> {
pub(crate) fn parse_dummy_collection(input: &str) -> Result<DummyCollection, Infallible> {
Ok(DummyCollection(
input.split(SEPARATOR).map(ToString::to_string).collect(),
input.split(',').map(ToString::to_string).collect(),
))
}