use serde::{Deserialize, Deserializer, Serialize};
use std::collections::{HashMap, HashSet};
use crate::rdm_namespace::generate_value_uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RdmValue {
pub id: String,
pub value: String,
#[serde(skip)]
pub concept_id: String,
#[serde(skip)]
pub language: String,
}
impl RdmValue {
pub fn new(id: String, value: String) -> Self {
Self {
id,
value,
concept_id: String::new(),
language: String::new(),
}
}
pub fn with_context(id: String, value: String, concept_id: String, language: String) -> Self {
Self {
id,
value,
concept_id,
language,
}
}
pub fn generate_id(concept_id: &str, value: &str, language: &str) -> String {
generate_value_uuid(concept_id, value, language).to_string()
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum PrefLabelEntry {
Simple(String),
WithId { id: String, value: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RdmConcept {
pub id: String,
#[serde(
default,
alias = "prefLabel",
alias = "prefLabels",
deserialize_with = "deserialize_pref_labels"
)]
pub pref_label: HashMap<String, RdmValue>,
#[serde(default, rename = "altLabels")]
pub alt_labels: HashMap<String, Vec<String>>,
#[serde(default)]
pub broader: Vec<String>,
#[serde(default)]
pub narrower: Vec<String>,
#[serde(default, rename = "scopeNote")]
pub scope_note: HashMap<String, String>,
}
fn deserialize_pref_labels<'de, D>(deserializer: D) -> Result<HashMap<String, RdmValue>, D::Error>
where
D: Deserializer<'de>,
{
let raw: HashMap<String, PrefLabelEntry> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (lang, entry) in raw {
let value = match entry {
PrefLabelEntry::Simple(text) => {
RdmValue::new("__pending__".to_string(), text)
}
PrefLabelEntry::WithId { id, value } => RdmValue::new(id, value),
};
result.insert(lang, value);
}
Ok(result)
}
impl RdmConcept {
pub fn get_label(&self, language: &str) -> Option<String> {
self.pref_label
.get(language)
.or_else(|| self.pref_label.get("en"))
.or_else(|| self.pref_label.values().next())
.map(|v| v.value.clone())
}
pub fn get_value(&self, language: &str) -> Option<&RdmValue> {
self.pref_label
.get(language)
.or_else(|| self.pref_label.get("en"))
.or_else(|| self.pref_label.values().next())
}
}
#[derive(Debug, Clone, Default)]
pub struct RdmCollection {
pub id: String,
pub name: Option<String>,
concepts: HashMap<String, RdmConcept>,
top_concepts: Vec<String>,
value_index: HashMap<String, (String, String)>,
}
impl RdmCollection {
pub fn new(id: String) -> Self {
Self {
id,
name: None,
concepts: HashMap::new(),
top_concepts: vec![],
value_index: HashMap::new(),
}
}
pub fn with_name(id: String, name: String) -> Self {
Self {
id,
name: Some(name),
concepts: HashMap::new(),
top_concepts: vec![],
value_index: HashMap::new(),
}
}
pub fn add_concept(&mut self, mut concept: RdmConcept) {
let concept_id = concept.id.clone();
for (lang, value) in concept.pref_label.iter_mut() {
if value.id == "__pending__" {
value.id = RdmValue::generate_id(&concept_id, &value.value, lang);
}
value.concept_id = concept_id.clone();
value.language = lang.clone();
self.value_index
.insert(value.id.clone(), (concept_id.clone(), lang.clone()));
}
if concept.broader.is_empty() {
self.top_concepts.push(concept_id.clone());
}
self.concepts.insert(concept_id, concept);
}
pub fn get_top_concepts(&self) -> Vec<&RdmConcept> {
self.top_concepts
.iter()
.filter_map(|id| self.concepts.get(id))
.collect()
}
pub fn get_concept(&self, concept_id: &str) -> Option<&RdmConcept> {
self.concepts.get(concept_id)
}
pub fn get_concept_mut(&mut self, concept_id: &str) -> Option<&mut RdmConcept> {
self.concepts.get_mut(concept_id)
}
pub fn get_label(&self, concept_id: &str, language: &str) -> Option<String> {
self.get_concept(concept_id)
.and_then(|c| c.get_label(language))
}
pub fn from_concepts_json(id: String, json: &str) -> Result<Self, String> {
let concepts: Vec<RdmConcept> = serde_json::from_str(json)
.map_err(|e| format!("Failed to parse concepts JSON: {}", e))?;
let mut collection = Self::new(id);
for concept in concepts {
collection.add_concept(concept);
}
Ok(collection)
}
pub fn len(&self) -> usize {
self.concepts.len()
}
pub fn is_empty(&self) -> bool {
self.concepts.is_empty()
}
pub fn has_concept(&self, concept_id: &str) -> bool {
self.concepts.contains_key(concept_id)
}
pub fn get_concept_ids(&self) -> Vec<&String> {
self.concepts.keys().collect()
}
pub fn get_parent_id(&self, concept_id: &str) -> Option<String> {
self.get_concept(concept_id)
.and_then(|c| c.broader.first().cloned())
}
pub fn get_value_by_id(&self, value_id: &str) -> Option<&RdmValue> {
self.value_index
.get(value_id)
.and_then(|(concept_id, lang)| {
self.concepts
.get(concept_id)
.and_then(|c| c.pref_label.get(lang))
})
}
pub fn get_concept_id_for_value(&self, value_id: &str) -> Option<&str> {
self.value_index
.get(value_id)
.map(|(concept_id, _)| concept_id.as_str())
}
pub fn has_value(&self, value_id: &str) -> bool {
self.value_index.contains_key(value_id)
}
pub fn get_value_ids(&self) -> Vec<&String> {
self.value_index.keys().collect()
}
pub fn find_by_label(&self, label: &str) -> Option<&RdmConcept> {
let label_lower = label.trim().to_lowercase();
let mut matches: Vec<_> = self
.concepts
.values()
.filter(|c| {
c.pref_label.values().any(|p| p.value.trim().to_lowercase() == label_lower) ||
c.alt_labels.values().any(|alts|
alts.iter().any(|l| l.trim().to_lowercase() == label_lower)
)
})
.collect();
matches.sort_by(|a, b| a.id.cmp(&b.id));
matches.into_iter().next()
}
pub fn find_all_by_label(&self, label: &str) -> Vec<&RdmConcept> {
let label_lower = label.trim().to_lowercase();
self.concepts
.values()
.filter(|c| {
c.pref_label
.values()
.any(|p| p.value.trim().to_lowercase() == label_lower)
|| c.alt_labels
.values()
.any(|alts| alts.iter().any(|l| l.trim().to_lowercase() == label_lower))
})
.collect()
}
pub fn search(&self, query: &str, language: Option<&str>) -> Vec<&RdmConcept> {
let lang = language.unwrap_or("en");
let query_lower = query.to_lowercase();
self.concepts
.values()
.filter(|c| {
if let Some(label) = c.pref_label.get(lang) {
if label.value.to_lowercase().starts_with(&query_lower) {
return true;
}
}
if let Some(alts) = c.alt_labels.get(lang) {
if alts
.iter()
.any(|l| l.to_lowercase().starts_with(&query_lower))
{
return true;
}
}
false
})
.collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct RdmCache {
collections: HashMap<String, RdmCollection>,
}
impl RdmCache {
pub fn new() -> Self {
Self {
collections: HashMap::new(),
}
}
pub fn add_collection_from_json(
&mut self,
collection_id: &str,
concepts_json: &str,
) -> Result<(), String> {
let collection =
RdmCollection::from_concepts_json(collection_id.to_string(), concepts_json)?;
self.collections
.insert(collection_id.to_string(), collection);
Ok(())
}
pub fn add_collection(&mut self, collection: RdmCollection) {
if let Some(existing) = self.collections.get_mut(&collection.id) {
for (concept_id, incoming_concept) in collection.concepts {
let incoming_has_labels = !incoming_concept.pref_label.is_empty();
let should_insert = match existing.concepts.get(&concept_id) {
None => true, Some(existing_concept) => {
let existing_has_labels = !existing_concept.pref_label.is_empty();
incoming_has_labels || !existing_has_labels
}
};
if should_insert {
existing.add_concept(incoming_concept);
}
}
if collection.name.is_some() {
existing.name = collection.name;
}
} else {
self.collections.insert(collection.id.clone(), collection);
}
}
pub fn has_collection(&self, collection_id: &str) -> bool {
self.collections.contains_key(collection_id)
}
pub fn get_collection_ids(&self) -> Vec<String> {
self.collections.keys().cloned().collect()
}
pub fn lookup_label(
&self,
collection_id: &str,
concept_id: &str,
language: &str,
) -> Option<String> {
self.collections
.get(collection_id)
.and_then(|c| c.get_label(concept_id, language))
}
pub fn lookup_concept(&self, collection_id: &str, concept_id: &str) -> Option<&RdmConcept> {
self.collections
.get(collection_id)
.and_then(|c| c.get_concept(concept_id))
}
pub fn get_parent_id(&self, collection_id: &str, concept_id: &str) -> Option<String> {
self.collections
.get(collection_id)
.and_then(|c| c.get_parent_id(concept_id))
}
pub fn lookup_value(&self, collection_id: &str, value_id: &str) -> Option<&RdmValue> {
self.collections
.get(collection_id)
.and_then(|c| c.get_value_by_id(value_id))
}
pub fn get_concept_id_for_value(&self, collection_id: &str, value_id: &str) -> Option<&str> {
self.collections
.get(collection_id)
.and_then(|c| c.get_concept_id_for_value(value_id))
}
pub fn validate_value(&self, collection_id: &str, value_id: &str) -> bool {
self.collections
.get(collection_id)
.map(|c| c.has_value(value_id))
.unwrap_or(false)
}
pub fn get_collection(&self, collection_id: &str) -> Option<&RdmCollection> {
self.collections.get(collection_id)
}
pub fn get_collection_mut(&mut self, collection_id: &str) -> Option<&mut RdmCollection> {
self.collections.get_mut(collection_id)
}
pub fn clear(&mut self) {
self.collections.clear();
}
pub fn remove_collection(&mut self, collection_id: &str) -> bool {
self.collections.remove(collection_id).is_some()
}
pub fn len(&self) -> usize {
self.collections.len()
}
pub fn is_empty(&self) -> bool {
self.collections.is_empty()
}
pub fn validate_concept(&self, collection_id: &str, concept_id: &str) -> bool {
self.collections
.get(collection_id)
.map(|c| c.has_concept(concept_id))
.unwrap_or(false)
}
pub fn lookup_by_label(&self, collection_id: &str, label: &str) -> Option<&RdmConcept> {
self.collections
.get(collection_id)
.and_then(|c| c.find_by_label(label))
}
pub fn lookup_all_by_label(&self, collection_id: &str, label: &str) -> Vec<&RdmConcept> {
self.collections
.get(collection_id)
.map(|c| c.find_all_by_label(label))
.unwrap_or_default()
}
pub fn search_all(&self, query: &str, language: Option<&str>) -> Vec<(&str, &RdmConcept)> {
self.collections
.iter()
.flat_map(|(coll_id, collection)| {
collection
.search(query, language)
.into_iter()
.map(move |c| (coll_id.as_str(), c))
})
.collect()
}
}
use crate::skos::{SkosCollection, SkosConcept, SkosNodeType, SkosValue};
impl RdmCache {
pub fn add_from_skos_collection(&mut self, skos: &SkosCollection) -> String {
let rdm = skos_to_rdm_collection(skos);
let id = rdm.id.clone();
self.add_collection(rdm);
id
}
pub fn add_from_skos_collections(&mut self, collections: &[SkosCollection]) -> Vec<String> {
let added_ids: Vec<String> = collections
.iter()
.map(|skos| self.add_from_skos_collection(skos))
.collect();
self.enrich_bare_concepts(&added_ids);
added_ids
}
fn enrich_bare_concepts(&mut self, newly_added_ids: &[String]) {
let mut concept_labels: HashMap<String, HashMap<String, RdmValue>> = HashMap::new();
for coll in self.collections.values() {
for (concept_id, concept) in &coll.concepts {
if !concept.pref_label.is_empty() {
concept_labels
.entry(concept_id.clone())
.or_insert_with(|| concept.pref_label.clone());
}
}
}
if concept_labels.is_empty() {
return;
}
let all_collection_ids: Vec<String> = self.collections.keys().cloned().collect();
for coll_id in &all_collection_ids {
let needs_enrichment: Vec<String> = {
if let Some(coll) = self.collections.get(coll_id) {
coll.concepts
.iter()
.filter(|(_, concept)| concept.pref_label.is_empty())
.filter(|(id, _)| concept_labels.contains_key(*id))
.map(|(id, _)| id.clone())
.collect()
} else {
vec![]
}
};
if needs_enrichment.is_empty() {
continue;
}
let dominated_by_new = newly_added_ids.contains(coll_id)
|| needs_enrichment
.iter()
.any(|cid| self.concept_in_collections(cid, newly_added_ids));
if !dominated_by_new {
continue;
}
if let Some(coll) = self.collections.get_mut(coll_id) {
for concept_id in needs_enrichment {
if let Some(labels) = concept_labels.get(&concept_id) {
let mut new_index_entries: Vec<(String, String, String)> = Vec::new();
let mut enriched_labels = labels.clone();
for (lang, value) in enriched_labels.iter_mut() {
value.concept_id = concept_id.clone();
value.language = lang.clone();
new_index_entries.push((
value.id.clone(),
concept_id.clone(),
lang.clone(),
));
}
if let Some(concept) = coll.get_concept_mut(&concept_id) {
concept.pref_label = enriched_labels;
}
for (value_id, cid, lang) in new_index_entries {
coll.value_index.insert(value_id, (cid, lang));
}
}
}
}
}
}
fn concept_in_collections(&self, concept_id: &str, collection_ids: &[String]) -> bool {
collection_ids.iter().any(|coll_id| {
self.collections
.get(coll_id)
.and_then(|c| c.get_concept(concept_id))
.map(|concept| !concept.pref_label.is_empty())
.unwrap_or(false)
})
}
}
pub fn skos_to_rdm_collection(skos: &SkosCollection) -> RdmCollection {
let mut rdm = RdmCollection::with_name(
skos.id.clone(),
skos.pref_labels
.get("en")
.map(|v| v.value.clone())
.unwrap_or_else(|| skos.id.clone()),
);
fn add_concept_recursive(
rdm: &mut RdmCollection,
skos_concept: &SkosConcept,
parent_id: Option<&str>,
) {
let mut pref_label: HashMap<String, RdmValue> = HashMap::new();
for (lang, skos_value) in &skos_concept.pref_labels {
pref_label.insert(
lang.clone(),
RdmValue::new(skos_value.id.clone(), skos_value.value.clone()),
);
}
let narrower: Vec<String> = skos_concept
.children
.as_ref()
.map(|children| children.iter().map(|c| c.id.clone()).collect())
.unwrap_or_default();
let broader = parent_id.map(|p| vec![p.to_string()]).unwrap_or_default();
let rdm_concept = RdmConcept {
id: skos_concept.id.clone(),
pref_label,
alt_labels: HashMap::new(),
broader,
narrower,
scope_note: HashMap::new(),
};
rdm.add_concept(rdm_concept);
if let Some(ref children) = skos_concept.children {
for child in children {
add_concept_recursive(rdm, child, Some(&skos_concept.id));
}
}
}
for skos_concept in skos.concepts.values() {
add_concept_recursive(&mut rdm, skos_concept, None);
}
if rdm.is_empty() && !skos.all_concepts.is_empty() {
for skos_concept in skos.all_concepts.values() {
if !rdm.has_concept(&skos_concept.id) {
add_concept_recursive(&mut rdm, skos_concept, None);
}
}
}
rdm
}
pub fn rdm_to_skos_collection(rdm: &RdmCollection, node_type: &str) -> SkosCollection {
rdm_to_skos_collection_excluding(rdm, node_type, &HashSet::new())
}
pub fn rdm_to_skos_collection_excluding(
rdm: &RdmCollection,
node_type: &str,
exclude_ids: &HashSet<String>,
) -> SkosCollection {
let mut collection_pref_labels = HashMap::new();
if let Some(ref name) = rdm.name {
collection_pref_labels.insert(
"en".to_string(),
SkosValue {
id: generate_value_uuid(&rdm.id, name, "en").to_string(),
value: name.clone(),
},
);
}
let mut all_skos_concepts: HashMap<String, SkosConcept> = HashMap::new();
let mut all_narrower_ids: HashSet<String> = HashSet::new();
for concept_id in rdm.get_concept_ids() {
if exclude_ids.contains(concept_id.as_str()) {
continue;
}
if let Some(rdm_concept) = rdm.get_concept(concept_id) {
let mut pref_labels = HashMap::new();
for (lang, rdm_value) in &rdm_concept.pref_label {
let value_id = if rdm_value.id.is_empty() || rdm_value.id == "__pending__" {
generate_value_uuid(concept_id, &rdm_value.value, lang).to_string()
} else {
rdm_value.id.clone()
};
pref_labels.insert(
lang.clone(),
SkosValue {
id: value_id,
value: rdm_value.value.clone(),
},
);
}
let skos_concept = SkosConcept {
id: concept_id.clone(),
uri: None,
pref_labels,
source: Some(concept_id.clone()),
sort_order: None,
children: None,
};
all_skos_concepts.insert(concept_id.clone(), skos_concept);
all_narrower_ids.extend(rdm_concept.narrower.iter().cloned());
}
}
let skos_node_type = if node_type == "Collection" {
SkosNodeType::Collection
} else {
SkosNodeType::ConceptScheme
};
let mut hierarchy: HashMap<String, SkosConcept> = HashMap::new();
let mut placed: HashSet<String> = HashSet::new();
for concept_id in rdm.get_concept_ids() {
if exclude_ids.contains(concept_id.as_str()) {
continue;
}
if !all_narrower_ids.contains(concept_id) {
if let Some(concept_with_children) =
build_concept_tree_from_rdm(concept_id, &all_skos_concepts, rdm, &mut placed)
{
hierarchy.insert(concept_id.clone(), concept_with_children);
}
}
}
let top_level_concepts = if hierarchy.is_empty() {
all_skos_concepts.clone()
} else {
hierarchy
};
SkosCollection {
id: rdm.id.clone(),
uri: None,
pref_labels: collection_pref_labels,
alt_labels: HashMap::new(),
scope_notes: HashMap::new(),
node_type: skos_node_type,
concepts: top_level_concepts,
all_concepts: all_skos_concepts,
values: HashMap::new(),
}
}
fn build_concept_tree_from_rdm(
concept_id: &str,
all_concepts: &HashMap<String, SkosConcept>,
rdm_collection: &RdmCollection,
placed: &mut HashSet<String>,
) -> Option<SkosConcept> {
if !placed.insert(concept_id.to_string()) {
return None; }
let mut concept = all_concepts.get(concept_id)?.clone();
if let Some(rdm_concept) = rdm_collection.get_concept(concept_id) {
if !rdm_concept.narrower.is_empty() {
let mut children = Vec::new();
for child_id in &rdm_concept.narrower {
if let Some(child) =
build_concept_tree_from_rdm(child_id, all_concepts, rdm_collection, placed)
{
children.push(child);
}
}
if !children.is_empty() {
concept.children = Some(children);
}
}
}
Some(concept)
}
use crate::type_serialization::ExternalResolver;
impl ExternalResolver for RdmCache {
fn resolve_concept(
&self,
collection_id: &str,
concept_id: &str,
language: &str,
) -> Option<String> {
self.lookup_label(collection_id, concept_id, language)
}
}
use crate::label_resolution::ConceptLookup;
impl ConceptLookup for RdmCache {
fn lookup_by_label(&self, collection_id: &str, label: &str) -> Option<String> {
self.collections
.get(collection_id)
.and_then(|c| c.find_by_label(label))
.map(|c| c.id.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_concept_label_lookup() {
let mut cache = RdmCache::new();
let concepts_json = r#"[
{
"id": "concept-1",
"prefLabel": {"en": "English Label", "de": "German Label"}
},
{
"id": "concept-2",
"prefLabel": {"en": "Second Concept"}
}
]"#;
cache
.add_collection_from_json("collection-1", concepts_json)
.unwrap();
assert!(cache.has_collection("collection-1"));
assert!(!cache.has_collection("collection-2"));
assert_eq!(
cache.lookup_label("collection-1", "concept-1", "en"),
Some("English Label".to_string())
);
assert_eq!(
cache.lookup_label("collection-1", "concept-1", "de"),
Some("German Label".to_string())
);
assert_eq!(
cache.lookup_label("collection-1", "concept-1", "fr"),
Some("English Label".to_string())
);
assert_eq!(cache.lookup_label("collection-1", "concept-3", "en"), None);
}
#[test]
fn test_clear_cache() {
let mut cache = RdmCache::new();
cache
.add_collection_from_json("coll-1", r#"[{"id": "c1", "prefLabel": {"en": "C1"}}]"#)
.unwrap();
cache
.add_collection_from_json("coll-2", r#"[{"id": "c2", "prefLabel": {"en": "C2"}}]"#)
.unwrap();
assert_eq!(cache.get_collection_ids().len(), 2);
cache.clear();
assert_eq!(cache.get_collection_ids().len(), 0);
}
#[test]
fn test_hierarchical_concepts() {
let mut collection = RdmCollection::new("coll-1".to_string());
let mut parent_labels = HashMap::new();
parent_labels.insert(
"en".to_string(),
RdmValue::new("v-parent-en".to_string(), "Parent".to_string()),
);
let parent = RdmConcept {
id: "parent".to_string(),
pref_label: parent_labels,
alt_labels: HashMap::new(),
broader: vec![],
narrower: vec!["child".to_string()],
scope_note: HashMap::new(),
};
let mut child_labels = HashMap::new();
child_labels.insert(
"en".to_string(),
RdmValue::new("v-child-en".to_string(), "Child".to_string()),
);
let child = RdmConcept {
id: "child".to_string(),
pref_label: child_labels,
alt_labels: HashMap::new(),
broader: vec!["parent".to_string()],
narrower: vec![],
scope_note: HashMap::new(),
};
collection.add_concept(parent);
collection.add_concept(child);
assert_eq!(collection.len(), 2);
let top = collection.get_top_concepts();
assert_eq!(top.len(), 1);
assert_eq!(top[0].id, "parent");
assert!(collection.has_concept("parent"));
assert!(collection.has_concept("child"));
}
#[test]
fn test_get_concept_mut() {
let mut collection = RdmCollection::new("coll-1".to_string());
let mut labels = HashMap::new();
labels.insert(
"en".to_string(),
RdmValue::new("v-c1-en".to_string(), "Original".to_string()),
);
let concept = RdmConcept {
id: "c1".to_string(),
pref_label: labels,
alt_labels: HashMap::new(),
broader: vec![],
narrower: vec![],
scope_note: HashMap::new(),
};
collection.add_concept(concept);
if let Some(c) = collection.get_concept_mut("c1") {
c.narrower.push("c2".to_string());
c.pref_label.insert(
"de".to_string(),
RdmValue::new("v-c1-de".to_string(), "Geändert".to_string()),
);
}
let c = collection.get_concept("c1").unwrap();
assert_eq!(c.narrower, vec!["c2".to_string()]);
assert_eq!(
c.pref_label.get("de").map(|v| v.value.as_str()),
Some("Geändert")
);
}
#[test]
fn test_add_child_concept_hierarchy() {
let mut collection = RdmCollection::new("coll-1".to_string());
let mut parent_labels = HashMap::new();
parent_labels.insert(
"en".to_string(),
RdmValue::new("v-animals-en".to_string(), "Animals".to_string()),
);
let parent = RdmConcept {
id: "animals".to_string(),
pref_label: parent_labels,
alt_labels: HashMap::new(),
broader: vec![],
narrower: vec![],
scope_note: HashMap::new(),
};
collection.add_concept(parent);
if let Some(p) = collection.get_concept_mut("animals") {
p.narrower.push("mammals".to_string());
}
let mut child_labels = HashMap::new();
child_labels.insert(
"en".to_string(),
RdmValue::new("v-mammals-en".to_string(), "Mammals".to_string()),
);
let child = RdmConcept {
id: "mammals".to_string(),
pref_label: child_labels,
alt_labels: HashMap::new(),
broader: vec!["animals".to_string()],
narrower: vec![],
scope_note: HashMap::new(),
};
collection.add_concept(child);
let top = collection.get_top_concepts();
assert_eq!(top.len(), 1);
assert_eq!(top[0].id, "animals");
assert_eq!(top[0].narrower, vec!["mammals".to_string()]);
let child = collection.get_concept("mammals").unwrap();
assert_eq!(child.broader, vec!["animals".to_string()]);
}
#[test]
fn test_value_id_lookup() {
let mut cache = RdmCache::new();
let concepts_json = r#"[
{
"id": "concept-1",
"prefLabels": {
"en": { "id": "value-1-en", "value": "English Label" },
"de": { "id": "value-1-de", "value": "German Label" }
}
},
{
"id": "concept-2",
"prefLabels": {
"en": { "id": "value-2-en", "value": "Second Concept" }
}
}
]"#;
cache
.add_collection_from_json("collection-1", concepts_json)
.unwrap();
let value = cache.lookup_value("collection-1", "value-1-en").unwrap();
assert_eq!(value.id, "value-1-en");
assert_eq!(value.value, "English Label");
assert_eq!(value.concept_id, "concept-1");
assert_eq!(value.language, "en");
assert_eq!(
cache.get_concept_id_for_value("collection-1", "value-1-de"),
Some("concept-1")
);
assert!(cache.lookup_value("collection-1", "nonexistent").is_none());
assert!(cache
.get_concept_id_for_value("collection-1", "nonexistent")
.is_none());
assert!(cache.validate_value("collection-1", "value-2-en"));
assert!(!cache.validate_value("collection-1", "nonexistent"));
}
#[test]
fn test_simple_preflabel_format_generates_ids() {
let mut cache = RdmCache::new();
let concepts_json = r#"[
{
"id": "concept-1",
"prefLabel": {"en": "Label One", "de": "Etikett Eins"}
}
]"#;
cache
.add_collection_from_json("collection-1", concepts_json)
.unwrap();
let collection = cache.get_collection("collection-1").unwrap();
let concept = collection.get_concept("concept-1").unwrap();
let en_value = concept.pref_label.get("en").unwrap();
assert_ne!(en_value.id, "__pending__");
assert_eq!(en_value.value, "Label One");
let looked_up = collection.get_value_by_id(&en_value.id).unwrap();
assert_eq!(looked_up.value, "Label One");
assert_eq!(looked_up.concept_id, "concept-1");
}
#[test]
fn test_get_parent_id() {
let mut cache = RdmCache::new();
let concepts_json = r#"[
{
"id": "parent-concept",
"prefLabel": {"en": "Parent"}
},
{
"id": "child-concept",
"prefLabel": {"en": "Child"},
"broader": ["parent-concept"]
}
]"#;
cache
.add_collection_from_json("coll-1", concepts_json)
.unwrap();
assert_eq!(
cache.get_parent_id("coll-1", "child-concept"),
Some("parent-concept".to_string())
);
assert_eq!(cache.get_parent_id("coll-1", "parent-concept"), None);
assert_eq!(cache.get_parent_id("coll-1", "nonexistent"), None);
}
}