use std::fmt;
use std::sync::Arc;
use super::error::{Conflict, Resolution, Resolver};
pub type MergePolicyFn = Arc<dyn Fn(&Conflict) -> Resolution + Send + Sync + 'static>;
type KeyMatcherFn = Arc<dyn Fn(&[u8]) -> bool + Send + Sync + 'static>;
#[derive(Clone)]
pub struct MergePolicyRegistry {
rules: Vec<MergePolicyRule>,
default: Option<MergePolicyFn>,
}
impl MergePolicyRegistry {
pub fn new() -> Self {
Self {
rules: Vec::new(),
default: None,
}
}
pub fn with_default<F>(policy: F) -> Self
where
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
Self::new().default_policy(policy)
}
pub fn set_default<F>(&mut self, policy: F) -> &mut Self
where
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.default = Some(Arc::new(policy));
self
}
pub fn default_policy<F>(mut self, policy: F) -> Self
where
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.set_default(policy);
self
}
pub fn push_prefix<F>(&mut self, prefix: impl Into<Vec<u8>>, policy: F) -> &mut Self
where
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.rules
.push(MergePolicyRule::prefix(prefix.into(), Arc::new(policy)));
self
}
pub fn add_prefix<F>(mut self, prefix: impl Into<Vec<u8>>, policy: F) -> Self
where
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.push_prefix(prefix, policy);
self
}
pub fn push_exact<F>(&mut self, key: impl Into<Vec<u8>>, policy: F) -> &mut Self
where
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.rules
.push(MergePolicyRule::exact(key.into(), Arc::new(policy)));
self
}
pub fn add_exact<F>(mut self, key: impl Into<Vec<u8>>, policy: F) -> Self
where
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.push_exact(key, policy);
self
}
pub fn push_pattern<M, F>(
&mut self,
name: impl Into<String>,
matcher: M,
policy: F,
) -> &mut Self
where
M: Fn(&[u8]) -> bool + Send + Sync + 'static,
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.rules.push(MergePolicyRule::pattern(
name.into(),
Arc::new(matcher),
Arc::new(policy),
));
self
}
pub fn add_pattern<M, F>(mut self, name: impl Into<String>, matcher: M, policy: F) -> Self
where
M: Fn(&[u8]) -> bool + Send + Sync + 'static,
F: Fn(&Conflict) -> Resolution + Send + Sync + 'static,
{
self.push_pattern(name, matcher, policy);
self
}
pub fn len(&self) -> usize {
self.rules.len()
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn has_default(&self) -> bool {
self.default.is_some()
}
pub fn rules(&self) -> &[MergePolicyRule] {
&self.rules
}
pub fn matching_rule(&self, key: &[u8]) -> Option<&MergePolicyRule> {
self.rules.iter().rev().find(|rule| rule.matches(key))
}
pub fn resolve(&self, conflict: &Conflict) -> Resolution {
if let Some(rule) = self.matching_rule(&conflict.key) {
return (rule.policy.as_ref())(conflict);
}
self.default
.as_ref()
.map(|policy| (policy.as_ref())(conflict))
.unwrap_or_else(Resolution::unresolved)
}
pub fn into_resolver(self) -> Resolver {
Box::new(move |conflict| self.resolve(conflict))
}
pub fn as_resolver(&self) -> Resolver {
self.clone().into_resolver()
}
}
impl Default for MergePolicyRegistry {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for MergePolicyRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MergePolicyRegistry")
.field("rules", &self.rules)
.field("has_default", &self.has_default())
.finish()
}
}
#[derive(Clone)]
pub struct MergePolicyRule {
matcher: MergePolicyMatcher,
policy: MergePolicyFn,
}
impl MergePolicyRule {
fn prefix(prefix: Vec<u8>, policy: MergePolicyFn) -> Self {
Self {
matcher: MergePolicyMatcher::Prefix(prefix),
policy,
}
}
fn exact(key: Vec<u8>, policy: MergePolicyFn) -> Self {
Self {
matcher: MergePolicyMatcher::Exact(key),
policy,
}
}
fn pattern(name: String, matcher: KeyMatcherFn, policy: MergePolicyFn) -> Self {
Self {
matcher: MergePolicyMatcher::Pattern { name, matcher },
policy,
}
}
pub fn matches(&self, key: &[u8]) -> bool {
match &self.matcher {
MergePolicyMatcher::Prefix(prefix) => key.starts_with(prefix),
MergePolicyMatcher::Exact(exact) => key == exact,
MergePolicyMatcher::Pattern { matcher, .. } => (matcher.as_ref())(key),
}
}
pub fn label(&self) -> MergePolicyRuleLabel<'_> {
match &self.matcher {
MergePolicyMatcher::Prefix(prefix) => MergePolicyRuleLabel::Prefix(prefix),
MergePolicyMatcher::Exact(key) => MergePolicyRuleLabel::Exact(key),
MergePolicyMatcher::Pattern { name, .. } => MergePolicyRuleLabel::Pattern(name),
}
}
}
impl fmt::Debug for MergePolicyRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MergePolicyRule")
.field("label", &self.label())
.finish_non_exhaustive()
}
}
#[derive(Clone)]
enum MergePolicyMatcher {
Prefix(Vec<u8>),
Exact(Vec<u8>),
Pattern { name: String, matcher: KeyMatcherFn },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MergePolicyRuleLabel<'a> {
Prefix(&'a [u8]),
Exact(&'a [u8]),
Pattern(&'a str),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prolly::error::resolver;
fn conflict(key: &[u8]) -> Conflict {
Conflict {
key: key.to_vec(),
base: Some(b"base".to_vec()),
left: Some(b"left".to_vec()),
right: Some(b"right".to_vec()),
}
}
#[test]
fn newest_matching_rule_wins() {
let registry = MergePolicyRegistry::new()
.add_prefix(b"settings/".to_vec(), resolver::prefer_left)
.add_exact(b"settings/theme".to_vec(), resolver::prefer_right);
let result = registry.resolve(&conflict(b"settings/theme"));
assert_eq!(result, Resolution::value(b"right".to_vec()));
let rule = registry.matching_rule(b"settings/theme").unwrap();
assert_eq!(rule.label(), MergePolicyRuleLabel::Exact(b"settings/theme"));
}
#[test]
fn pattern_and_default_policies_work() {
let registry = MergePolicyRegistry::with_default(|_| Resolution::unresolved()).add_pattern(
"summary",
|key| key.ends_with(b"/summary"),
|conflict| {
let mut value = conflict.left.clone().unwrap_or_default();
value.push(b'\n');
value.extend(conflict.right.clone().unwrap_or_default());
Resolution::value(value)
},
);
assert_eq!(
registry.resolve(&conflict(b"documents/42/summary")),
Resolution::value(b"left\nright".to_vec())
);
assert_eq!(
registry.resolve(&conflict(b"documents/42/title")),
Resolution::unresolved()
);
}
}