Skip to content

Commit 85e9e5a

Browse files
committed
Add merge-dict conflict resolver spec and Implement in iOS
* Added merge-dict conflict resolver spec. * Bump API spec version to 2.0.1. * Implemented merge-dict resolver in iOS.
1 parent 0077c1f commit 85e9e5a

4 files changed

Lines changed: 79 additions & 1 deletion

File tree

servers/ios/TestServer/Server/TestServer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class TestServer : ObservableObject {
5454
setupRoutes()
5555
}
5656

57-
/// Implement API v1.2.1 (No support for start/stop listener - 1.1.0, and dynamic dataset - 1.1.1 yet)
57+
/// Implement API v1.2.1 + Merge-Dict Conflict Resolver defined in 2.0.1.
5858
private func setupRoutes() {
5959
app.get("", use: Handlers.getRoot)
6060
app.post("newSession", use: Handlers.newSession)

servers/ios/TestServer/Utils/ReplicationConflictResolverFactory.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,61 @@ struct ReplicationConflictResolverFactory {
6464
}
6565
}
6666

67+
struct MergeDictResolver : AnyConflictResolver {
68+
let property: String
69+
70+
func resolve(peerID: PeerID?, conflict: Conflict) -> Document? {
71+
if conflict.localDocument == nil || conflict.remoteDocument == nil {
72+
return nil
73+
}
74+
75+
let doc = conflict.remoteDocument!.toMutable()
76+
77+
guard let localDict = conflict.localDocument!.dictionary(forKey: property) else {
78+
return doc.setString("Both values are not dictionary", forKey: property)
79+
}
80+
81+
guard let remoteDict = conflict.remoteDocument!.dictionary(forKey: property) else {
82+
return doc.setString("Both values are not dictionary", forKey: property)
83+
}
84+
85+
let mergedDict = MutableDictionaryObject()
86+
87+
for key in localDict {
88+
mergedDict.setValue(localDict.value(forKey: key), forKey: key)
89+
}
90+
91+
for key in remoteDict {
92+
let remoteValue = remoteDict.value(forKey: key)!
93+
if let curValue = mergedDict.value(forKey: key) {
94+
if !isEquals(curValue, remoteValue) {
95+
return doc.setString("Conflicting values found at key named '\(key)'", forKey: property)
96+
}
97+
}
98+
mergedDict.setValue(remoteValue, forKey: key)
99+
}
100+
101+
return doc.setValue(mergedDict, forKey: property)
102+
}
103+
104+
private func isEquals(_ lhs: Any, _ rhs: Any) -> Bool {
105+
switch (lhs, rhs) {
106+
case let (l as String, r as String): return l == r
107+
case let (l as NSNumber, r as NSNumber): return l == r
108+
case let (l as DictionaryObject, r as DictionaryObject): return l == r
109+
case let (l as ArrayObject, r as ArrayObject): return l == r
110+
case let (l as Blob, r as Blob): return l == r
111+
default: return false
112+
}
113+
}
114+
}
115+
67116
private enum ConflictResolverType : String {
68117
case localWins = "local-wins"
69118
case removeWins = "remote-wins"
70119
case delete = "delete"
71120
case merge = "merge"
121+
case mergeDict = "merge-dict"
72122
}
73123

74124
static func getResolver(
@@ -90,6 +140,11 @@ struct ReplicationConflictResolverFactory {
90140
throw TestServerError.badRequest("The property parameter is missing for the merge conflict resolver")
91141
}
92142
return ConflictResolver(MergeResolver(property: property))
143+
case .mergeDict:
144+
guard let property = params?["property"]?.value as? String else {
145+
throw TestServerError.badRequest("The property parameter is missing for the merge-dict conflict resolver")
146+
}
147+
return ConflictResolver(MergeDictResolver(property: property))
93148
}
94149
}
95150
}

spec/api/api.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ info:
4646
```
4747
4848
Changes
49+
2.0.1 (07/10/2025)
50+
* Add Merge-Dict Conflict Resolver
51+
4952
2.0.0 (06/13/2025)
5053
* This is a breaking change: CBLTest-API-Version is now "2"
5154
* deprecate dataset_version

spec/api/conflict-resolvers.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,23 @@ For simplicity, restrict to top level keys.
4040
| :--------- | ----------- |
4141
| property| `<top level key>` |
4242

43+
### merge-dict
44+
45+
Performs a merge of the specified dictionary property by combining the two dictionaries. For simplicity, restrict to top level keys.
46+
47+
* If one of the values is not a dictionary, the merged value will be set as:
48+
```
49+
{"error": "Both values are not dictionary"}
50+
```
51+
* For any duplicated keys, the values must be the same, otherwise, the merged value will be set as:
52+
```
53+
{"error": "Conflicting values found at key named 'key-name'"}
54+
```
55+
56+
**name** : `merge-dict`
57+
58+
**params** :
59+
60+
| Key | Value |
61+
| :--------- | ----------- |
62+
| property| `<top level key>` |

0 commit comments

Comments
 (0)