doublets 0.19.0

Doublets (links) data structure implementation.
Documentation
//! Decorators that keep `(source, target)` pairs unique.
//!
//! Ports of C# `LinksUniquenessValidator`, `LinksUniquenessResolver` and
//! `LinksCascadeUniquenessAndUsagesResolver`.

use std::{
    fmt::{self, Debug, Formatter},
    marker::PhantomData,
};

use data::{Flow, LinkReference, LinksConstants};

use super::{
    helpers,
    macros::{decorator_struct, forward},
};
use crate::{
    data::{ReadHandler, WriteHandler},
    Doublets, Error, Fuse, Link, Links,
};

decorator_struct! {
    /// Rejects an update that would make two links share the same `(source, target)` pair.
    ///
    /// Port of C# `LinksUniquenessValidator`. The update fails with
    /// [`Error::AlreadyExists`], the analogue of C#'s
    /// `LinkWithSameValueAlreadyExistsException`.
    UniquenessValidator
}

impl<T: LinkReference, L: Doublets<T>> Links<T> for UniquenessValidator<T, L> {
    forward!(count_links, each_links, create_links, delete_links);

    #[inline]
    fn update_links(
        &mut self,
        query: &[T],
        change: &[T],
        handler: WriteHandler<'_, T>,
    ) -> Result<Flow, Error<T>> {
        let constants = self.links.constants();
        let source = helpers::part(change, constants.source_part);
        let target = helpers::part(change, constants.target_part);
        helpers::ensure_does_not_exist(&self.links, source, target)?;
        self.links.update_links(query, change, handler)
    }
}

decorator_struct! {
    /// Redirects an update that would produce a duplicate to the link that already exists,
    /// then deletes the now-redundant link.
    ///
    /// Port of C# `LinksUniquenessResolver`. Composed with the default
    /// [`Doublets::create_link`] this turns `create_link` into the equivalent of
    /// [`Doublets::get_or_create`]: the temporary link created first is deleted, and the
    /// surviving link is reported to the handler.
    ///
    /// # Deviation from C\#
    ///
    /// C# reports nothing for the surviving link, so `CreateAndUpdate` returns the address
    /// of the link it just deleted. This port reports the surviving link as
    /// `(before, after)` instead, so [`Doublets::update_by`] and [`Doublets::create_link`]
    /// return an address that is actually valid.
    ///
    /// # Requested address
    ///
    /// This decorator resolves pair conflicts and nothing else. It does not check that
    /// the link being updated exists. Like the C# original, it looks up the new pair
    /// first and deletes the old link only if it still exists. When the old address is
    /// missing and the pair is already stored, the update therefore reports the existing
    /// link and changes nothing. A missing address with a new pair is forwarded, so the
    /// inner layers decide. See [`CascadeUniquenessAndUsagesResolver`] for how to select
    /// the `Error::NotExists` contract explicitly.
    UniquenessResolver
}

impl<T: LinkReference, L: Doublets<T>> UniquenessResolver<T, L> {
    /// Port of C# `LinksUniquenessResolver.ResolveAddressChangeConflict`.
    #[inline]
    fn resolve_address_change_conflict(
        &mut self,
        old: T,
        new: T,
        handler: WriteHandler<'_, T>,
    ) -> Result<Flow, Error<T>> {
        resolve_address_change_conflict(self, old, new, handler)
    }
}

impl<T: LinkReference, L: Doublets<T>> Links<T> for UniquenessResolver<T, L> {
    forward!(count_links, each_links, create_links, delete_links);

    #[inline]
    fn update_links(
        &mut self,
        query: &[T],
        change: &[T],
        handler: WriteHandler<'_, T>,
    ) -> Result<Flow, Error<T>> {
        let constants = self.links.constants();
        let source = helpers::part(change, constants.source_part);
        let target = helpers::part(change, constants.target_part);
        let old = helpers::part(query, constants.index_part);

        let Some(new) = self.links.search(source, target) else {
            return self.links.update_links(query, change, handler);
        };
        self.resolve_address_change_conflict(old, new, handler)
    }
}

decorator_struct! {
    /// Like [`UniquenessResolver`], but first re-points every link that referenced the
    /// redundant link at the surviving one.
    ///
    /// Port of C# `LinksCascadeUniquenessAndUsagesResolver`.
    ///
    /// # Requested address
    ///
    /// Like [`UniquenessResolver`], this decorator does not check that the link being
    /// updated exists. Existence is a separate policy, so callers who guarantee valid
    /// addresses pay for no extra lookup. When the old address is missing and the new
    /// pair is already stored, the update reports the existing link and changes
    /// nothing; when the pair is new, the update is forwarded to the inner layers.
    ///
    /// To get [`Error::NotExists`] for a missing address, select that contract
    /// explicitly. Compose [`InnerReferenceExistenceValidator`] *outside* the resolver,
    /// so it runs before the pair lookup. It also checks the new endpoints. Or call
    /// [`Doublets::try_get_link`] before the updates that need it. A validator placed
    /// inside the resolver is never reached for an existing pair.
    ///
    /// ```
    /// use doublets::{decorators::DecoratorsExt, unit, Doublets, Error};
    /// use mem::Global;
    /// # fn main() -> Result<(), Error<u32>> {
    /// let mut store = unit::Store::<u32, _>::new(Global::new())?
    ///     .with_automatic_uniqueness_and_usages_resolution()
    ///     .with_inner_reference_existence_validation();
    /// store.create_point()?;
    /// store.create_point()?;
    /// let link = store.create_link(1, 2)?;
    /// assert!(matches!(store.update(99, 3, 3), Err(Error::NotExists(99))));
    /// assert!(matches!(store.update(99, 1, 2), Err(Error::NotExists(99))));
    /// assert_eq!(store.search(1, 2), Some(link));
    ///
    /// // Without the validator only the pair is resolved: link 99 is not checked.
    /// let mut store = unit::Store::<u32, _>::new(Global::new())?
    ///     .with_automatic_uniqueness_and_usages_resolution();
    /// store.create_point()?;
    /// store.create_point()?;
    /// store.create_link(1, 2)?;
    /// // Per-call alternative: look the address up only where you need the error.
    /// assert!(matches!(store.try_get_link(99), Err(Error::NotExists(99))));
    /// assert_eq!(store.update(99, 1, 2)?, link);
    /// assert_eq!(store.count(), 3);
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [`InnerReferenceExistenceValidator`]: super::InnerReferenceExistenceValidator
    CascadeUniquenessAndUsagesResolver
}

impl<T: LinkReference, L: Doublets<T>> CascadeUniquenessAndUsagesResolver<T, L> {
    /// Port of C# `LinksCascadeUniquenessAndUsagesResolver.ResolveAddressChangeConflict`.
    #[inline]
    fn resolve_address_change_conflict(
        &mut self,
        old: T,
        new: T,
        handler: WriteHandler<'_, T>,
    ) -> Result<Flow, Error<T>> {
        let mut fuse = Fuse::new(&mut *handler);
        // C# uses the facade here so the merge recurses through the whole stack; the
        // static equivalent is `self`, which is this layer plus everything it wraps.
        helpers::merge_usages(self, old, new, |before, after| fuse.call(before, after))?;
        resolve_address_change_conflict(self, old, new, &mut |before, after| {
            fuse.call(before, after)
        })
    }
}

impl<T: LinkReference, L: Doublets<T>> Links<T> for CascadeUniquenessAndUsagesResolver<T, L> {
    forward!(count_links, each_links, create_links, delete_links);

    #[inline]
    fn update_links(
        &mut self,
        query: &[T],
        change: &[T],
        handler: WriteHandler<'_, T>,
    ) -> Result<Flow, Error<T>> {
        let constants = self.links.constants();
        let source = helpers::part(change, constants.source_part);
        let target = helpers::part(change, constants.target_part);
        let old = helpers::part(query, constants.index_part);

        let Some(new) = self.links.search(source, target) else {
            return self.links.update_links(query, change, handler);
        };
        self.resolve_address_change_conflict(old, new, handler)
    }
}

/// Shared body of C# `LinksUniquenessResolver.ResolveAddressChangeConflict`.
///
/// `links` is the decorator itself, standing in for C#'s `_facade`, so the cascading
/// delete runs through every layer of the stack.
#[inline]
fn resolve_address_change_conflict<T, D>(
    links: &mut D,
    old: T,
    new: T,
    handler: WriteHandler<'_, T>,
) -> Result<Flow, Error<T>>
where
    T: LinkReference,
    D: Doublets<T>,
{
    let mut fuse = Fuse::new(&mut *handler);
    if old != new && links.exist(old) {
        links.delete_links(&[old], &mut |before, after| fuse.call(before, after))?;
    }
    let survivor = links.try_get_link(new)?;
    Ok(fuse.call(survivor.clone(), survivor))
}