tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use std::collections::BTreeSet;

use serde::de::DeserializeOwned;
use serde::de::value::Error as ValueDeError;
use serde_json::Value;

use crate::error::ConfigError;
use crate::report::ConfigReport;

use super::overrides::coerce_retry_scalars;
use super::path::normalize_external_path;
use super::unknown::find_source_for_unknown_path;

mod coercing;
mod insert;

pub(super) use self::coercing::CoercingDeserializer;
pub(crate) use self::insert::insert_path;

pub(super) fn deserialize_with_path<T>(
    value: &Value,
    report: &ConfigReport,
    string_coercion_paths: &BTreeSet<String>,
) -> Result<T, ConfigError>
where
    T: DeserializeOwned,
{
    let deserialize_attempt = |value: &Value| {
        let deserializer = CoercingDeserializer::new(value, "", string_coercion_paths, None, None);
        let result: Result<T, serde_path_to_error::Error<ValueDeError>> =
            serde_path_to_error::deserialize(deserializer);
        result
    };

    match deserialize_attempt(value) {
        Ok(config) => Ok(config),
        Err(error) => {
            let retry_value = coerce_retry_scalars(value, "", string_coercion_paths);
            if retry_value != *value
                && let Ok(config) = deserialize_attempt(&retry_value)
            {
                return Ok(config);
            }
            Err(deserialization_error(report, error))
        }
    }
}

fn deserialization_error(
    report: &ConfigReport,
    error: serde_path_to_error::Error<ValueDeError>,
) -> ConfigError {
    let path = error.path().to_string();
    let lookup_path = normalize_external_path(&path);
    let source = find_source_for_unknown_path(report, &lookup_path);
    ConfigError::Deserialize {
        path,
        provenance: source,
        message: error.inner().to_string(),
    }
}