use std::collections::HashMap;
use std::sync::LazyLock;
use serde::Deserialize;
const DECLARED_LICENSE_ALIASES_TEXT: &str =
include_str!("../../resources/license_detection/declared_license_aliases.toml");
#[derive(Debug, Deserialize)]
struct AliasFile {
#[serde(default)]
alias: Vec<AliasEntry>,
}
#[derive(Debug, Deserialize)]
struct AliasEntry {
names: Vec<String>,
expression: String,
}
static DECLARED_LICENSE_ALIASES: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
let parsed: AliasFile = toml::from_str(DECLARED_LICENSE_ALIASES_TEXT)
.expect("declared_license_aliases.toml must be valid TOML");
let mut map = HashMap::new();
for entry in parsed.alias {
for name in entry.names {
map.insert(name.trim().to_ascii_lowercase(), entry.expression.clone());
}
}
map
});
pub(crate) fn declared_license_alias(statement: &str) -> Option<&'static str> {
let key = statement.trim().to_ascii_lowercase();
DECLARED_LICENSE_ALIASES.get(&key).map(String::as_str)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn loads_and_indexes_aliases() {
assert_eq!(declared_license_alias("apache"), Some("apache-2.0"));
assert_eq!(declared_license_alias("Apache"), Some("apache-2.0"));
assert_eq!(declared_license_alias(" APACHE "), Some("apache-2.0"));
assert_eq!(declared_license_alias("boost"), Some("boost-1.0"));
assert_eq!(declared_license_alias("PSF"), Some("python"));
assert_eq!(declared_license_alias("Python"), Some("python"));
assert_eq!(declared_license_alias("BSD"), Some("bsd-new"));
assert_eq!(declared_license_alias("bsd"), Some("bsd-new"));
assert_eq!(declared_license_alias("BSD-style"), Some("bsd-new"));
assert_eq!(declared_license_alias("BSD style"), Some("bsd-new"));
}
#[test]
fn unaliased_names_return_none() {
assert_eq!(declared_license_alias("LGPL"), None);
assert_eq!(declared_license_alias("MPL"), None);
assert_eq!(declared_license_alias("EPL"), None);
assert_eq!(declared_license_alias("Apache License, Version 2.0"), None);
assert_eq!(declared_license_alias(""), None);
assert_eq!(declared_license_alias("BSD with advertising"), None);
assert_eq!(declared_license_alias("BSD-style sockets API"), None);
}
#[test]
fn every_alias_entry_has_a_nonempty_reason() {
let value: toml::Value =
toml::from_str(DECLARED_LICENSE_ALIASES_TEXT).expect("aliases TOML parses");
let aliases = value
.get("alias")
.and_then(toml::Value::as_array)
.expect("at least one [[alias]] entry");
assert!(!aliases.is_empty());
for entry in aliases {
let names = entry.get("names").and_then(toml::Value::as_array);
assert!(
names.is_some_and(|n| !n.is_empty()),
"every alias needs at least one name: {entry:?}"
);
assert!(
entry
.get("expression")
.and_then(toml::Value::as_str)
.is_some(),
"every alias needs an expression: {entry:?}"
);
let reason = entry.get("reason").and_then(toml::Value::as_str);
assert!(
reason.is_some_and(|r| r.trim().len() >= 20),
"every alias needs a substantive `reason`: {entry:?}"
);
}
}
#[test]
fn every_alias_expression_resolves_in_the_license_index() {
for expression in DECLARED_LICENSE_ALIASES.values() {
assert!(
crate::parsers::license_normalization::normalize_declared_license_key(expression)
.is_some(),
"alias expression {expression:?} does not resolve in the license index"
);
}
}
}