Skip to main content

prolly/prolly/
policy.rs

1//! Merge policy registry helpers.
2//!
3//! The standard merge API accepts a single resolver closure. This module lets
4//! applications compose domain-specific resolver policies by key prefix, exact
5//! key, or custom key matcher, then pass the registry back to `merge`.
6
7use std::fmt;
8use std::sync::Arc;
9
10use super::error::{Conflict, Resolution, Resolver};
11
12/// Shared merge policy function used by [`MergePolicyRegistry`].
13pub type MergePolicyFn = Arc<dyn Fn(&Conflict) -> Resolution + Send + Sync + 'static>;
14
15type KeyMatcherFn = Arc<dyn Fn(&[u8]) -> bool + Send + Sync + 'static>;
16
17/// Domain-level conflict resolver selected by key.
18#[derive(Clone)]
19pub struct MergePolicyRegistry {
20    rules: Vec<MergePolicyRule>,
21    default: Option<MergePolicyFn>,
22}
23
24impl MergePolicyRegistry {
25    /// Create an empty registry.
26    pub fn new() -> Self {
27        Self {
28            rules: Vec::new(),
29            default: None,
30        }
31    }
32
33    /// Create a registry with a default policy for unmatched keys.
34    pub fn with_default<F>(policy: F) -> Self
35    where
36        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
37    {
38        Self::new().default_policy(policy)
39    }
40
41    /// Set the default policy for unmatched keys.
42    pub fn set_default<F>(&mut self, policy: F) -> &mut Self
43    where
44        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
45    {
46        self.default = Some(Arc::new(policy));
47        self
48    }
49
50    /// Builder-style default policy registration.
51    pub fn default_policy<F>(mut self, policy: F) -> Self
52    where
53        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
54    {
55        self.set_default(policy);
56        self
57    }
58
59    /// Add a policy for keys starting with `prefix`.
60    ///
61    /// Rules are evaluated from newest to oldest, so later rules override
62    /// earlier broader rules.
63    pub fn push_prefix<F>(&mut self, prefix: impl Into<Vec<u8>>, policy: F) -> &mut Self
64    where
65        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
66    {
67        self.rules
68            .push(MergePolicyRule::prefix(prefix.into(), Arc::new(policy)));
69        self
70    }
71
72    /// Builder-style prefix policy registration.
73    pub fn add_prefix<F>(mut self, prefix: impl Into<Vec<u8>>, policy: F) -> Self
74    where
75        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
76    {
77        self.push_prefix(prefix, policy);
78        self
79    }
80
81    /// Add a policy for one exact key.
82    ///
83    /// Rules are evaluated from newest to oldest, so later rules override
84    /// earlier broader rules.
85    pub fn push_exact<F>(&mut self, key: impl Into<Vec<u8>>, policy: F) -> &mut Self
86    where
87        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
88    {
89        self.rules
90            .push(MergePolicyRule::exact(key.into(), Arc::new(policy)));
91        self
92    }
93
94    /// Builder-style exact-key policy registration.
95    pub fn add_exact<F>(mut self, key: impl Into<Vec<u8>>, policy: F) -> Self
96    where
97        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
98    {
99        self.push_exact(key, policy);
100        self
101    }
102
103    /// Add a policy selected by a custom key matcher.
104    ///
105    /// `name` is used only for diagnostics and debugging.
106    pub fn push_pattern<M, F>(
107        &mut self,
108        name: impl Into<String>,
109        matcher: M,
110        policy: F,
111    ) -> &mut Self
112    where
113        M: Fn(&[u8]) -> bool + Send + Sync + 'static,
114        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
115    {
116        self.rules.push(MergePolicyRule::pattern(
117            name.into(),
118            Arc::new(matcher),
119            Arc::new(policy),
120        ));
121        self
122    }
123
124    /// Builder-style custom key matcher policy registration.
125    pub fn add_pattern<M, F>(mut self, name: impl Into<String>, matcher: M, policy: F) -> Self
126    where
127        M: Fn(&[u8]) -> bool + Send + Sync + 'static,
128        F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
129    {
130        self.push_pattern(name, matcher, policy);
131        self
132    }
133
134    /// Number of registered key-specific rules.
135    pub fn len(&self) -> usize {
136        self.rules.len()
137    }
138
139    /// Whether no key-specific rules are registered.
140    pub fn is_empty(&self) -> bool {
141        self.rules.is_empty()
142    }
143
144    /// Whether a default policy is configured for unmatched keys.
145    pub fn has_default(&self) -> bool {
146        self.default.is_some()
147    }
148
149    /// Borrow registered rules in insertion order.
150    pub fn rules(&self) -> &[MergePolicyRule] {
151        &self.rules
152    }
153
154    /// Return the newest matching key-specific rule, if any.
155    pub fn matching_rule(&self, key: &[u8]) -> Option<&MergePolicyRule> {
156        self.rules.iter().rev().find(|rule| rule.matches(key))
157    }
158
159    /// Resolve one conflict using the newest matching rule or the default.
160    ///
161    /// If no rule matches and no default policy exists, the conflict is left
162    /// unresolved.
163    pub fn resolve(&self, conflict: &Conflict) -> Resolution {
164        if let Some(rule) = self.matching_rule(&conflict.key) {
165            return (rule.policy.as_ref())(conflict);
166        }
167
168        self.default
169            .as_ref()
170            .map(|policy| (policy.as_ref())(conflict))
171            .unwrap_or_else(Resolution::unresolved)
172    }
173
174    /// Convert this registry into a standard merge resolver.
175    pub fn into_resolver(self) -> Resolver {
176        Box::new(move |conflict| self.resolve(conflict))
177    }
178
179    /// Clone this registry into a standard merge resolver.
180    pub fn as_resolver(&self) -> Resolver {
181        self.clone().into_resolver()
182    }
183}
184
185impl Default for MergePolicyRegistry {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191impl fmt::Debug for MergePolicyRegistry {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        f.debug_struct("MergePolicyRegistry")
194            .field("rules", &self.rules)
195            .field("has_default", &self.has_default())
196            .finish()
197    }
198}
199
200/// One registered merge policy rule.
201#[derive(Clone)]
202pub struct MergePolicyRule {
203    matcher: MergePolicyMatcher,
204    policy: MergePolicyFn,
205}
206
207impl MergePolicyRule {
208    fn prefix(prefix: Vec<u8>, policy: MergePolicyFn) -> Self {
209        Self {
210            matcher: MergePolicyMatcher::Prefix(prefix),
211            policy,
212        }
213    }
214
215    fn exact(key: Vec<u8>, policy: MergePolicyFn) -> Self {
216        Self {
217            matcher: MergePolicyMatcher::Exact(key),
218            policy,
219        }
220    }
221
222    fn pattern(name: String, matcher: KeyMatcherFn, policy: MergePolicyFn) -> Self {
223        Self {
224            matcher: MergePolicyMatcher::Pattern { name, matcher },
225            policy,
226        }
227    }
228
229    /// Return true when this rule applies to `key`.
230    pub fn matches(&self, key: &[u8]) -> bool {
231        match &self.matcher {
232            MergePolicyMatcher::Prefix(prefix) => key.starts_with(prefix),
233            MergePolicyMatcher::Exact(exact) => key == exact,
234            MergePolicyMatcher::Pattern { matcher, .. } => (matcher.as_ref())(key),
235        }
236    }
237
238    /// Return a human-oriented rule label for debugging.
239    pub fn label(&self) -> MergePolicyRuleLabel<'_> {
240        match &self.matcher {
241            MergePolicyMatcher::Prefix(prefix) => MergePolicyRuleLabel::Prefix(prefix),
242            MergePolicyMatcher::Exact(key) => MergePolicyRuleLabel::Exact(key),
243            MergePolicyMatcher::Pattern { name, .. } => MergePolicyRuleLabel::Pattern(name),
244        }
245    }
246}
247
248impl fmt::Debug for MergePolicyRule {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        f.debug_struct("MergePolicyRule")
251            .field("label", &self.label())
252            .finish_non_exhaustive()
253    }
254}
255
256#[derive(Clone)]
257enum MergePolicyMatcher {
258    Prefix(Vec<u8>),
259    Exact(Vec<u8>),
260    Pattern { name: String, matcher: KeyMatcherFn },
261}
262
263/// Human-readable label for a merge policy rule.
264#[derive(Clone, Copy, Debug, PartialEq, Eq)]
265pub enum MergePolicyRuleLabel<'a> {
266    /// Prefix-based rule.
267    Prefix(&'a [u8]),
268    /// Exact-key rule.
269    Exact(&'a [u8]),
270    /// Custom matcher rule.
271    Pattern(&'a str),
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::prolly::error::resolver;
278
279    fn conflict(key: &[u8]) -> Conflict {
280        Conflict {
281            key: key.to_vec(),
282            base: Some(b"base".to_vec()),
283            left: Some(b"left".to_vec()),
284            right: Some(b"right".to_vec()),
285        }
286    }
287
288    #[test]
289    fn newest_matching_rule_wins() {
290        let registry = MergePolicyRegistry::new()
291            .add_prefix(b"settings/".to_vec(), resolver::prefer_left)
292            .add_exact(b"settings/theme".to_vec(), resolver::prefer_right);
293
294        let result = registry.resolve(&conflict(b"settings/theme"));
295        assert_eq!(result, Resolution::value(b"right".to_vec()));
296
297        let rule = registry.matching_rule(b"settings/theme").unwrap();
298        assert_eq!(rule.label(), MergePolicyRuleLabel::Exact(b"settings/theme"));
299    }
300
301    #[test]
302    fn pattern_and_default_policies_work() {
303        let registry = MergePolicyRegistry::with_default(|_| Resolution::unresolved()).add_pattern(
304            "summary",
305            |key| key.ends_with(b"/summary"),
306            |conflict| {
307                let mut value = conflict.left.clone().unwrap_or_default();
308                value.push(b'\n');
309                value.extend(conflict.right.clone().unwrap_or_default());
310                Resolution::value(value)
311            },
312        );
313
314        assert_eq!(
315            registry.resolve(&conflict(b"documents/42/summary")),
316            Resolution::value(b"left\nright".to_vec())
317        );
318        assert_eq!(
319            registry.resolve(&conflict(b"documents/42/title")),
320            Resolution::unresolved()
321        );
322    }
323}