Skip to main content

doublets/decorators/
uniqueness.rs

1//! Decorators that keep `(source, target)` pairs unique.
2//!
3//! Ports of C# `LinksUniquenessValidator`, `LinksUniquenessResolver` and
4//! `LinksCascadeUniquenessAndUsagesResolver`.
5
6use std::{
7    fmt::{self, Debug, Formatter},
8    marker::PhantomData,
9};
10
11use data::{Flow, LinkReference, LinksConstants};
12
13use super::{
14    helpers,
15    macros::{decorator_struct, forward},
16};
17use crate::{
18    data::{ReadHandler, WriteHandler},
19    Doublets, Error, Fuse, Link, Links,
20};
21
22decorator_struct! {
23    /// Rejects an update that would make two links share the same `(source, target)` pair.
24    ///
25    /// Port of C# `LinksUniquenessValidator`. The update fails with
26    /// [`Error::AlreadyExists`], the analogue of C#'s
27    /// `LinkWithSameValueAlreadyExistsException`.
28    UniquenessValidator
29}
30
31impl<T: LinkReference, L: Doublets<T>> Links<T> for UniquenessValidator<T, L> {
32    forward!(count_links, each_links, create_links, delete_links);
33
34    #[inline]
35    fn update_links(
36        &mut self,
37        query: &[T],
38        change: &[T],
39        handler: WriteHandler<'_, T>,
40    ) -> Result<Flow, Error<T>> {
41        let constants = self.links.constants();
42        let source = helpers::part(change, constants.source_part);
43        let target = helpers::part(change, constants.target_part);
44        helpers::ensure_does_not_exist(&self.links, source, target)?;
45        self.links.update_links(query, change, handler)
46    }
47}
48
49decorator_struct! {
50    /// Redirects an update that would produce a duplicate to the link that already exists,
51    /// then deletes the now-redundant link.
52    ///
53    /// Port of C# `LinksUniquenessResolver`. Composed with the default
54    /// [`Doublets::create_link`] this turns `create_link` into the equivalent of
55    /// [`Doublets::get_or_create`]: the temporary link created first is deleted, and the
56    /// surviving link is reported to the handler.
57    ///
58    /// # Deviation from C\#
59    ///
60    /// C# reports nothing for the surviving link, so `CreateAndUpdate` returns the address
61    /// of the link it just deleted. This port reports the surviving link as
62    /// `(before, after)` instead, so [`Doublets::update_by`] and [`Doublets::create_link`]
63    /// return an address that is actually valid.
64    UniquenessResolver
65}
66
67impl<T: LinkReference, L: Doublets<T>> UniquenessResolver<T, L> {
68    /// Port of C# `LinksUniquenessResolver.ResolveAddressChangeConflict`.
69    #[inline]
70    fn resolve_address_change_conflict(
71        &mut self,
72        old: T,
73        new: T,
74        handler: WriteHandler<'_, T>,
75    ) -> Result<Flow, Error<T>> {
76        resolve_address_change_conflict(self, old, new, handler)
77    }
78}
79
80impl<T: LinkReference, L: Doublets<T>> Links<T> for UniquenessResolver<T, L> {
81    forward!(count_links, each_links, create_links, delete_links);
82
83    #[inline]
84    fn update_links(
85        &mut self,
86        query: &[T],
87        change: &[T],
88        handler: WriteHandler<'_, T>,
89    ) -> Result<Flow, Error<T>> {
90        let constants = self.links.constants();
91        let source = helpers::part(change, constants.source_part);
92        let target = helpers::part(change, constants.target_part);
93        let old = helpers::part(query, constants.index_part);
94
95        let Some(new) = self.links.search(source, target) else {
96            return self.links.update_links(query, change, handler);
97        };
98        self.resolve_address_change_conflict(old, new, handler)
99    }
100}
101
102decorator_struct! {
103    /// Like [`UniquenessResolver`], but first re-points every link that referenced the
104    /// redundant link at the surviving one.
105    ///
106    /// Port of C# `LinksCascadeUniquenessAndUsagesResolver`.
107    CascadeUniquenessAndUsagesResolver
108}
109
110impl<T: LinkReference, L: Doublets<T>> CascadeUniquenessAndUsagesResolver<T, L> {
111    /// Port of C# `LinksCascadeUniquenessAndUsagesResolver.ResolveAddressChangeConflict`.
112    #[inline]
113    fn resolve_address_change_conflict(
114        &mut self,
115        old: T,
116        new: T,
117        handler: WriteHandler<'_, T>,
118    ) -> Result<Flow, Error<T>> {
119        let mut fuse = Fuse::new(&mut *handler);
120        // C# uses the facade here so the merge recurses through the whole stack; the
121        // static equivalent is `self`, which is this layer plus everything it wraps.
122        helpers::merge_usages(self, old, new, |before, after| fuse.call(before, after))?;
123        resolve_address_change_conflict(self, old, new, &mut |before, after| {
124            fuse.call(before, after)
125        })
126    }
127}
128
129impl<T: LinkReference, L: Doublets<T>> Links<T> for CascadeUniquenessAndUsagesResolver<T, L> {
130    forward!(count_links, each_links, create_links, delete_links);
131
132    #[inline]
133    fn update_links(
134        &mut self,
135        query: &[T],
136        change: &[T],
137        handler: WriteHandler<'_, T>,
138    ) -> Result<Flow, Error<T>> {
139        let constants = self.links.constants();
140        let source = helpers::part(change, constants.source_part);
141        let target = helpers::part(change, constants.target_part);
142        let old = helpers::part(query, constants.index_part);
143
144        let Some(new) = self.links.search(source, target) else {
145            return self.links.update_links(query, change, handler);
146        };
147        self.resolve_address_change_conflict(old, new, handler)
148    }
149}
150
151/// Shared body of C# `LinksUniquenessResolver.ResolveAddressChangeConflict`.
152///
153/// `links` is the decorator itself, standing in for C#'s `_facade`, so the cascading
154/// delete runs through every layer of the stack.
155#[inline]
156fn resolve_address_change_conflict<T, D>(
157    links: &mut D,
158    old: T,
159    new: T,
160    handler: WriteHandler<'_, T>,
161) -> Result<Flow, Error<T>>
162where
163    T: LinkReference,
164    D: Doublets<T>,
165{
166    let mut fuse = Fuse::new(&mut *handler);
167    if old != new && links.exist(old) {
168        links.delete_links(&[old], &mut |before, after| fuse.call(before, after))?;
169    }
170    let survivor = links.try_get_link(new)?;
171    Ok(fuse.call(survivor.clone(), survivor))
172}