lib-humus-configuration 0.2.0

Helper crate for reading configuration files into data structures using serde.
Documentation
// SPDX-FileCopyrightText: 2025 Slatian
//
// SPDX-License-Identifier: LGPL-3.0-or-later

use std::error::Error;
use std::fmt;
use std::path::PathBuf;

use crate::ConfigFormat;

/// Error type returned when reading a configuration file fails.
#[derive(Debug)]
pub struct HumusConfigError {
	/// The path of the file that caused the error
	pub path: PathBuf,
	/// What caused the error
	pub cause: ErrorCause,
}

/// Cause of a [HumusConfigError].
///
/// **⚠️ Warning:** Available variants depend on the enabled feature flags, don't do exhaustive matching on them in libraries.
#[derive(Debug)]
pub enum ErrorCause {
	/// There was a problem with reading the file itself
	FileRead(std::io::Error),

	/// There was a problem while parsing the file as JSON
	#[cfg(feature = "json")]
	Json(serde_json::Error),

	/// There was a problem while parsing the file as JSON5
	#[cfg(feature = "json5")]
	Json5(json5::Error),

	/// There was a problem parsing the file as TOML
	#[cfg(feature = "toml")]
	Toml(toml::de::Error),

	/// The file format was recognized, but isn't allowed by the settings
	FormatNotAllowed {
		/// The detected format
		format: ConfigFormat,
		/// The format you should prefer instead
		prefer: ConfigFormat,
	},
}

impl ErrorCause {
	/// Return the format that caused the error
	pub fn get_format(&self) -> Option<ConfigFormat> {
		match self {
			Self::FileRead(_) => None,
			#[cfg(feature = "json")]
			Self::Json(_) => Some(ConfigFormat::Json),
			#[cfg(feature = "json5")]
			Self::Json5(_) => Some(ConfigFormat::Json5),
			#[cfg(feature = "toml")]
			Self::Toml(_) => Some(ConfigFormat::Toml),
			Self::FormatNotAllowed { format, .. } => Some(*format),
		}
	}
}

impl fmt::Display for HumusConfigError {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		let path = &self.path;
		match &self.cause {
			ErrorCause::FileRead(io_error) => {
				write!(f, "Error while reading file {path:?}: {io_error}")
			}
			#[cfg(feature = "json")]
			ErrorCause::Json(error) => write!(f, "Unable to parse file as json {path:?}:\n{error}"),
			#[cfg(feature = "json5")]
			ErrorCause::Json5(error) => {
				write!(f, "Unable to parse file as json5 {path:?}:\n{error}")
			}
			#[cfg(feature = "toml")]
			ErrorCause::Toml(error) => write!(f, "Unable to parse file as toml {path:?}:\n{error}"),
			ErrorCause::FormatNotAllowed { format, prefer } => write!(
				f,
				"Unable to parse file {path:?}: Using {format} here is not allowed, please prefer {prefer}. "
			),
		}
	}
}

impl Error for HumusConfigError {
	fn source(&self) -> Option<&(dyn Error + 'static)> {
		match &self.cause {
			ErrorCause::FileRead(error) => Some(error),
			#[cfg(feature = "json")]
			ErrorCause::Json(error) => Some(error),
			#[cfg(feature = "json5")]
			ErrorCause::Json5(error) => Some(error),
			#[cfg(feature = "toml")]
			ErrorCause::Toml(error) => Some(error),
			ErrorCause::FormatNotAllowed { .. } => None,
		}
	}
}