Skip to content

Commit 99626bf

Browse files
authored
SWIFT-372 Add bsonEquals to BSONValue protocol (#230)
1 parent fc7bdc1 commit 99626bf

File tree

5 files changed

+66
-49
lines changed

5 files changed

+66
-49
lines changed

Sources/MongoSwift/BSON/AnyBSONValue.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public struct AnyBSONValue: Codable, Equatable, Hashable {
6363
}
6464

6565
public static func == (lhs: AnyBSONValue, rhs: AnyBSONValue) -> Bool {
66-
return bsonEquals(lhs.value, rhs.value)
66+
return lhs.value.bsonEquals(rhs.value)
6767
}
6868

6969
/**

Sources/MongoSwift/BSON/BSONEncoder.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,10 @@ private class MutableArray: BSONValue {
751751
required convenience init(from decoder: Decoder) throws {
752752
fatalError("`MutableArray` is not meant to be initialized from a `Decoder`")
753753
}
754+
755+
func bsonEquals(_ other: BSONValue?) -> Bool {
756+
return self.array.bsonEquals(other)
757+
}
754758
}
755759

756760
/// A private class wrapping a Swift dictionary so we can pass it by reference
@@ -799,6 +803,13 @@ private class MutableDictionary: BSONValue {
799803

800804
init() {}
801805

806+
func bsonEquals(_ other: BSONValue?) -> Bool {
807+
guard let otherDict = other as? MutableDictionary else {
808+
return false
809+
}
810+
return otherDict.keys == self.keys && otherDict.values.bsonEquals(self.values)
811+
}
812+
802813
/// methods required by the BSONValue protocol that we don't actually need/use. MutableDictionary
803814
/// is just a BSONValue to simplify usage alongside true BSONValues within the encoder.
804815
public static func from(iterator iter: DocumentIterator) -> Self {

Sources/MongoSwift/BSON/BSONValue.swift

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -56,29 +56,53 @@ public protocol BSONValue {
5656
var bsonType: BSONType { get }
5757

5858
/**
59-
* Given the `DocumentStorage` backing a `Document`, appends this `BSONValue` to the end.
60-
*
61-
* - Parameters:
62-
* - storage: A `DocumentStorage` to write to.
63-
* - key: A `String`, the key under which to store the value.
64-
*
65-
* - Throws:
66-
* - `RuntimeError.internalError` if the `DocumentStorage` would exceed the maximum size by encoding this
67-
* key-value pair.
68-
* - `UserError.logicError` if the value is an `Array` and it contains a non-`BSONValue` element.
69-
*/
59+
* Given the `DocumentStorage` backing a `Document`, appends this `BSONValue` to the end.
60+
*
61+
* - Parameters:
62+
* - storage: A `DocumentStorage` to write to.
63+
* - key: A `String`, the key under which to store the value.
64+
*
65+
* - Throws:
66+
* - `RuntimeError.internalError` if the `DocumentStorage` would exceed the maximum size by encoding this
67+
* key-value pair.
68+
* - `UserError.logicError` if the value is an `Array` and it contains a non-`BSONValue` element.
69+
*/
7070
func encode(to storage: DocumentStorage, forKey key: String) throws
7171

7272
/**
73-
* Given a `DocumentIterator` known to have a next value of this type,
74-
* initializes the value.
75-
*
76-
* - Throws: `UserError.logicError` if the current type of the `DocumentIterator` does not correspond to the
77-
* associated type of this `BSONValue`.
78-
*/
73+
* Function to test equality with another `BSONValue`. This function tests for exact BSON equality.
74+
* This means that differing types with equivalent value are not equivalent.
75+
*
76+
* e.g.
77+
* 4.0 (Double) != 4 (Int)
78+
*
79+
* - Parameters:
80+
* - other: The right-hand-side `BSONValue` to compare.
81+
*
82+
* - Returns: `true` if `self` is equal to `rhs`, `false` otherwise.
83+
*/
84+
func bsonEquals(_ other: BSONValue?) -> Bool
85+
86+
/**
87+
* Given a `DocumentIterator` known to have a next value of this type,
88+
* initializes the value.
89+
*
90+
* - Throws: `UserError.logicError` if the current type of the `DocumentIterator` does not correspond to the
91+
* associated type of this `BSONValue`.
92+
*/
7993
static func from(iterator iter: DocumentIterator) throws -> Self
8094
}
8195

96+
extension BSONValue where Self: Equatable {
97+
/// Default implementation of `bsonEquals` for `BSONValue`s that conform to `Equatable`.
98+
public func bsonEquals(_ other: BSONValue?) -> Bool {
99+
guard let otherAsSelf = other as? Self else {
100+
return false
101+
}
102+
return self == otherAsSelf
103+
}
104+
}
105+
82106
/// An extension of `Array` to represent the BSON array type.
83107
extension Array: BSONValue {
84108
public var bsonType: BSONType { return .array }
@@ -130,6 +154,13 @@ extension Array: BSONValue {
130154
throw bsonTooLargeError(value: self, forKey: key)
131155
}
132156
}
157+
158+
public func bsonEquals(_ other: BSONValue?) -> Bool {
159+
guard let otherArr = other as? [BSONValue], let selfArr = self as? [BSONValue] else {
160+
return false
161+
}
162+
return self.count == otherArr.count && zip(selfArr, otherArr).allSatisfy { lhs, rhs in lhs.bsonEquals(rhs) }
163+
}
133164
}
134165

135166
/// A struct to represent the BSON null type.
@@ -1082,8 +1113,6 @@ public struct BSONUndefined: BSONValue, Equatable, Codable {
10821113
}
10831114
}
10841115

1085-
// See https://github.com/realm/SwiftLint/issues/461
1086-
// swiftlint:disable cyclomatic_complexity
10871116
/**
10881117
* A helper function to test equality between two `BSONValue`s. This function tests for exact BSON equality.
10891118
* This means that differing types with equivalent value are not equivalent.
@@ -1100,33 +1129,9 @@ public struct BSONUndefined: BSONValue, Equatable, Codable {
11001129
*
11011130
* - Returns: `true` if `lhs` is equal to `rhs`, `false` otherwise.
11021131
*/
1132+
@available(*, deprecated, message: "Use lhs.bsonEquals(rhs) instead")
11031133
public func bsonEquals(_ lhs: BSONValue, _ rhs: BSONValue) -> Bool {
1104-
switch (lhs, rhs) {
1105-
case let (l as Int, r as Int): return l == r
1106-
case let (l as Int32, r as Int32): return l == r
1107-
case let (l as Int64, r as Int64): return l == r
1108-
case let (l as Double, r as Double): return l == r
1109-
case let (l as Decimal128, r as Decimal128): return l == r
1110-
case let (l as Bool, r as Bool): return l == r
1111-
case let (l as String, r as String): return l == r
1112-
case let (l as RegularExpression, r as RegularExpression): return l == r
1113-
case let (l as Timestamp, r as Timestamp): return l == r
1114-
case let (l as Date, r as Date): return l == r
1115-
case (_ as MinKey, _ as MinKey): return true
1116-
case (_ as MaxKey, _ as MaxKey): return true
1117-
case let (l as ObjectId, r as ObjectId): return l == r
1118-
case let (l as CodeWithScope, r as CodeWithScope): return l == r
1119-
case let (l as Binary, r as Binary): return l == r
1120-
case (_ as BSONNull, _ as BSONNull): return true
1121-
case let (l as Document, r as Document): return l == r
1122-
case let (l as [BSONValue], r as [BSONValue]): // TODO: SWIFT-242
1123-
return l.count == r.count && zip(l, r).reduce(true, { prev, next in prev && bsonEquals(next.0, next.1) })
1124-
case (_ as [Any], _ as [Any]): return false
1125-
case let (l as Symbol, r as Symbol): return l == r
1126-
case let (l as DBPointer, r as DBPointer): return l == r
1127-
case (_ as BSONUndefined, _ as BSONUndefined): return true
1128-
default: return false
1129-
}
1134+
return lhs.bsonEquals(rhs)
11301135
}
11311136

11321137
/**
@@ -1139,6 +1144,7 @@ public func bsonEquals(_ lhs: BSONValue, _ rhs: BSONValue) -> Bool {
11391144
*
11401145
* - Returns: True if lhs is equal to rhs, false otherwise.
11411146
*/
1147+
@available(*, deprecated, message: "use lhs?.bsonEquals(rhs) instead")
11421148
public func bsonEquals(_ lhs: BSONValue?, _ rhs: BSONValue?) -> Bool {
11431149
guard let left = lhs, let right = rhs else {
11441150
return lhs == nil && rhs == nil

Tests/MongoSwiftTests/BSONValueTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ final class BSONValueTests: MongoSwiftTestCase {
8585
)
8686
// Check that when an array contains non-BSONValues, we return false
8787
let arr = [[String: Int]()]
88-
expect(bsonEquals(arr, arr)).to(beFalse())
88+
expect(arr.bsonEquals(arr)).to(beFalse())
8989

9090
// Different types
9191
expect(4).toNot(bsonEqual("swift"))
9292

9393
// Arrays of different sizes should not be equal
9494
let b0: [BSONValue] = [1, 2]
9595
let b1: [BSONValue] = [1, 2, 3]
96-
expect(bsonEquals(b0, b1)).to(beFalse())
96+
expect(b0.bsonEquals(b1)).to(beFalse())
9797
}
9898

9999
/// Test object for ObjectIdRoundTrip

Tests/MongoSwiftTests/TestUtils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ internal func bsonEqual(_ expectedValue: BSONValue?) -> Predicate<BSONValue> {
261261
case (nil, nil), (_, nil):
262262
return PredicateResult(status: .fail, message: msg)
263263
case let (expected?, actual?):
264-
let matches = bsonEquals(expected, actual)
264+
let matches = expected.bsonEquals(actual)
265265
return PredicateResult(bool: matches, message: msg)
266266
}
267267
}

0 commit comments

Comments
 (0)