use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::iri::IriId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct EntityId(pub u32);
impl EntityId {
#[must_use]
pub fn index(self) -> u32 {
self.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EntityKind {
Class,
Individual,
ObjectProperty,
DataProperty,
AnnotationProperty,
Datatype,
ClassIndividual,
ClassObjectProperty,
ObjectPropertyIndividual,
}
impl EntityKind {
#[must_use]
pub fn is_class(self) -> bool {
matches!(
self,
Self::Class | Self::ClassIndividual | Self::ClassObjectProperty
)
}
#[must_use]
pub fn is_individual(self) -> bool {
matches!(
self,
Self::Individual | Self::ClassIndividual | Self::ObjectPropertyIndividual
)
}
#[must_use]
pub fn is_object_property(self) -> bool {
matches!(
self,
Self::ObjectProperty | Self::ClassObjectProperty | Self::ObjectPropertyIndividual
)
}
#[must_use]
pub fn merge_punning(stored: Self, requested: Self) -> Option<Self> {
if stored.satisfies(requested) {
return Some(stored);
}
if requested.satisfies(stored) {
return Some(requested);
}
match (stored, requested) {
(Self::Class, Self::Individual) | (Self::Individual, Self::Class) => {
Some(Self::ClassIndividual)
}
(Self::Class, Self::ObjectProperty) | (Self::ObjectProperty, Self::Class) => {
Some(Self::ClassObjectProperty)
}
(Self::ObjectProperty, Self::Individual) | (Self::Individual, Self::ObjectProperty) => {
Some(Self::ObjectPropertyIndividual)
}
_ => None,
}
}
#[must_use]
pub fn satisfies(self, expected: Self) -> bool {
if self == expected {
return true;
}
match self {
Self::ClassIndividual => matches!(expected, Self::Class | Self::Individual),
Self::ClassObjectProperty => {
matches!(expected, Self::Class | Self::ObjectProperty)
}
Self::ObjectPropertyIndividual => {
matches!(expected, Self::ObjectProperty | Self::Individual)
}
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EntityRecord {
pub iri: IriId,
pub kind: EntityKind,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct EntityRegistry {
entities: Vec<EntityRecord>,
by_iri: HashMap<IriId, EntityId>,
}
impl EntityRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.entities.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entities.is_empty()
}
#[must_use]
pub fn entity_by_iri(&self, iri: IriId) -> Option<EntityId> {
self.by_iri.get(&iri).copied()
}
pub fn entity(&self, id: EntityId) -> Result<&EntityRecord> {
self.entities
.get(id.0 as usize)
.ok_or(Error::UnknownEntity(id))
}
pub fn set_kind(&mut self, id: EntityId, kind: EntityKind) -> Result<()> {
let record = self
.entities
.get_mut(id.0 as usize)
.ok_or(Error::UnknownEntity(id))?;
record.kind = kind;
Ok(())
}
pub fn get_or_register(
&mut self,
iri: IriId,
iri_str: &str,
kind: EntityKind,
) -> Result<EntityId> {
if let Some(&existing) = self.by_iri.get(&iri) {
let record = &mut self.entities[existing.0 as usize];
if !record.kind.satisfies(kind) {
let Some(merged) = EntityKind::merge_punning(record.kind, kind) else {
return Err(Error::EntityKindMismatch {
iri: iri_str.to_owned(),
expected: kind,
found: record.kind,
});
};
record.kind = merged;
}
return Ok(existing);
}
let id = EntityId(
u32::try_from(self.entities.len())
.map_err(|_| Error::InvalidAxiom("entity registry capacity exceeded".into()))?,
);
self.by_iri.insert(iri, id);
self.entities.push(EntityRecord { iri, kind });
Ok(id)
}
pub fn iter(&self) -> impl Iterator<Item = (EntityId, &EntityRecord)> {
self.entities
.iter()
.enumerate()
.map(|(i, record)| (EntityId(i as u32), record))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::iri::InternPool;
#[test]
fn register_and_lookup() {
let mut pool = InternPool::new();
let iri = pool.intern("http://example.org/A").expect("intern");
let mut registry = EntityRegistry::new();
let id = registry
.get_or_register(iri, "http://example.org/A", EntityKind::Class)
.expect("register");
assert_eq!(registry.entity_by_iri(iri), Some(id));
assert_eq!(registry.entity(id).expect("entity").kind, EntityKind::Class);
}
#[test]
fn class_individual_punning_upgrades_kind() {
let mut pool = InternPool::new();
let iri = pool.intern("http://example.org/A").expect("intern");
let mut registry = EntityRegistry::new();
registry
.get_or_register(iri, "http://example.org/A", EntityKind::Class)
.expect("register");
let id = registry
.get_or_register(iri, "http://example.org/A", EntityKind::Individual)
.expect("punning upgrade");
assert_eq!(
registry.entity(id).expect("entity").kind,
EntityKind::ClassIndividual
);
}
#[test]
fn incompatible_kind_mismatch_errors() {
let mut pool = InternPool::new();
let iri = pool.intern("http://example.org/A").expect("intern");
let mut registry = EntityRegistry::new();
registry
.get_or_register(iri, "http://example.org/A", EntityKind::Class)
.expect("register");
let err = registry
.get_or_register(iri, "http://example.org/A", EntityKind::DataProperty)
.expect_err("mismatch");
assert!(matches!(
err,
Error::EntityKindMismatch {
expected: EntityKind::DataProperty,
found: EntityKind::Class,
..
}
));
if let Error::EntityKindMismatch { iri: err_iri, .. } = err {
assert_eq!(err_iri, "http://example.org/A");
}
}
#[test]
fn unknown_entity_errors() {
let registry = EntityRegistry::new();
let err = registry.entity(EntityId(0)).expect_err("unknown");
assert_eq!(err, Error::UnknownEntity(EntityId(0)));
}
#[test]
fn reregister_same_kind_returns_stable_id() {
let mut pool = InternPool::new();
let iri = pool.intern("http://example.org/A").expect("intern");
let mut registry = EntityRegistry::new();
let first = registry
.get_or_register(iri, "http://example.org/A", EntityKind::Class)
.expect("register");
let second = registry
.get_or_register(iri, "http://example.org/A", EntityKind::Class)
.expect("register");
assert_eq!(first, second);
}
}