use std::collections::HashMap;
use std::sync::RwLock;
use uuid::Uuid;
lazy_static::lazy_static! {
static ref GLOBAL_RDM_NAMESPACE: RwLock<Option<Uuid>> = RwLock::new(None);
}
pub fn set_rdm_namespace(namespace: &str) -> Result<(), String> {
let uuid = parse_rdm_namespace(namespace)?;
if let Ok(mut guard) = GLOBAL_RDM_NAMESPACE.write() {
*guard = Some(uuid);
}
Ok(())
}
pub fn get_rdm_namespace() -> Option<Uuid> {
GLOBAL_RDM_NAMESPACE.read().ok().and_then(|guard| *guard)
}
pub fn has_rdm_namespace() -> bool {
GLOBAL_RDM_NAMESPACE
.read()
.ok()
.map(|guard| guard.is_some())
.unwrap_or(false)
}
pub fn clear_rdm_namespace() {
if let Ok(mut guard) = GLOBAL_RDM_NAMESPACE.write() {
*guard = None;
}
}
pub fn parse_rdm_namespace(namespace: &str) -> Result<Uuid, String> {
if namespace.starts_with("http://") || namespace.starts_with("https://") {
Ok(Uuid::new_v5(&Uuid::NAMESPACE_URL, namespace.as_bytes()))
} else {
Uuid::parse_str(namespace).map_err(|e| {
format!(
"Invalid namespace: expected UUID or URL (http/https), got: {} ({})",
namespace, e
)
})
}
}
pub fn generate_collection_uuid(namespace: &Uuid, name: &str) -> Uuid {
let key = format!("collection/{}", name);
Uuid::new_v5(namespace, key.as_bytes())
}
pub fn generate_concept_uuid(collection_id: &Uuid, label: &str) -> Uuid {
Uuid::new_v5(collection_id, label.as_bytes())
}
pub fn generate_concept_uuid_from_str(collection_id: &str, label: &str) -> Result<Uuid, String> {
let namespace = Uuid::parse_str(collection_id).map_err(|e| {
format!(
"Collection ID must be a valid UUID for auto-generation: {}",
e
)
})?;
Ok(generate_concept_uuid(&namespace, label))
}
pub fn generate_value_uuid(concept_id: &str, value: &str, language: &str) -> Uuid {
let namespace = Uuid::new_v5(&Uuid::NAMESPACE_URL, b"value");
let name = format!("{}/prefLabel/{}/{}", concept_id, value, language);
Uuid::new_v5(&namespace, name.as_bytes())
}
pub fn labels_to_deterministic_string(labels: &HashMap<String, String>) -> String {
let mut entries: Vec<(&String, &String)> = labels.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
entries
.iter()
.map(|(lang, val)| format!("{}:{}", lang, val))
.collect::<Vec<_>>()
.join("|")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_uuid_string() {
let uuid = parse_rdm_namespace("550e8400-e29b-41d4-a716-446655440000").unwrap();
assert_eq!(uuid.to_string(), "550e8400-e29b-41d4-a716-446655440000");
}
#[test]
fn test_parse_url_to_uuid() {
let uuid1 = parse_rdm_namespace("http://example.org/rdm/").unwrap();
let uuid2 = parse_rdm_namespace("http://example.org/rdm/").unwrap();
assert_eq!(uuid1, uuid2);
assert_ne!(uuid1.to_string(), "http://example.org/rdm/");
}
#[test]
fn test_parse_https_url() {
let uuid = parse_rdm_namespace("https://example.org/rdm/").unwrap();
assert!(uuid.to_string().len() == 36);
}
#[test]
fn test_parse_invalid() {
let result = parse_rdm_namespace("not-a-uuid");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid namespace"));
}
#[test]
fn test_generate_collection_uuid() {
let namespace = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let uuid1 = generate_collection_uuid(&namespace, "TestCollection");
let uuid2 = generate_collection_uuid(&namespace, "TestCollection");
assert_eq!(uuid1, uuid2);
let uuid3 = generate_collection_uuid(&namespace, "OtherCollection");
assert_ne!(uuid1, uuid3);
}
#[test]
fn test_generate_concept_uuid() {
let collection_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let uuid1 = generate_concept_uuid(&collection_id, "ConceptA");
let uuid2 = generate_concept_uuid(&collection_id, "ConceptA");
assert_eq!(uuid1, uuid2);
let uuid3 = generate_concept_uuid(&collection_id, "ConceptB");
assert_ne!(uuid1, uuid3);
}
#[test]
fn test_generate_concept_uuid_from_str() {
let uuid =
generate_concept_uuid_from_str("550e8400-e29b-41d4-a716-446655440000", "ConceptA")
.unwrap();
assert!(uuid.to_string().len() == 36);
let result = generate_concept_uuid_from_str("not-a-uuid", "ConceptA");
assert!(result.is_err());
}
#[test]
fn test_generate_value_uuid() {
let uuid1 = generate_value_uuid("concept-123", "Hello", "en");
let uuid2 = generate_value_uuid("concept-123", "Hello", "en");
assert_eq!(uuid1, uuid2);
let uuid3 = generate_value_uuid("concept-123", "Hallo", "de");
assert_ne!(uuid1, uuid3);
}
#[test]
fn test_labels_to_deterministic_string() {
let mut labels = HashMap::new();
labels.insert("en".to_string(), "Hello".to_string());
labels.insert("de".to_string(), "Hallo".to_string());
labels.insert("fr".to_string(), "Bonjour".to_string());
let s = labels_to_deterministic_string(&labels);
assert_eq!(s, "de:Hallo|en:Hello|fr:Bonjour");
}
#[test]
fn test_labels_to_deterministic_string_single() {
let mut labels = HashMap::new();
labels.insert("en".to_string(), "Hello".to_string());
let s = labels_to_deterministic_string(&labels);
assert_eq!(s, "en:Hello");
}
#[test]
fn test_labels_to_deterministic_string_empty() {
let labels = HashMap::new();
let s = labels_to_deterministic_string(&labels);
assert_eq!(s, "");
}
}