use std::error::Error;
use std::fmt;
use std::sync::Arc;
use crate::access::{EngineNetwork, ReadOnlyNetwork, ReadOnlyViolation};
use crate::configuration::AccessMode;
use crate::link_network::{Link, LinkId, LinkMetadata, LinkNetwork, LinkType};
#[derive(Debug)]
pub enum StorageError {
ReadOnly(ReadOnlyViolation),
Io(std::io::Error),
Doublets(String),
Corrupt(String),
}
impl fmt::Display for StorageError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ReadOnly(error) => error.fmt(formatter),
Self::Io(error) => write!(formatter, "storage I/O error: {error}"),
Self::Doublets(error) => write!(formatter, "doublets storage error: {error}"),
Self::Corrupt(error) => write!(formatter, "corrupt storage: {error}"),
}
}
}
impl Error for StorageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::ReadOnly(error) => Some(error),
Self::Io(error) => Some(error),
Self::Doublets(_) | Self::Corrupt(_) => None,
}
}
}
impl From<ReadOnlyViolation> for StorageError {
fn from(error: ReadOnlyViolation) -> Self {
Self::ReadOnly(error)
}
}
impl From<std::io::Error> for StorageError {
fn from(error: std::io::Error) -> Self {
Self::Io(error)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LinkStoreBackend {
LinoProjection,
DoubletsRs,
DoubletsWeb,
}
impl LinkStoreBackend {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::LinoProjection => "LiNo projection",
Self::DoubletsRs => "doublets-rs",
Self::DoubletsWeb => "doublets-web",
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LinkStoreQuery {
id: Option<LinkId>,
references: Option<Vec<LinkId>>,
link_type: Option<LinkType>,
term: Option<String>,
language: Option<String>,
named: Option<bool>,
}
impl LinkStoreQuery {
#[must_use]
pub const fn new() -> Self {
Self {
id: None,
references: None,
link_type: None,
term: None,
language: None,
named: None,
}
}
#[must_use]
pub const fn with_id(mut self, id: LinkId) -> Self {
self.id = Some(id);
self
}
#[must_use]
pub fn with_references<I>(mut self, references: I) -> Self
where
I: IntoIterator<Item = LinkId>,
{
self.references = Some(references.into_iter().collect());
self
}
#[must_use]
pub const fn with_link_type(mut self, link_type: LinkType) -> Self {
self.link_type = Some(link_type);
self
}
#[must_use]
pub fn with_term(mut self, term: impl Into<String>) -> Self {
self.term = Some(term.into());
self
}
#[must_use]
pub fn with_language(mut self, language: impl Into<String>) -> Self {
self.language = Some(language.into());
self
}
#[must_use]
pub const fn with_named(mut self, named: bool) -> Self {
self.named = Some(named);
self
}
fn matches(&self, link: &Link) -> bool {
if self.id.is_some_and(|id| id != link.id()) {
return false;
}
if self
.references
.as_deref()
.is_some_and(|references| references != link.references())
{
return false;
}
if self
.link_type
.is_some_and(|link_type| Some(link_type) != link.metadata().link_type())
{
return false;
}
if self
.term
.as_deref()
.is_some_and(|term| Some(term) != link.metadata().term())
{
return false;
}
if self
.language
.as_deref()
.is_some_and(|language| Some(language) != link.metadata().language())
{
return false;
}
if self
.named
.is_some_and(|named| named != link.metadata().is_named())
{
return false;
}
true
}
}
pub trait LinkStore {
fn create(
&mut self,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<LinkId, StorageError>;
fn read(&self, id: LinkId) -> Result<Option<Link>, StorageError>;
fn update(
&mut self,
id: LinkId,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<bool, StorageError>;
fn delete(&mut self, id: LinkId) -> Result<bool, StorageError>;
fn search(&self, query: &LinkStoreQuery) -> Result<Vec<Link>, StorageError>;
fn count(&self, query: &LinkStoreQuery) -> Result<usize, StorageError> {
self.search(query).map(|links| links.len())
}
}
impl LinkStore for LinkNetwork {
fn create(
&mut self,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<LinkId, StorageError> {
Ok(self.insert_dynamic_link(references, metadata))
}
fn read(&self, id: LinkId) -> Result<Option<Link>, StorageError> {
Ok(self.link(id).cloned())
}
fn update(
&mut self,
id: LinkId,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<bool, StorageError> {
Ok(replace_network_link(self, id, references, metadata, true))
}
fn delete(&mut self, id: LinkId) -> Result<bool, StorageError> {
Ok(delete_network_link(self, id))
}
fn search(&self, query: &LinkStoreQuery) -> Result<Vec<Link>, StorageError> {
Ok(self
.links()
.filter(|link| query.matches(link))
.cloned()
.collect())
}
}
fn insert_network_link_with_id(
network: &mut LinkNetwork,
id: LinkId,
references: &[LinkId],
metadata: LinkMetadata,
registered_term: bool,
) {
let term = registered_term
.then(|| metadata.term().map(Arc::<str>::from))
.flatten();
network.links.insert(
id,
Arc::new(Link {
id,
references: Arc::from(references.to_vec()),
metadata,
}),
);
if let Some(term) = term {
network.terms.insert(term, id);
}
network.next_id = network.next_id.max(id.as_u64() + 1);
}
fn replace_network_link(
network: &mut LinkNetwork,
id: LinkId,
references: &[LinkId],
metadata: LinkMetadata,
registered_term: bool,
) -> bool {
if !network.links.contains_key(&id) {
return false;
}
network.terms.retain(|_, stored_id| *stored_id != id);
insert_network_link_with_id(network, id, references, metadata, registered_term);
true
}
fn delete_network_link(network: &mut LinkNetwork, id: LinkId) -> bool {
let removed = network.links.remove(&id).is_some();
if removed {
network.terms.retain(|_, stored_id| *stored_id != id);
}
removed
}
#[cfg(feature = "doublets")]
fn network_from_stored_links(links: Vec<(Link, bool)>) -> LinkNetwork {
let mut network = LinkNetwork::new();
for (link, registered_term) in links {
insert_network_link_with_id(
&mut network,
link.id,
&link.references,
link.metadata,
registered_term,
);
}
network
}
impl LinkStore for ReadOnlyNetwork {
fn create(
&mut self,
_references: &[LinkId],
_metadata: LinkMetadata,
) -> Result<LinkId, StorageError> {
Err(ReadOnlyViolation.into())
}
fn read(&self, id: LinkId) -> Result<Option<Link>, StorageError> {
LinkStore::read(self.network(), id)
}
fn update(
&mut self,
_id: LinkId,
_references: &[LinkId],
_metadata: LinkMetadata,
) -> Result<bool, StorageError> {
Err(ReadOnlyViolation.into())
}
fn delete(&mut self, _id: LinkId) -> Result<bool, StorageError> {
Err(ReadOnlyViolation.into())
}
fn search(&self, query: &LinkStoreQuery) -> Result<Vec<Link>, StorageError> {
LinkStore::search(self.network(), query)
}
}
impl LinkStore for EngineNetwork {
fn create(
&mut self,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<LinkId, StorageError> {
LinkStore::create(self.as_mutable()?, references, metadata)
}
fn read(&self, id: LinkId) -> Result<Option<Link>, StorageError> {
LinkStore::read(self.network(), id)
}
fn update(
&mut self,
id: LinkId,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<bool, StorageError> {
LinkStore::update(self.as_mutable()?, id, references, metadata)
}
fn delete(&mut self, id: LinkId) -> Result<bool, StorageError> {
LinkStore::delete(self.as_mutable()?, id)
}
fn search(&self, query: &LinkStoreQuery) -> Result<Vec<Link>, StorageError> {
LinkStore::search(self.network(), query)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EngineLinkStore<S> {
Mutable(S),
ReadOnly(S),
}
impl<S> EngineLinkStore<S> {
#[must_use]
pub const fn with_access_mode(store: S, access_mode: AccessMode) -> Self {
match access_mode {
AccessMode::Mutable => Self::Mutable(store),
AccessMode::ReadOnly => Self::ReadOnly(store),
}
}
#[must_use]
pub const fn access_mode(&self) -> AccessMode {
match self {
Self::Mutable(_) => AccessMode::Mutable,
Self::ReadOnly(_) => AccessMode::ReadOnly,
}
}
#[must_use]
pub const fn store(&self) -> &S {
match self {
Self::Mutable(store) | Self::ReadOnly(store) => store,
}
}
pub fn into_inner(self) -> S {
match self {
Self::Mutable(store) | Self::ReadOnly(store) => store,
}
}
}
impl<S: LinkStore> LinkStore for EngineLinkStore<S> {
fn create(
&mut self,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<LinkId, StorageError> {
match self {
Self::Mutable(store) => store.create(references, metadata),
Self::ReadOnly(_) => Err(ReadOnlyViolation.into()),
}
}
fn read(&self, id: LinkId) -> Result<Option<Link>, StorageError> {
self.store().read(id)
}
fn update(
&mut self,
id: LinkId,
references: &[LinkId],
metadata: LinkMetadata,
) -> Result<bool, StorageError> {
match self {
Self::Mutable(store) => store.update(id, references, metadata),
Self::ReadOnly(_) => Err(ReadOnlyViolation.into()),
}
}
fn delete(&mut self, id: LinkId) -> Result<bool, StorageError> {
match self {
Self::Mutable(store) => store.delete(id),
Self::ReadOnly(_) => Err(ReadOnlyViolation.into()),
}
}
fn search(&self, query: &LinkStoreQuery) -> Result<Vec<Link>, StorageError> {
self.store().search(query)
}
}
#[cfg(feature = "doublets")]
mod doublets;
#[cfg(feature = "doublets")]
pub use doublets::DoubletsLinkStore;