1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2023-2024 Hugo Osvaldo Barrera
//
// SPDX-License-Identifier: EUPL-1.2

//! Types for specifying rules for a synchronisation.
use std::sync::Arc;

use crate::{
    base::{Item, Storage},
    CollectionId, Href,
};

/// A collection declared either via its `href` or `collection_id`.
#[derive(Debug, Clone)]
pub enum CollectionDescription {
    Id { id: CollectionId },
    Href { href: Href },
}

impl CollectionDescription {
    pub(crate) fn alias(&self) -> String {
        match self {
            CollectionDescription::Id { id } => id.to_string(),
            CollectionDescription::Href { href } => format!("href:{href}"),
        }
    }
}

/// A mapping between of a pair of collections across storages.
///
/// This is an unresolved mapping which may be lacking information on one side.
#[derive(Debug, Clone)]
pub enum DeclaredMapping {
    /// Copy between two collections with the same definition on both sides.
    ///
    /// Usage of [`CollectionDescription::Href`] between different storage implementations is
    /// discouraged.
    Direct { description: CollectionDescription },
    /// Copy between two collections with explicit definitions on both sides.
    Mapped {
        /// A descriptive name used for logging and display.
        alias: String,
        a: CollectionDescription,
        b: CollectionDescription,
    },
}

impl DeclaredMapping {
    /// Create a direct mapping.
    ///
    /// This creates the simplest kind of mapping: it maps two collections with the same
    /// [`CollectionId`].
    #[must_use]
    pub fn direct(id: CollectionId) -> Self {
        DeclaredMapping::Direct {
            description: CollectionDescription::Id { id },
        }
    }
}

/// A pair of storage that are to be synchronised.
///
/// This type merely wraps around the declaration of what shall be synchronised. It can be
/// constructed offline and is the entry point to create a [`Plan`] and then execute it.
///
/// New pairs can be created via [`StoragePair::new`].
///
/// [`Plan`]: crate::sync::plan::Plan
pub struct StoragePair<I: Item> {
    pub(super) storage_a: Arc<dyn Storage<I>>,
    pub(super) storage_b: Arc<dyn Storage<I>>,
    pub(super) mappings: Vec<DeclaredMapping>,
    pub(super) all_from_a: bool,
    pub(super) all_from_b: bool,
    pub(super) on_empty: OnEmpty,
}

impl<I: Item> StoragePair<I> {
    /// Create a new instance.
    ///
    /// By default, no collections are to be synchronised. See other associated functions for
    /// details con configuring additional collections.
    #[must_use]
    pub fn new(storage_a: Arc<dyn Storage<I>>, storage_b: Arc<dyn Storage<I>>) -> StoragePair<I> {
        StoragePair {
            storage_a,
            storage_b,
            mappings: Vec::new(),
            all_from_a: false,
            all_from_b: false,
            on_empty: OnEmpty::Skip,
        }
    }

    /// Include the specified mapping when synchronising.
    #[must_use]
    pub fn with_mapping(mut self, mapping: DeclaredMapping) -> Self {
        self.mappings.push(mapping);
        self
    }

    /// Include all collections from storage A when synchronising.
    ///
    /// By default, only explicitly included collections are synchronised.
    #[must_use]
    pub fn with_all_from_a(mut self) -> Self {
        self.all_from_a = true;
        self
    }

    /// Include all collections from storage B when synchronising.
    ///
    /// By default, only explicitly included collections are synchronised.
    #[must_use]
    pub fn with_all_from_b(mut self) -> Self {
        self.all_from_b = true;
        self
    }

    /// Returns a reference to storage a.
    #[must_use]
    pub fn storage_a(&self) -> &dyn Storage<I> {
        self.storage_a.as_ref()
    }

    /// Returns a reference to storage a.
    #[must_use]
    pub fn storage_b(&self) -> &dyn Storage<I> {
        self.storage_b.as_ref()
    }

    /// Action to take when a collection is completely emptied.
    #[must_use]
    pub fn on_empty(mut self, action: OnEmpty) -> Self {
        self.on_empty = action;
        self
    }
}

#[derive(Debug, PartialEq, Default)]
pub enum OnEmpty {
    #[default]
    Skip,
    Sync,
}