Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion servers/ios/TestServer/Server/TestServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class TestServer : ObservableObject {
setupRoutes()
}

/// Implement API v1.2.1 (No support for start/stop listener - 1.1.0, and dynamic dataset - 1.1.1 yet)
/// Implement API v1.2.1 + Merge-Dict Conflict Resolver defined in 2.0.1.
private func setupRoutes() {
app.get("", use: Handlers.getRoot)
app.post("newSession", use: Handlers.newSession)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,61 @@ struct ReplicationConflictResolverFactory {
}
}

struct MergeDictResolver : AnyConflictResolver {
let property: String

func resolve(peerID: PeerID?, conflict: Conflict) -> Document? {
if conflict.localDocument == nil || conflict.remoteDocument == nil {
return nil
}

let doc = conflict.remoteDocument!.toMutable()

guard let localDict = conflict.localDocument!.dictionary(forKey: property) else {
return doc.setString("Both values are not dictionary", forKey: property)
}

guard let remoteDict = conflict.remoteDocument!.dictionary(forKey: property) else {
return doc.setString("Both values are not dictionary", forKey: property)
}

let mergedDict = MutableDictionaryObject()

for key in localDict {
mergedDict.setValue(localDict.value(forKey: key), forKey: key)
}

for key in remoteDict {
let remoteValue = remoteDict.value(forKey: key)!
if let curValue = mergedDict.value(forKey: key) {
if !isEquals(curValue, remoteValue) {
return doc.setString("Conflicting values found at key named '\(key)'", forKey: property)
}
}
mergedDict.setValue(remoteValue, forKey: key)
}

return doc.setValue(mergedDict, forKey: property)
}

private func isEquals(_ lhs: Any, _ rhs: Any) -> Bool {
switch (lhs, rhs) {
case let (l as String, r as String): return l == r
case let (l as NSNumber, r as NSNumber): return l == r
case let (l as DictionaryObject, r as DictionaryObject): return l == r
case let (l as ArrayObject, r as ArrayObject): return l == r
case let (l as Blob, r as Blob): return l == r
default: return false
}
}
}

private enum ConflictResolverType : String {
case localWins = "local-wins"
case removeWins = "remote-wins"
case delete = "delete"
case merge = "merge"
case mergeDict = "merge-dict"
}

static func getResolver(
Expand All @@ -90,6 +140,11 @@ struct ReplicationConflictResolverFactory {
throw TestServerError.badRequest("The property parameter is missing for the merge conflict resolver")
}
return ConflictResolver(MergeResolver(property: property))
case .mergeDict:
guard let property = params?["property"]?.value as? String else {
throw TestServerError.badRequest("The property parameter is missing for the merge-dict conflict resolver")
}
return ConflictResolver(MergeDictResolver(property: property))
}
}
}
3 changes: 3 additions & 0 deletions spec/api/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ info:
```

Changes
2.0.1 (07/10/2025)
* Add Merge-Dict Conflict Resolver

2.0.0 (06/13/2025)
* This is a breaking change: CBLTest-API-Version is now "2"
* deprecate dataset_version
Expand Down
20 changes: 20 additions & 0 deletions spec/api/conflict-resolvers.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,23 @@ For simplicity, restrict to top level keys.
| :--------- | ----------- |
| property| `<top level key>` |

### merge-dict

Performs a merge of the specified dictionary property by combining the two dictionaries. For simplicity, restrict to top level keys.

* If one of the values is not a dictionary, the merged value will be set as:
```
{"error": "Both values are not dictionary"}
```
* For any duplicated keys, the values must be the same, otherwise, the merged value will be set as:
```
{"error": "Conflicting values found at key named 'key-name'"}
```

**name** : `merge-dict`

**params** :

| Key | Value |
| :--------- | ----------- |
| property| `<top level key>` |