use {
crate::{
core::constants::{
configuration::{
self,
CONFIG_SECTION,
},
traits,
},
resolution::{
ImplKey,
ProjectionKey,
},
},
serde::Deserialize,
std::{
collections::{
HashMap,
HashSet,
},
sync::LazyLock,
},
};
#[derive(Debug, Clone, Deserialize)]
pub struct UserConfig {
#[serde(default)]
pub brand_mappings: HashMap<String, String>,
#[serde(default)]
pub apply_macro_aliases: HashSet<String>,
#[serde(default = "default_ignored_traits")]
pub ignored_traits: HashSet<String>,
}
impl Default for UserConfig {
fn default() -> Self {
Self {
brand_mappings: HashMap::new(),
apply_macro_aliases: HashSet::new(),
ignored_traits: default_ignored_traits(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct Config {
pub user_config: UserConfig,
pub projections: HashMap<ProjectionKey, (syn::Generics, syn::Type)>,
pub module_defaults: HashMap<String, String>,
pub scoped_defaults: HashMap<(String, String), String>,
pub concrete_types: HashSet<String>,
pub self_type_name: Option<String>,
pub impl_type_param_docs: HashMap<ImplKey, Vec<(String, String)>>,
pub signature_hashes: HashMap<u64, (String, String, String)>,
pub dispatch_traits: HashMap<String, crate::analysis::dispatch::DispatchTraitInfo>,
}
impl Config {
pub fn brand_mappings(&self) -> &HashMap<String, String> {
&self.user_config.brand_mappings
}
pub fn apply_macro_aliases(&self) -> &HashSet<String> {
&self.user_config.apply_macro_aliases
}
pub fn ignored_traits(&self) -> &HashSet<String> {
&self.user_config.ignored_traits
}
}
impl From<UserConfig> for Config {
fn from(user_config: UserConfig) -> Self {
Self {
user_config,
projections: HashMap::new(),
module_defaults: HashMap::new(),
scoped_defaults: HashMap::new(),
concrete_types: HashSet::new(),
self_type_name: None,
impl_type_param_docs: HashMap::new(),
signature_hashes: HashMap::new(),
dispatch_traits: HashMap::new(),
}
}
}
fn default_ignored_traits() -> HashSet<String> {
traits::DEFAULT_IGNORED_TRAITS.iter().map(|s| s.to_string()).collect()
}
#[derive(Debug, Deserialize)]
struct CargoMetadata {
document_signature: Option<UserConfig>,
}
#[derive(Debug, Deserialize)]
struct CargoManifest {
package: Option<PackageMetadata>,
}
#[derive(Debug, Deserialize)]
struct PackageMetadata {
metadata: Option<CargoMetadata>,
}
static USER_CONFIG_CACHE: LazyLock<UserConfig> = LazyLock::new(|| {
match load_user_config_worker() {
Ok(config) => config,
Err(ConfigLoadError::NotFound) => {
UserConfig::default()
}
Err(e) => {
eprintln!(
"warning: Failed to load [package.metadata.{CONFIG_SECTION}] configuration: {e}"
);
eprintln!(" Using default configuration instead.");
eprintln!(
" Check your Cargo.toml for syntax errors in the [package.metadata.{CONFIG_SECTION}] section."
);
UserConfig::default()
}
}
});
#[derive(Debug)]
enum ConfigLoadError {
NotFound,
IoError(std::io::Error),
TomlError(toml::de::Error),
InvalidStructure(String),
}
impl std::fmt::Display for ConfigLoadError {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match self {
ConfigLoadError::NotFound => write!(f, "configuration not found"),
ConfigLoadError::IoError(e) => write!(f, "failed to read Cargo.toml: {e}"),
ConfigLoadError::TomlError(e) => write!(f, "invalid TOML syntax: {e}"),
ConfigLoadError::InvalidStructure(msg) => {
write!(f, "invalid configuration structure: {msg}")
}
}
}
}
fn load_user_config_worker() -> Result<UserConfig, ConfigLoadError> {
let manifest_dir =
std::env::var(configuration::CARGO_MANIFEST_DIR).unwrap_or_else(|_| ".".to_string());
let manifest_path = std::path::Path::new(&manifest_dir).join(configuration::CARGO_TOML);
let content = std::fs::read_to_string(&manifest_path).map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
ConfigLoadError::NotFound
} else {
ConfigLoadError::IoError(e)
}
})?;
let manifest: CargoManifest = toml::from_str(&content).map_err(ConfigLoadError::TomlError)?;
let package = manifest.package.ok_or_else(|| {
ConfigLoadError::InvalidStructure("missing [package] section".to_string())
})?;
let metadata = package.metadata.ok_or(ConfigLoadError::NotFound)?;
let user_config = metadata.document_signature.ok_or(ConfigLoadError::NotFound)?;
Ok(user_config)
}
pub fn load_user_config() -> UserConfig {
USER_CONFIG_CACHE.clone()
}
pub fn load_config() -> Config {
load_user_config().into()
}
#[inline]
pub fn get_config() -> Config {
load_config()
}