use std::any;
use serde::de::Error as DeError;
use self::deserializer::ValueDeserializer;
pub use self::{
deserializer::DeserializerOptions,
macros::Serde,
param::{
CustomKnownOption, DeserializeParam, Optional, OrString, Qualified, Serde, WellKnown,
WellKnownOption, WithDefault,
},
repeated::{Delimited, Entries, NamedEntries, Repeated, ToEntries},
secret::{FromSecretString, Secret},
units::WithUnit,
};
use crate::{
error::{ErrorWithOrigin, LocationInConfig, LowLevelError},
metadata::{BasicTypes, ConfigMetadata, ParamMetadata},
value::{Pointer, StrValue, Value, ValueOrigin, WithOrigin},
DescribeConfig, DeserializeConfigError, ParseError, ParseErrorCategory, ParseErrors,
};
#[doc(hidden)]
pub mod _private;
mod deserializer;
mod macros;
mod param;
#[cfg(feature = "primitive-types")]
mod primitive_types_impl;
mod repeated;
mod secret;
#[cfg(test)]
mod tests;
mod units;
#[derive(Debug)]
pub struct DeserializeContext<'a> {
de_options: &'a DeserializerOptions,
root_value: &'a WithOrigin,
path: String,
patched_current_value: Option<&'a WithOrigin>,
current_config: &'static ConfigMetadata,
location_in_config: Option<LocationInConfig>,
errors: &'a mut ParseErrors,
}
impl<'a> DeserializeContext<'a> {
pub(crate) fn new(
de_options: &'a DeserializerOptions,
root_value: &'a WithOrigin,
path: String,
current_config: &'static ConfigMetadata,
errors: &'a mut ParseErrors,
) -> Self {
Self {
de_options,
root_value,
path,
patched_current_value: None,
current_config,
location_in_config: None,
errors,
}
}
fn child(
&mut self,
path: &str,
location_in_config: Option<LocationInConfig>,
) -> DeserializeContext<'_> {
DeserializeContext {
de_options: self.de_options,
root_value: self.root_value,
path: Pointer(&self.path).join(path),
patched_current_value: self.patched_current_value.and_then(|val| {
if path.is_empty() {
Some(val)
} else if let Value::Object(object) = &val.inner {
object.get(path)
} else {
None
}
}),
current_config: self.current_config,
location_in_config,
errors: self.errors,
}
}
pub fn borrow(&mut self) -> DeserializeContext<'_> {
DeserializeContext {
de_options: self.de_options,
root_value: self.root_value,
path: self.path.clone(),
patched_current_value: self.patched_current_value,
current_config: self.current_config,
location_in_config: self.location_in_config,
errors: self.errors,
}
}
fn patched<'s>(&'s mut self, current_value: &'s WithOrigin) -> DeserializeContext<'s> {
DeserializeContext {
de_options: self.de_options,
root_value: self.root_value,
path: self.path.clone(),
patched_current_value: Some(current_value),
current_config: self.current_config,
location_in_config: self.location_in_config,
errors: self.errors,
}
}
pub(crate) fn current_value(&self) -> Option<&'a WithOrigin> {
self.patched_current_value
.or_else(|| self.root_value.get(Pointer(&self.path)))
}
pub fn current_value_deserializer(
&self,
name: &'static str,
) -> Result<ValueDeserializer<'a>, ErrorWithOrigin> {
if let Some(value) = self.current_value() {
Ok(ValueDeserializer::new(value, self.de_options))
} else {
Err(DeError::missing_field(name))
}
}
fn for_nested_config(&mut self, index: usize) -> DeserializeContext<'_> {
let nested_meta = self.current_config.nested_configs.get(index).unwrap_or_else(|| {
panic!("Internal error: called `for_nested_config()` with missing config index {index}")
});
let path = nested_meta.name;
DeserializeContext {
current_config: nested_meta.meta,
..self.child(path, None)
}
}
fn for_param(&mut self, index: usize) -> (DeserializeContext<'_>, &'static ParamMetadata) {
let param = self.current_config.params.get(index).unwrap_or_else(|| {
panic!("Internal error: called `for_param()` with missing param index {index}")
});
(
self.child(param.name, Some(LocationInConfig::Param(index))),
param,
)
}
pub fn push_error(&mut self, err: ErrorWithOrigin) {
self.push_generic_error(err, None);
}
#[cold]
fn push_generic_error(&mut self, err: ErrorWithOrigin, validation: Option<String>) {
let (inner, category) = match err.inner {
LowLevelError::Json { err, category } => (err, category),
LowLevelError::InvalidArray
| LowLevelError::InvalidObject
| LowLevelError::Validation => return,
};
let mut origin = err.origin;
if matches!(origin.as_ref(), ValueOrigin::Unknown) {
if let Some(val) = self.current_value() {
origin = val.origin.clone();
}
}
self.errors.push(ParseError {
inner,
category,
path: self.path.clone(),
origin,
config: self.current_config,
location_in_config: self.location_in_config,
validation,
});
}
#[tracing::instrument(
level = "trace",
skip_all,
fields(path = self.path, config = ?self.current_config.ty)
)]
pub(crate) fn deserialize_any_config(
mut self,
) -> Result<Box<dyn any::Any>, DeserializeConfigError> {
if let Some(val) = self.current_value() {
if !matches!(&val.inner, Value::Object(_)) {
self.push_error(val.invalid_type("config object"));
return Err(DeserializeConfigError::new());
}
}
let config = (self.current_config.deserializer)(self.borrow())?;
let mut has_errors = false;
for &validation in self.current_config.validations {
let _span = tracing::trace_span!("validation", %validation).entered();
if let Err(err) = validation.validate(config.as_ref()) {
tracing::info!(%validation, origin = %err.origin, "config validation failed: {}", err.inner);
self.push_generic_error(err, Some(validation.to_string()));
has_errors = true;
}
}
if has_errors {
Err(DeserializeConfigError::new())
} else {
Ok(config)
}
}
pub(crate) fn deserialize_config<C: 'static>(self) -> Result<C, DeserializeConfigError> {
Ok(*self
.deserialize_any_config()?
.downcast::<C>()
.expect("Internal error: config deserializer output has wrong type"))
}
pub(crate) fn deserialize_any_config_opt(
mut self,
) -> Result<Option<Box<dyn any::Any>>, DeserializeConfigError> {
if self.current_value().is_none() {
return Ok(None);
}
let error_count = self.errors.len();
self.borrow()
.deserialize_any_config()
.map(Some)
.or_else(|err| {
let only_missing_field_errors = self
.errors
.iter()
.skip(error_count)
.all(|err| matches!(err.category, ParseErrorCategory::MissingField));
if only_missing_field_errors {
tracing::trace!(
"optional config misses required params and no other errors; coercing it to `None`"
);
self.errors.truncate(error_count);
Ok(None)
} else {
Err(err)
}
})
}
pub(crate) fn deserialize_config_opt<C: 'static>(
self,
) -> Result<Option<C>, DeserializeConfigError> {
let config = self.deserialize_any_config_opt()?.map(|boxed| {
*boxed
.downcast::<C>()
.expect("Internal error: config deserializer output has wrong type")
});
Ok(config)
}
}
#[doc(hidden)]
impl DeserializeContext<'_> {
pub fn deserialize_nested_config<C: DeserializeConfig>(
&mut self,
index: usize,
default_fn: Option<fn() -> C>,
) -> Result<C, DeserializeConfigError> {
let child_ctx = self.for_nested_config(index);
if child_ctx.current_value().is_none() {
if let Some(default) = default_fn {
return Ok(default());
}
}
child_ctx.deserialize_config()
}
pub fn deserialize_nested_config_opt<C: DeserializeConfig>(
&mut self,
index: usize,
) -> Result<Option<C>, DeserializeConfigError> {
self.for_nested_config(index).deserialize_config_opt()
}
#[tracing::instrument(
level = "trace",
name = "deserialize_param",
skip_all,
fields(path = self.path, config = ?self.current_config.ty, param)
)]
pub(crate) fn deserialize_any_param(
&mut self,
index: usize,
) -> Result<Box<dyn any::Any>, DeserializeConfigError> {
let (mut child_ctx, param) = self.for_param(index);
tracing::Span::current().record("param", param.rust_field_name);
let maybe_coerced = child_ctx
.current_value()
.and_then(|val| val.coerce_value_type(param.expecting));
let mut child_ctx = if let Some(coerced) = &maybe_coerced {
child_ctx.patched(coerced)
} else {
child_ctx
};
tracing::trace!(
deserializer = ?param.deserializer,
value = ?child_ctx.current_value(),
"deserializing param"
);
match param
.deserializer
.deserialize_param(child_ctx.borrow(), param)
{
Ok(param) => Ok(param),
Err(err) => {
tracing::info!(origin = %err.origin, "deserialization failed: {}", err.inner);
child_ctx.push_error(err);
Err(DeserializeConfigError::new())
}
}
}
pub fn deserialize_param<T: 'static>(
&mut self,
index: usize,
) -> Result<T, DeserializeConfigError> {
self.deserialize_any_param(index).map(|val| {
*val.downcast()
.expect("Internal error: deserializer output has wrong type")
})
}
}
impl WithOrigin {
#[tracing::instrument(level = "trace", skip(self))]
fn coerce_value_type(&self, expecting: BasicTypes) -> Option<Self> {
let Value::String(StrValue::Plain(str)) = &self.inner else {
return None; };
if !expecting.contains(BasicTypes::STRING) && (str.is_empty() || str == "null") {
return Some(Self::new(Value::Null, self.origin.clone()));
}
match expecting {
BasicTypes::BOOL => match str.parse::<bool>() {
Ok(bool_value) => {
return Some(Self::new(bool_value.into(), self.origin.clone()));
}
Err(err) => {
tracing::info!(%expecting, "failed coercing value: {err}");
}
},
BasicTypes::INTEGER | BasicTypes::FLOAT => match str.parse::<serde_json::Number>() {
Ok(number) => {
return Some(Self::new(number.into(), self.origin.clone()));
}
Err(err) => {
tracing::info!(%expecting, "failed coercing value: {err}");
}
},
_ => { }
}
None
}
}
pub trait DeserializeConfig: DescribeConfig + Sized {
fn deserialize_config(ctx: DeserializeContext<'_>) -> Result<Self, DeserializeConfigError>;
}