diff --git a/Sources/MongoSwift/BSON/BSONValue.swift b/Sources/MongoSwift/BSON/BSONValue.swift index 22089fddb..fcc941bfe 100644 --- a/Sources/MongoSwift/BSON/BSONValue.swift +++ b/Sources/MongoSwift/BSON/BSONValue.swift @@ -103,6 +103,75 @@ extension BSONValue where Self: Equatable { } } +/// A protocol that numeric `BSONValue`s should conform to. It provides functionality for converting to BSON's native +/// number types. +public protocol BSONNumber: BSONValue { + /// Create an `Int` from this `BSONNumber`. + /// This will return nil if the conversion cannot result in an exact representation. + var intValue: Int? { get } + + /// Create an `Int32` from this `BSONNumber`. + /// This will return nil if the conversion cannot result in an exact representation. + var int32Value: Int32? { get } + + /// Create an `Int64` from this `BSONNumber`. + /// This will return nil if the conversion cannot result in an exact representation. + var int64Value: Int64? { get } + + /// Create a `Double` from this `BSONNumber`. + /// This will return nil if the conversion cannot result in an exact representation. + var doubleValue: Double? { get } + + /// Create a `Decimal128` from this `BSONNumber`. + /// This will return nil if the conversion cannot result in an exact representation. + var decimal128Value: Decimal128? { get } +} + +/// Default conformance to `BSONNumber` for `BinaryInteger`s. +extension BSONNumber where Self: BinaryInteger { + /// Create an `Int` from this `BinaryInteger`. + /// This will return nil if the conversion cannot result in an exact representation. + public var intValue: Int? { return Int(exactly: self) } + + /// Create an `Int32` from this `BinaryInteger`. + /// This will return nil if the conversion cannot result in an exact representation. + public var int32Value: Int32? { return Int32(exactly: self) } + + /// Create an `Int64` from this `BinaryInteger`. + /// This will return nil if the conversion cannot result in an exact representation. + public var int64Value: Int64? { return Int64(exactly: self) } + + /// Create a `Double` from this `BinaryInteger`. + /// This will return nil if the conversion cannot result in an exact representation. + public var doubleValue: Double? { return Double(exactly: self) } +} + +/// Default conformance to `BSONNumber` for `BinaryFloatingPoint`s. +extension BSONNumber where Self: BinaryFloatingPoint { + /// Create an `Int` from this `BinaryFloatingPoint`. + /// This will return nil if the conversion cannot result in an exact representation. + public var intValue: Int? { return Int(exactly: self) } + + /// Create an `Int32` from this `BinaryFloatingPoint`. + /// This will return nil if the conversion cannot result in an exact representation. + public var int32Value: Int32? { return Int32(exactly: self) } + + /// Create an `Int64` from this `BinaryFloatingPoint`. + /// This will return nil if the conversion cannot result in an exact representation. + public var int64Value: Int64? { return Int64(exactly: self) } + + /// Create a `Double` from this `BinaryFloatingPoint`. + /// This will return nil if the conversion cannot result in an exact representation. + public var doubleValue: Double? { return Double(self) } +} + +/// Default implementation of `Decimal128` conversions for all `Numeric`s. +extension BSONNumber where Self: Numeric { + /// Create a `Decimal128` from this `Numeric`. + /// This will return nil if the conversion cannot result in an exact representation. + public var decimal128Value: Decimal128? { return Decimal128(String(describing: self)) } +} + /// An extension of `Array` to represent the BSON array type. extension Array: BSONValue { public var bsonType: BSONType { return .array } @@ -427,7 +496,7 @@ public struct DBPointer: BSONValue, Codable, Equatable { } /// A struct to represent the BSON Decimal128 type. -public struct Decimal128: BSONValue, Equatable, Codable, CustomStringConvertible { +public struct Decimal128: BSONNumber, Equatable, Codable, CustomStringConvertible { public var bsonType: BSONType { return .decimal128 } public var description: String { @@ -502,8 +571,32 @@ public struct Decimal128: BSONValue, Equatable, Codable, CustomStringConvertible } } +/// Extension of `Decimal128` to add `BSONNumber` conformance. +/// TODO: implement the missing converters (SWIFT-367) +extension Decimal128 { + /// Create an `Int` from this `Decimal128`. + /// Note: this function is not implemented yet and will always return nil. + public var intValue: Int? { return nil } + + /// Create an `Int32` from this `Decimal128`. + /// Note: this function is not implemented yet and will always return nil. + public var int32Value: Int32? { return nil } + + /// Create an `Int64` from this `Decimal128`. + /// Note: this function is not implemented yet and will always return nil. + public var int64Value: Int64? { return nil } + + /// Create a `Double` from this `Decimal128`. + /// Note: this function is not implemented yet and will always return nil. + public var doubleValue: Double? { return nil } + + /// Returns this `Decimal128`. + /// This is implemented as part of `BSONNumber` conformance. + public var decimal128Value: Decimal128? { return self } +} + /// An extension of `Double` to represent the BSON Double type. -extension Double: BSONValue { +extension Double: BSONNumber { public var bsonType: BSONType { return .double } public func encode(to storage: DocumentStorage, forKey key: String) throws { @@ -524,39 +617,66 @@ extension Double: BSONValue { } /// An extension of `Int` to represent the BSON Int32 or Int64 type. -/// The `Int` will be encoded as an Int32 if possible, or an Int64 if necessary. -extension Int: BSONValue { - public var bsonType: BSONType { return self.int32Value != nil ? .int32 : .int64 } +/// On 64-bit systems, `Int` corresponds to a BSON Int64. On 32-bit systems, it corresponds to a BSON Int32. +extension Int: BSONNumber { + /// `Int` corresponds to a BSON int32 or int64 depending upon whether the compilation system is 32 or 64 bit. + /// Use MemoryLayout instead of Int.bitWidth to avoid a compiler warning. + /// See: https://forums.swift.org/t/how-can-i-condition-on-the-size-of-int/9080/4 + internal static var bsonType: BSONType { + return MemoryLayout.size == 4 ? .int32 : .int64 + } + + public var bsonType: BSONType { return Int.bsonType } - internal var int32Value: Int32? { return Int32(exactly: self) } - internal var int64Value: Int64? { return Int64(exactly: self) } + // Return this `Int` as an `Int32` on 32-bit systems or an `Int64` on 64-bit systems + internal var typedValue: BSONNumber { + if self.bsonType == .int64 { + return Int64(self) + } + return Int32(self) + } public func encode(to storage: DocumentStorage, forKey key: String) throws { - if let int32 = self.int32Value { - return try int32.encode(to: storage, forKey: key) + try self.typedValue.encode(to: storage, forKey: key) + } + + public func bsonEquals(_ other: BSONValue?) -> Bool { + guard let other = other, other.bsonType == self.bsonType else { + return false } - if let int64 = self.int64Value { - return try int64.encode(to: storage, forKey: key) + + if let otherInt = other as? Int { + return self == otherInt } - throw RuntimeError.internalError(message: "`Int` value \(self) could not be encoded as `Int32` or `Int64`") + switch (self.typedValue, other) { + case let (self32 as Int32, other32 as Int32): + return self32 == other32 + case let (self64 as Int64, other64 as Int64): + return self64 == other64 + default: + return false + } } public static func from(iterator iter: DocumentIterator) throws -> Int { - return try iter.withBSONIterPointer { iterPtr in - // TODO: handle this more gracefully (SWIFT-221) - switch iter.currentType { - case .int32, .int64: - return self.init(Int(bson_iter_int32(iterPtr))) - default: - throw wrongIterTypeError(iter, expected: Int.self) - } + var val: Int? + if Int.bsonType == .int64 { + val = Int(exactly: try Int64.from(iterator: iter)) + } else { + val = Int(exactly: try Int32.from(iterator: iter)) + } + + guard let out = val else { + // This should not occur + throw RuntimeError.internalError(message: "Couldn't read `Int` from Document") } + return out } } /// An extension of `Int32` to represent the BSON Int32 type. -extension Int32: BSONValue { +extension Int32: BSONNumber { public var bsonType: BSONType { return .int32 } public func encode(to storage: DocumentStorage, forKey key: String) throws { @@ -574,10 +694,19 @@ extension Int32: BSONValue { self.init(bson_iter_int32(iterPtr)) } } + + public func bsonEquals(_ other: BSONValue?) -> Bool { + if let other32 = other as? Int32 { + return self == other32 + } else if let otherInt = other as? Int { + return self == otherInt.typedValue as? Int32 + } + return false + } } /// An extension of `Int64` to represent the BSON Int64 type. -extension Int64: BSONValue { +extension Int64: BSONNumber { public var bsonType: BSONType { return .int64 } public func encode(to storage: DocumentStorage, forKey key: String) throws { @@ -595,6 +724,15 @@ extension Int64: BSONValue { self.init(bson_iter_int64(iterPtr)) } } + + public func bsonEquals(_ other: BSONValue?) -> Bool { + if let other64 = other as? Int64 { + return self == other64 + } else if let otherInt = other as? Int { + return self == otherInt.typedValue as? Int64 + } + return false + } } /// A struct to represent the BSON Code and CodeWithScope types. diff --git a/Sources/MongoSwift/BSON/CodableNumber.swift b/Sources/MongoSwift/BSON/CodableNumber.swift index 844e53a62..c8df443d3 100644 --- a/Sources/MongoSwift/BSON/CodableNumber.swift +++ b/Sources/MongoSwift/BSON/CodableNumber.swift @@ -82,7 +82,7 @@ extension UInt8: CodableNumber { extension UInt16: CodableNumber { internal var bsonValue: BSONValue? { // UInt16 always fits in an Int32 - return Int(exactly: self) + return Int32(exactly: self) } } diff --git a/Sources/MongoSwift/BSON/DocumentIterator.swift b/Sources/MongoSwift/BSON/DocumentIterator.swift index 2365c39d1..484b8f47b 100644 --- a/Sources/MongoSwift/BSON/DocumentIterator.swift +++ b/Sources/MongoSwift/BSON/DocumentIterator.swift @@ -186,9 +186,9 @@ public class DocumentIterator: IteratorProtocol { .javascript: CodeWithScope.self, .symbol: Symbol.self, .javascriptWithScope: CodeWithScope.self, - .int32: Int.self, + .int32: Int.bsonType == .int32 ? Int.self : Int32.self, .timestamp: Timestamp.self, - .int64: Int64.self, + .int64: Int.bsonType == .int64 ? Int.self : Int64.self, .decimal128: Decimal128.self, .minKey: MinKey.self, .maxKey: MaxKey.self, diff --git a/Sources/MongoSwift/BSON/Overwritable.swift b/Sources/MongoSwift/BSON/Overwritable.swift index ae2c41c2c..1ea8c191c 100644 --- a/Sources/MongoSwift/BSON/Overwritable.swift +++ b/Sources/MongoSwift/BSON/Overwritable.swift @@ -21,13 +21,14 @@ extension Bool: Overwritable { extension Int: Overwritable { internal func writeToCurrentPosition(of iter: DocumentIterator) throws { - if let int32 = self.int32Value { + switch self.typedValue { + case let int32 as Int32: return int32.writeToCurrentPosition(of: iter) - } else if let int64 = self.int64Value { + case let int64 as Int64: return int64.writeToCurrentPosition(of: iter) + default: + throw RuntimeError.internalError(message: "`Int` value \(self) could not be encoded as `Int32` or `Int64`") } - - throw RuntimeError.internalError(message: "`Int` value \(self) could not be encoded as `Int32` or `Int64`") } } diff --git a/Sources/MongoSwift/MongoCollection+BulkWrite.swift b/Sources/MongoSwift/MongoCollection+BulkWrite.swift index 2010b0b8f..fd828a577 100644 --- a/Sources/MongoSwift/MongoCollection+BulkWrite.swift +++ b/Sources/MongoSwift/MongoCollection+BulkWrite.swift @@ -443,18 +443,22 @@ public struct BulkWriteResult { * - `RuntimeError.internalError` if an unexpected error occurs reading the reply from the server. */ fileprivate init(reply: Document, insertedIds: [Int: BSONValue]) throws { - self.deletedCount = try reply.getValue(for: "nRemoved") as? Int ?? 0 - self.insertedCount = try reply.getValue(for: "nInserted") as? Int ?? 0 + // These values are converted to Int via BSONNumber because they're returned from libmongoc as BSON int32s, + // which are retrieved from documents as Ints on 32-bit systems and Int32s on 64-bit ones. To retrieve them in a + // cross-platform manner, we must convert them this way. Also, regardless of how they are stored in the + // we want to use them as Ints. + self.deletedCount = (try reply.getValue(for: "nRemoved") as? BSONNumber)?.intValue ?? 0 + self.insertedCount = (try reply.getValue(for: "nInserted") as? BSONNumber)?.intValue ?? 0 self.insertedIds = insertedIds - self.matchedCount = try reply.getValue(for: "nMatched") as? Int ?? 0 - self.modifiedCount = try reply.getValue(for: "nModified") as? Int ?? 0 - self.upsertedCount = try reply.getValue(for: "nUpserted") as? Int ?? 0 + self.matchedCount = (try reply.getValue(for: "nMatched") as? BSONNumber)?.intValue ?? 0 + self.modifiedCount = (try reply.getValue(for: "nModified") as? BSONNumber)?.intValue ?? 0 + self.upsertedCount = (try reply.getValue(for: "nUpserted") as? BSONNumber)?.intValue ?? 0 var upsertedIds = [Int: BSONValue]() if let upserted = try reply.getValue(for: "upserted") as? [Document] { for upsert in upserted { - guard let index = try upsert.getValue(for: "index") as? Int else { + guard let index = (try upsert.getValue(for: "index") as? BSONNumber)?.intValue else { throw RuntimeError.internalError(message: "Could not cast upserted index to `Int`") } upsertedIds[index] = upsert["_id"] diff --git a/Sources/MongoSwift/MongoCollection+Indexes.swift b/Sources/MongoSwift/MongoCollection+Indexes.swift index c69b40514..eadfaaba5 100644 --- a/Sources/MongoSwift/MongoCollection+Indexes.swift +++ b/Sources/MongoSwift/MongoCollection+Indexes.swift @@ -16,7 +16,7 @@ public struct IndexModel: Encodable { /// Gets the default name for this index. internal var defaultName: String { - return String(cString: mongoc_collection_keys_to_index_string(self.keys.data)) + return self.keys.map { k, v in "\(k)_\(v)" }.joined(separator: "_") } // Encode own data as well as nested options data diff --git a/Sources/MongoSwift/MongoCollection+Write.swift b/Sources/MongoSwift/MongoCollection+Write.swift index 99edbcc4a..313f36463 100644 --- a/Sources/MongoSwift/MongoCollection+Write.swift +++ b/Sources/MongoSwift/MongoCollection+Write.swift @@ -402,24 +402,6 @@ public struct InsertManyResult { /// Map of the index of the document in `values` to the value of its ID public let insertedIds: [Int: BSONValue] - /** - * Create an `InsertManyResult` from a reply and map of inserted IDs. - * - * Note: we forgo using a Decodable initializer because we still need to - * explicitly add `insertedIds`. - * - * - Parameters: - * - reply: A `Document` result from `mongoc_collection_insert_many()` - * - insertedIds: Map of inserted IDs - * - * - Throws: - * - `RuntimeError.internalError` if an unexpected error occurs the reading server reply. - */ - fileprivate init(reply: Document, insertedIds: [Int: BSONValue]) throws { - self.insertedCount = try reply.getValue(for: "insertedCount") as? Int ?? 0 - self.insertedIds = insertedIds - } - /// Internal initializer used for converting from a `BulkWriteResult` optional to an `InsertManyResult` optional. internal init?(from result: BulkWriteResult?) { guard let result = result else { diff --git a/Sources/MongoSwift/SDAM.swift b/Sources/MongoSwift/SDAM.swift index c4e5d080e..f54d985a1 100644 --- a/Sources/MongoSwift/SDAM.swift +++ b/Sources/MongoSwift/SDAM.swift @@ -125,11 +125,11 @@ public struct ServerDescription { self.opTime = lastWrite["opTime"] as? ObjectId } - if let minVersion = isMaster["minWireVersion"] as? Int32 { + if let minVersion = (isMaster["minWireVersion"] as? BSONNumber)?.int32Value { self.minWireVersion = minVersion } - if let maxVersion = isMaster["maxWireVersion"] as? Int32 { + if let maxVersion = (isMaster["maxWireVersion"] as? BSONNumber)?.int32Value { self.maxWireVersion = maxVersion } @@ -156,14 +156,14 @@ public struct ServerDescription { } self.setName = isMaster["setName"] as? String - self.setVersion = isMaster["setVersion"] as? Int64 + self.setVersion = (isMaster["setVersion"] as? BSONNumber)?.int64Value self.electionId = isMaster["electionId"] as? ObjectId if let primary = isMaster["primary"] as? String { self.primary = ConnectionId(primary) } - self.logicalSessionTimeoutMinutes = isMaster["logicalSessionTimeoutMinutes"] as? Int64 + self.logicalSessionTimeoutMinutes = (isMaster["logicalSessionTimeoutMinutes"] as? BSONNumber)?.int64Value } /// An internal initializer to create a `ServerDescription` from an OpaquePointer to a diff --git a/Sources/MongoSwift/WriteConcern.swift b/Sources/MongoSwift/WriteConcern.swift index b435eb3b3..90813eaf6 100644 --- a/Sources/MongoSwift/WriteConcern.swift +++ b/Sources/MongoSwift/WriteConcern.swift @@ -67,9 +67,9 @@ public class WriteConcern: Codable { /// If the write concern is not satisfied within this timeout (in milliseconds), /// the operation will return an error. The value MUST be greater than or equal to 0. - public var wtimeoutMS: Int32? { + public var wtimeoutMS: Int64? { let timeout = mongoc_write_concern_get_wtimeout(self._writeConcern) - return timeout == 0 ? nil : timeout + return timeout == 0 ? nil : Int64(timeout) } /// Indicates whether this is an acknowledged write concern. @@ -95,10 +95,13 @@ public class WriteConcern: Codable { /// Initializes a new `WriteConcern`. /// - Throws: /// - `UserError.invalidArgumentError` if the options form an invalid combination. - public init(journal: Bool? = nil, w: W? = nil, wtimeoutMS: Int32? = nil) throws { + public init(journal: Bool? = nil, w: W? = nil, wtimeoutMS: Int64? = nil) throws { self._writeConcern = mongoc_write_concern_new() if let journal = journal { mongoc_write_concern_set_journal(self._writeConcern, journal) } - if let wtimeoutMS = wtimeoutMS { mongoc_write_concern_set_wtimeout(self._writeConcern, wtimeoutMS) } + if let wtimeoutMS = wtimeoutMS { + // libmongoc takes in a 32-bit integer for wtimeoutMS, but the specification states it should be an int64 + mongoc_write_concern_set_wtimeout(self._writeConcern, Int32(truncatingIfNeeded: wtimeoutMS)) + } if let w = w { switch w { @@ -135,7 +138,7 @@ public class WriteConcern: Codable { let container = try decoder.container(keyedBy: CodingKeys.self) let w = try container.decodeIfPresent(W.self, forKey: .w) let journal = try container.decodeIfPresent(Bool.self, forKey: .j) - let wtimeoutMS = try container.decodeIfPresent(Int32.self, forKey: .wtimeout) + let wtimeoutMS = try container.decodeIfPresent(Int64.self, forKey: .wtimeout) try self.init(journal: journal, w: w, wtimeoutMS: wtimeoutMS) } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 97a218220..cfbd8150b 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -14,6 +14,7 @@ extension BSONValueTests { ("testBSONEquals", testBSONEquals), ("testObjectIdRoundTrip", testObjectIdRoundTrip), ("testHashable", testHashable), + ("testBSONNumber", testBSONNumber), ] } @@ -74,6 +75,7 @@ extension DocumentTests { ("testDateEncodingStrategies", testDateEncodingStrategies), ("testDateDecodingStrategies", testDateDecodingStrategies), ("testDataCodingStrategies", testDataCodingStrategies), + ("testIntegerRetrieval", testIntegerRetrieval), ] } diff --git a/Tests/MongoSwiftTests/BSONValueTests.swift b/Tests/MongoSwiftTests/BSONValueTests.swift index c80f4a96c..6be5d0f00 100644 --- a/Tests/MongoSwiftTests/BSONValueTests.swift +++ b/Tests/MongoSwiftTests/BSONValueTests.swift @@ -187,4 +187,61 @@ final class BSONValueTests: MongoSwiftTestCase { expect(Set([str.hashValue, doc.hashValue, json.hashValue]).count).to(equal(3)) } + + struct BSONNumberTestCase { + let int: Int? + let double: Double? + let int32: Int32? + let int64: Int64? + let decimal: Decimal128? + + func run() { + let candidates = ([self.int, self.double, self.int32, self.int64, self.decimal] as [BSONNumber?]) + .compactMap { $0 } + + candidates.forEach { l in + // Skip the Decimal128 conversions until they're implemented + // TODO: don't skip these (SWIFT-367) + if l is Decimal128 { + return + } + + let compare = { (computed: BSONNumber?, expected: BSONNumber?) in + guard computed != nil else { + expect(expected).to(beNil()) + return + } + expect(computed).to(bsonEqual(expected)) + } + + compare(l.intValue, self.int) + compare(l.int32Value, self.int32) + compare(l.int64Value, self.int64) + compare(l.doubleValue, self.double) + + // Skip double for this conversion since it generates a Decimal128(5.0) =/= Decimal128(5) + if !(l is Double) { + compare(l.decimal128Value, self.decimal) + } + } + } + } + + func testBSONNumber() throws { + let decimal128 = Decimal128("5.5")! + let double = 5.5 + + expect(double.doubleValue).to(equal(double)) + expect(double.decimal128Value).to(equal(decimal128)) + + let cases = [ + BSONNumberTestCase(int: 5, double: 5.0, int32: Int32(5), int64: Int64(5), decimal: Decimal128("5")!), + BSONNumberTestCase(int: -5, double: -5.0, int32: Int32(-5), int64: Int64(-5), decimal: Decimal128("-5")!), + BSONNumberTestCase(int: 0, double: 0.0, int32: Int32(0), int64: Int64(0), decimal: Decimal128("0")!), + BSONNumberTestCase(int: nil, double: 1.234, int32: nil, int64: nil, decimal: Decimal128("1.234")!), + BSONNumberTestCase(int: nil, double: -31.234, int32: nil, int64: nil, decimal: Decimal128("-31.234")!) + ] + + cases.forEach { $0.run() } + } } diff --git a/Tests/MongoSwiftTests/CodecTests.swift b/Tests/MongoSwiftTests/CodecTests.swift index 04651dd46..aeca11438 100644 --- a/Tests/MongoSwiftTests/CodecTests.swift +++ b/Tests/MongoSwiftTests/CodecTests.swift @@ -154,10 +154,12 @@ final class CodecTests: MongoSwiftTestCase { let encoder = BSONEncoder() let s1 = Numbers(int8: 42, int16: 42, uint8: 42, uint16: 42, uint32: 42, uint64: 42, uint: 42, float: 42) + + let int32 = Int32(42) // all should be stored as Int32s, except the float should be stored as a double let doc1: Document = [ - "int8": 42, "int16": 42, "uint8": 42, "uint16": 42, - "uint32": 42, "uint64": 42, "uint": 42, "float": 42.0 + "int8": int32, "int16": int32, "uint8": int32, "uint16": int32, + "uint32": int32, "uint64": int32, "uint": int32, "float": 42.0 ] expect(try encoder.encode(s1)).to(equal(doc1)) @@ -361,16 +363,17 @@ final class CodecTests: MongoSwiftTestCase { { "double" : 2.0, "string" : "hi", - "doc" : { "x" : 1 }, + "doc" : { "x" : { "$numberLong": "1" } }, "arr" : [ 1, 2 ], "binary" : { "$binary" : { "base64": "\(base64)", "subType" : "00" } }, "oid" : { "$oid" : "507f1f77bcf86cd799439011" }, "bool" : true, "date" : { "$date" : "2001-01-01T01:23:20Z" }, - "code" : { "$code" : "hi", "$scope" : { "x" : 1 } }, + "code" : { "$code" : "hi", "$scope" : { "x" : { "$numberLong": "1" } } }, "int" : 1, "ts" : { "$timestamp" : { "t" : 1, "i" : 2 } }, - "int32" : 5, "int64" : 6, + "int32" : 5, + "int64" : 6, "dec" : { "$numberDecimal" : "1.2E+10" }, "minkey" : { "$minKey" : 1 }, "maxkey" : { "$maxKey" : 1 }, @@ -418,8 +421,9 @@ final class CodecTests: MongoSwiftTestCase { from: "{\"$code\": \"hi\" }")).to(equal(CodeWithScope(code: "hi"))) let cws = CodeWithScope(code: "hi", scope: ["x": 1]) expect(try decoder.decode(CodeWithScope.self, - from: "{\"$code\": \"hi\", \"$scope\": {\"x\" : 1} }")).to(equal(cws)) - expect(try decoder.decode(Document.self, from: "{\"x\": 1}")).to(equal(["x": 1])) + from: "{\"$code\": \"hi\", \"$scope\": {\"x\" : { \"$numberLong\": \"1\" }} }") + ).to(equal(cws)) + expect(try decoder.decode(Document.self, from: "{\"x\": 1}")).to(equal(["x": Int32(1)])) let ts = Timestamp(timestamp: 1, inc: 2) expect(try decoder.decode(Timestamp.self, from: "{ \"$timestamp\" : { \"t\" : 1, \"i\" : 2 } }")).to(equal(ts)) @@ -537,7 +541,10 @@ final class CodecTests: MongoSwiftTestCase { // array let array: [BSONValue] = [1, 2, "hello"] - let decodedArray = try decoder.decode(AnyBSONValue.self, from: "[1, 2, \"hello\"]").value as? [BSONValue] + let decodedArray = try decoder.decode( + AnyBSONValue.self, + from: "[{\"$numberLong\": \"1\"}, {\"$numberLong\": \"2\"}, \"hello\"]" + ).value as? [BSONValue] expect(decodedArray?[0]).to(bsonEqual(1)) expect(decodedArray?[1]).to(bsonEqual(2)) expect(decodedArray?[2]).to(bsonEqual("hello")) @@ -636,7 +643,7 @@ final class CodecTests: MongoSwiftTestCase { expect( try decoder.decode(AnyBSONValue.self, from: "{ \"$code\" : \"console.log(x);\", " - + "\"$scope\" : { \"x\" : { \"$numberInt\" : \"1\" } } }").value as? CodeWithScope + + "\"$scope\" : { \"x\" : { \"$numberLong\" : \"1\" } } }").value as? CodeWithScope ).to(equal(code)) let wrappedCode: Document = ["x": code] @@ -649,19 +656,20 @@ final class CodecTests: MongoSwiftTestCase { // int32 let int32 = Int32(5) - expect(try decoder.decode(AnyBSONValue.self, from: "{ \"$numberInt\" : \"5\" }").value).to(bsonEqual(5)) + expect(try decoder.decode(AnyBSONValue.self, from: "{ \"$numberInt\" : \"5\" }").value).to(bsonEqual(int32)) let wrappedInt32: Document = ["x": int32] expect(try encoder.encode(AnyBSONStruct(int32))).to(equal(wrappedInt32)) - // as int because we convert Int32 -> Int when decoding - expect(try decoder.decode(AnyBSONStruct.self, from: wrappedInt32).x.value).to(bsonEqual(5)) - expect(try decoder.decode(AnyBSONStruct.self, - from: wrappedInt32.canonicalExtendedJSON).x.value).to(bsonEqual(5)) + expect(try decoder.decode(AnyBSONStruct.self, from: wrappedInt32).x.value).to(bsonEqual(int32)) + expect(try decoder.decode( + AnyBSONStruct.self, + from: wrappedInt32.canonicalExtendedJSON).x.value).to(bsonEqual(int32) + ) // int let int = 5 - expect(try decoder.decode(AnyBSONValue.self, from: "{ \"$numberInt\" : \"5\" }").value).to(bsonEqual(int)) + expect(try decoder.decode(AnyBSONValue.self, from: "{ \"$numberLong\" : \"5\" }").value).to(bsonEqual(int)) let wrappedInt: Document = ["x": int] expect(try encoder.encode(AnyBSONStruct(int))).to(equal(wrappedInt)) @@ -672,9 +680,7 @@ final class CodecTests: MongoSwiftTestCase { // int64 let int64 = Int64(5) - expect( - try decoder.decode(AnyBSONValue.self, from: "{ \"$numberLong\" : \"5\" }").value as? Int64 - ).to(equal(int64)) + expect(try decoder.decode(AnyBSONValue.self, from: "{\"$numberLong\":\"5\"}").value).to(bsonEqual(int64)) let wrappedInt64: Document = ["x": int64] expect(try encoder.encode(AnyBSONStruct(Int64(5)))).to(equal(wrappedInt64)) diff --git a/Tests/MongoSwiftTests/CommandMonitoringTests.swift b/Tests/MongoSwiftTests/CommandMonitoringTests.swift index 7791d77ad..660e5add0 100644 --- a/Tests/MongoSwiftTests/CommandMonitoringTests.swift +++ b/Tests/MongoSwiftTests/CommandMonitoringTests.swift @@ -157,28 +157,20 @@ private struct CMTest: Decodable { case "find": let modifiers = self.op.args["modifiers"] as? Document - var batchSize: Int32? - if let size = self.op.args["batchSize"] as? Int64 { - batchSize = Int32(size) - } - var maxTime: Int64? - if let max = modifiers?["$maxTimeMS"] as? Int { - maxTime = Int64(max) - } var hint: Hint? if let hintDoc = modifiers?["$hint"] as? Document { hint = .indexSpec(hintDoc) } - let options = FindOptions(batchSize: batchSize, + let options = FindOptions(batchSize: (self.op.args["batchSize"] as? BSONNumber)?.int32Value, comment: modifiers?["$comment"] as? String, hint: hint, - limit: self.op.args["limit"] as? Int64, + limit: (self.op.args["limit"] as? BSONNumber)?.int64Value, max: modifiers?["$max"] as? Document, - maxTimeMS: maxTime, + maxTimeMS: (modifiers?["$maxTimeMS"] as? BSONNumber)?.int64Value, min: modifiers?["$min"] as? Document, returnKey: modifiers?["$returnKey"] as? Bool, showRecordId: modifiers?["$showDiskLoc"] as? Bool, - skip: self.op.args["skip"] as? Int64, + skip: (self.op.args["skip"] as? BSONNumber)?.int64Value, sort: self.op.args["sort"] as? Document) // we have to iterate the cursor to make the command execute @@ -260,7 +252,7 @@ private struct CommandStartedExpectation: ExpectationType, Decodable { // if it's a getMore, we can't directly compare the results if commandName == "getMore" { // verify that the getMore ID matches the stored cursor ID for this test - expect(event.command["getMore"]).to(bsonEqual(testContext["cursorId"] as? Int64)) + expect(event.command["getMore"]).to(bsonEqual(testContext["cursorId"] as? BSONNumber)) // compare collection and batchSize fields expect(event.command["collection"]).to(bsonEqual(self.command["collection"])) expect(event.command["batchSize"]).to(bsonEqual(self.command["batchSize"])) @@ -288,14 +280,14 @@ private func normalizeCommand(_ input: Document) -> Document { // parses it as an Int32 which we convert to Int. convert to Int64 here because we /// (as per the crud spec) use an Int64 for maxTimeMS and send that to // the server in our actual commands. - } else if k == "maxTimeMS", let iV = v as? Int { - output[k] = Int64(iV) + } else if k == "maxTimeMS", let iV = (v as? BSONNumber)?.int64Value { + output[k] = iV // The expected batch sizes are always Int64s, however, find command // events actually have Int32 batch sizes... (as the spec says...) // but getMores have Int64s. so only convert if it's a find command... - } else if k == "batchSize", let iV = v as? Int64 { - if input["find"] != nil { output[k] = Int(iV) } else { output[k] = v } + } else if k == "batchSize", let iV = v as? BSONNumber { + if input["find"] != nil { output[k] = iV.int32Value! } else { output[k] = v } // recursively normalize if it's a document } else if let docVal = v as? Document { @@ -365,14 +357,14 @@ private struct CommandSucceededExpectation: ExpectationType, Decodable { let receivedCursor = event.reply["cursor"] as? Document if let expectedCursor = self.cursor { // if the received cursor has an ID, and the expected ID is not 0, compare cursor IDs - if let id = receivedCursor!["id"] as? Int64, expectedCursor["id"] as? Int64 != 0 { - let storedId = testContext["cursorId"] as? Int64 + if let id = receivedCursor!["id"] as? BSONNumber, (expectedCursor["id"] as? BSONNumber)?.intValue != 0 { + let storedId = testContext["cursorId"] as? BSONNumber // if we aren't already storing a cursor ID for this test, add one if storedId == nil { testContext["cursorId"] = id // otherwise, verify that this ID matches the stored one } else { - expect(storedId).to(equal(id)) + expect(storedId).to(bsonEqual(id)) } } compareCursors(expected: expectedCursor, actual: receivedCursor!) @@ -388,7 +380,7 @@ private struct CommandSucceededExpectation: ExpectationType, Decodable { expect(expected.count).to(equal(actual.count)) for err in actual { // check each error code exists and is > 0 - expect(err["code"] as? Int).to(beGreaterThan(0)) + expect((err["code"] as? BSONNumber)?.intValue).to(beGreaterThan(0)) // check each error msg exists and has length > 0 expect(err["errmsg"] as? String).toNot(beEmpty()) } @@ -419,8 +411,8 @@ private func normalizeExpectedReply(_ input: Document) -> Document { continue // The server sends back doubles, but the JSON test files // contain integer statuses (see SPEC-1050.) - } else if k == "ok", let dV = v as? Int { - output[k] = Double(dV) + } else if k == "ok", let dV = (v as? BSONNumber)?.doubleValue { + output[k] = dV // just copy the value over as is } else { output[k] = v diff --git a/Tests/MongoSwiftTests/CrudTests.swift b/Tests/MongoSwiftTests/CrudTests.swift index d7d2102d5..87d7c49c1 100644 --- a/Tests/MongoSwiftTests/CrudTests.swift +++ b/Tests/MongoSwiftTests/CrudTests.swift @@ -135,26 +135,11 @@ private class CrudTest { let collection: Document? var arrayFilters: [Document]? { return self.args["arrayFilters"] as? [Document] } - var batchSize: Int32? { - if let b = self.args["batchSize"] as? Int { - return Int32(b) - } - return nil - } + var batchSize: Int32? { return (self.args["batchSize"] as? BSONNumber)?.int32Value } var collation: Document? { return self.args["collation"] as? Document } var sort: Document? { return self.args["sort"] as? Document } - var skip: Int64? { - if let s = self.args["skip"] as? Int { - return Int64(s) - } - return nil - } - var limit: Int64? { - if let l = self.args["limit"] as? Int { - return Int64(l) - } - return nil - } + var skip: Int64? { return (self.args["skip"] as? BSONNumber)?.int64Value } + var limit: Int64? { return (self.args["limit"] as? BSONNumber)?.int64Value } var projection: Document? { return self.args["projection"] as? Document } var returnDoc: ReturnDocument? { if let ret = self.args["returnDocument"] as? String { @@ -203,8 +188,8 @@ private class CrudTest { expect(result?.modifiedCount).to(equal(expected.modifiedCount)) expect(result?.upsertedCount).to(equal(expected.upsertedCount)) - if let upsertedId = result?.upsertedId?.value as? Int { - expect(upsertedId).to(equal(expected.upsertedId?.value as? Int)) + if let upsertedId = result?.upsertedId?.value as? BSONNumber { + expect(upsertedId).to(bsonEqual(expected.upsertedId?.value)) } else { expect(expected.upsertedId).to(beNil()) } @@ -352,23 +337,23 @@ private class BulkWriteTest: CrudTest { return } - if let expectedDeletedCount = expected["deletedCount"] as? Int { - expect(result.deletedCount).to(equal(expectedDeletedCount)) + if let expectedDeletedCount = expected["deletedCount"] as? BSONNumber { + expect(result.deletedCount).to(equal(expectedDeletedCount.intValue)) } - if let expectedInsertedCount = expected["insertedCount"] as? Int { - expect(result.insertedCount).to(equal(expectedInsertedCount)) + if let expectedInsertedCount = expected["insertedCount"] as? BSONNumber { + expect(result.insertedCount).to(equal(expectedInsertedCount.intValue)) } if let expectedInsertedIds = expected["insertedIds"] as? Document { expect(BulkWriteTest.prepareIds(result.insertedIds)).to(equal(expectedInsertedIds)) } - if let expectedMatchedCount = expected["matchedCount"] as? Int { - expect(result.matchedCount).to(equal(expectedMatchedCount)) + if let expectedMatchedCount = expected["matchedCount"] as? BSONNumber { + expect(result.matchedCount).to(equal(expectedMatchedCount.intValue)) } - if let expectedModifiedCount = expected["modifiedCount"] as? Int { - expect(result.modifiedCount).to(equal(expectedModifiedCount)) + if let expectedModifiedCount = expected["modifiedCount"] as? BSONNumber { + expect(result.modifiedCount).to(equal(expectedModifiedCount.intValue)) } - if let expectedUpsertedCount = expected["upsertedCount"] as? Int { - expect(result.upsertedCount).to(equal(expectedUpsertedCount)) + if let expectedUpsertedCount = expected["upsertedCount"] as? BSONNumber { + expect(result.upsertedCount).to(equal(expectedUpsertedCount.intValue)) } if let expectedUpsertedIds = expected["upsertedIds"] as? Document { expect(BulkWriteTest.prepareIds(result.upsertedIds)).to(equal(expectedUpsertedIds)) @@ -382,7 +367,7 @@ private class CountTest: CrudTest { let filter: Document = try self.args.get("filter") let options = CountOptions(collation: self.collation, limit: self.limit, skip: self.skip) let result = try coll.count(filter, options: options) - expect(result).to(equal(self.result as? Int)) + expect(result).to(equal((self.result as? BSONNumber)?.intValue)) } } @@ -399,7 +384,7 @@ private class DeleteTest: CrudTest { } let expected = self.result as? Document // the only value in a DeleteResult is `deletedCount` - expect(result?.deletedCount).to(equal(expected?["deletedCount"] as? Int)) + expect(result?.deletedCount).to(equal((expected?["deletedCount"] as? BSONNumber)?.intValue)) } } @@ -526,8 +511,8 @@ private class InsertManyTest: CrudTest { return } - if let expectedInsertedCount = expected["insertedCount"] as? Int { - expect(result.insertedCount).to(equal(expectedInsertedCount)) + if let expectedInsertedCount = expected["insertedCount"] as? BSONNumber { + expect(result.insertedCount).to(equal(expectedInsertedCount.intValue)) } if let expectedInsertedIds = expected["insertedIds"] as? Document { expect(InsertManyTest.prepareIds(result.insertedIds)).to(equal(expectedInsertedIds)) diff --git a/Tests/MongoSwiftTests/Document+SequenceTests.swift b/Tests/MongoSwiftTests/Document+SequenceTests.swift index a1454bbc7..f506b5677 100644 --- a/Tests/MongoSwiftTests/Document+SequenceTests.swift +++ b/Tests/MongoSwiftTests/Document+SequenceTests.swift @@ -40,7 +40,7 @@ final class Document_SequenceTests: MongoSwiftTestCase { let int32Tup = iter.next()! expect(int32Tup.key).to(equal("int32")) - expect(int32Tup.value).to(bsonEqual(5)) + expect(int32Tup.value).to(bsonEqual(Int32(5))) let doubleTup = iter.next()! expect(doubleTup.key).to(equal("double")) diff --git a/Tests/MongoSwiftTests/DocumentTests.swift b/Tests/MongoSwiftTests/DocumentTests.swift index 7380fbaf2..760da1fac 100644 --- a/Tests/MongoSwiftTests/DocumentTests.swift +++ b/Tests/MongoSwiftTests/DocumentTests.swift @@ -131,7 +131,7 @@ final class DocumentTests: MongoSwiftTestCase { expect(doc["true"]).to(bsonEqual(true)) expect(doc["false"]).to(bsonEqual(false)) expect(doc["int"]).to(bsonEqual(25)) - expect(doc["int32"]).to(bsonEqual(5)) + expect(doc["int32"]).to(bsonEqual(Int32(5))) expect(doc["int64"]).to(bsonEqual(Int64(10))) expect(doc["double"]).to(bsonEqual(15.0)) expect(doc["decimal128"]).to(bsonEqual(Decimal128("1.2E+10")!)) @@ -169,7 +169,7 @@ final class DocumentTests: MongoSwiftTestCase { expect(doc["binary6"]).to(bsonEqual(try Binary(data: testData, subtype: .userDefined))) expect(doc["binary7"]).to(bsonEqual(try Binary(data: testData, subtype: 200))) - let nestedArray = doc["nestedarray"] as? [[Int]] + let nestedArray = doc["nestedarray"] as? [[Int32]] expect(nestedArray?[0]).to(equal([1, 2])) expect(nestedArray?[1]).to(equal([3, 4])) @@ -182,7 +182,7 @@ final class DocumentTests: MongoSwiftTestCase { expect(DocumentTests.testDoc.true).to(bsonEqual(true)) expect(DocumentTests.testDoc.false).to(bsonEqual(false)) expect(DocumentTests.testDoc.int).to(bsonEqual(25)) - expect(DocumentTests.testDoc.int32).to(bsonEqual(5)) + expect(DocumentTests.testDoc.int32).to(bsonEqual(Int32(5))) expect(DocumentTests.testDoc.int64).to(bsonEqual(Int64(10))) expect(DocumentTests.testDoc.double).to(bsonEqual(15.0)) expect(DocumentTests.testDoc.decimal128).to(bsonEqual(Decimal128("1.2E+10")!)) @@ -211,9 +211,9 @@ final class DocumentTests: MongoSwiftTestCase { options: NSRegularExpression.optionsFromString("imx") ))) - let nestedArray = DocumentTests.testDoc.nestedarray as? [[Int]] - expect(nestedArray?[0]).to(equal([1, 2])) - expect(nestedArray?[1]).to(equal([3, 4])) + let nestedArray = DocumentTests.testDoc.nestedarray as? [[Int32]] + expect(nestedArray?[0]).to(bsonEqual([Int32(1), Int32(2)])) + expect(nestedArray?[1]).to(bsonEqual([Int32(3), Int32(4)])) expect(DocumentTests.testDoc.nesteddoc).to(bsonEqual(["a": 1, "b": 2, "c": false, "d": [3, 4]] as Document)) expect((DocumentTests.testDoc.nesteddoc as? Document)?.a).to(bsonEqual(1)) @@ -488,12 +488,7 @@ final class DocumentTests: MongoSwiftTestCase { // overwrite int32 with int32 doc["int32"] = Int32(15) - expect(doc["int32"]).to(bsonEqual(15)) - expect(doc.data).to(equal(pointer)) - - // overwrite int32 with an int that fits into int32s - doc["int32"] = 20 - expect(doc["int32"]).to(bsonEqual(20)) + expect(doc["int32"]).to(bsonEqual(Int32(15))) expect(doc.data).to(equal(pointer)) doc["bool"] = true @@ -521,7 +516,7 @@ final class DocumentTests: MongoSwiftTestCase { expect(doc).to(equal([ "double": 3.0, - "int32": 20, + "int32": Int32(15), "int64": Int64.min, "bool": true, "decimal": Decimal128("100")!, @@ -542,7 +537,7 @@ final class DocumentTests: MongoSwiftTestCase { // final values expect(doc).to(equal([ "double": 3.0, - "int32": 20, + "int32": Int32(15), "int64": bigInt, "bool": true, "decimal": Decimal128("100")!, @@ -550,6 +545,11 @@ final class DocumentTests: MongoSwiftTestCase { "timestamp": Timestamp(timestamp: 5, inc: 10), "datetime": Date(msSinceEpoch: 2000) ])) + + // should not be able to overwrite an int32 with an int on a 64-bit system + doc["int32"] = 20 + expect(doc["int32"]).to(bsonEqual(20)) + expect(doc.data).toNot(equal(pointer)) } // test replacing some of the non-Overwritable types with values of their own types @@ -727,12 +727,7 @@ final class DocumentTests: MongoSwiftTestCase { Mirror(reflecting: abt).children.forEach { child in let value = child.value as! BSONValue doc[str] = value - - if value is Int32 { // TODO: handle this differently (SWIFT-221) - expect(Int32(doc[str] as! Int)).to(bsonEqual(value)) - } else { - expect(doc[str]).to(bsonEqual(value)) - } + expect(doc[str]).to(bsonEqual(value)) } } @@ -814,7 +809,7 @@ final class DocumentTests: MongoSwiftTestCase { encoder.dateEncodingStrategy = .millisecondsSince1970 let millisecondsSince1970 = try encoder.encode(dateStruct) - expect(millisecondsSince1970["date"] as? Int64).to(equal(date.msSinceEpoch)) + expect(millisecondsSince1970["date"]).to(bsonEqual(date.msSinceEpoch)) if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { encoder.dateEncodingStrategy = .iso8601 @@ -839,7 +834,7 @@ final class DocumentTests: MongoSwiftTestCase { try container.encode(Int64(d.timeIntervalSince1970 + 12)) }) let custom = try encoder.encode(dateStruct) - expect(custom["date"] as? Int64).to(equal(Int64(date.timeIntervalSince1970 + 12))) + expect(custom["date"]).to(bsonEqual(Int64(date.timeIntervalSince1970 + 12))) let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short @@ -953,7 +948,7 @@ final class DocumentTests: MongoSwiftTestCase { let data = Data(base64Encoded: "dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBzaGVlcCBkb2cu")! let binaryData = try Binary(data: data, subtype: .generic) - let arrData = data.map { byte in Int(byte) } + let arrData = data.map { byte in Int32(byte) } let dataStruct = DataWrapper(data: data) let defaultDoc = try encoder.encode(dataStruct) @@ -1006,4 +1001,24 @@ final class DocumentTests: MongoSwiftTestCase { encoder.dataEncodingStrategy = .custom({ _, _ in }) expect(try encoder.encode(dataStruct)).to(bsonEqual(["data": [:] as Document] as Document)) } + + func testIntegerRetrieval() { + let doc: Document = ["int": 12, "int32": Int32(12), "int64": Int64(12)] + + // Int always goes in and comes out as int + expect(doc["int"]).to(bsonEqual(12)) + expect(doc["int"] as? Int).to(equal(12)) + + if MongoSwiftTestCase.is32Bit { + expect(doc["int32"] as? Int).to(equal(12)) + expect(doc["int32"] as? Int32).to(beNil()) + expect(doc["int64"] as? Int).to(beNil()) + expect(doc["int64"] as? Int64).to(equal(Int64(12))) + } else { + expect(doc["int32"] as? Int).to(beNil()) + expect(doc["int32"] as? Int32).to(equal(Int32(12))) + expect(doc["int64"] as? Int).to(equal(12)) + expect(doc["int64"] as? Int64).to(beNil()) + } + } } diff --git a/Tests/MongoSwiftTests/MongoClientTests.swift b/Tests/MongoSwiftTests/MongoClientTests.swift index a06aedb8d..09779d085 100644 --- a/Tests/MongoSwiftTests/MongoClientTests.swift +++ b/Tests/MongoSwiftTests/MongoClientTests.swift @@ -182,7 +182,7 @@ final class MongoClientTests: MongoSwiftTestCase { doc = try collDoc.find(["_id": customDbCollId]).nextOrError() expect(doc).toNot(beNil()) - expect(doc?["date"] as? Int64).to(equal(date.msSinceEpoch)) + expect(doc?["date"]).to(bsonEqual(date.msSinceEpoch)) expect(doc?["uuid"] as? String).to(equal(uuid.uuidString)) expect(doc?["data"] as? String).to(equal(data.base64EncodedString())) diff --git a/Tests/MongoSwiftTests/MongoCollectionTests.swift b/Tests/MongoSwiftTests/MongoCollectionTests.swift index 1db5d58e8..f61d7705b 100644 --- a/Tests/MongoSwiftTests/MongoCollectionTests.swift +++ b/Tests/MongoSwiftTests/MongoCollectionTests.swift @@ -124,8 +124,8 @@ final class MongoCollectionTests: MongoSwiftTestCase { // the inserted IDs should either be the ones we set, // or newly created ObjectIds for (_, v) in res!.insertedIds { - if let val = v as? Int { - expect([10, 11]).to(contain(val)) + if let val = v as? BSONNumber { + expect([10, 11]).to(contain(val.intValue)) } else { expect(v).to(beAnInstanceOf(ObjectId.self)) } diff --git a/Tests/MongoSwiftTests/ReadWriteConcernTests.swift b/Tests/MongoSwiftTests/ReadWriteConcernTests.swift index bf2d9d883..baea919f5 100644 --- a/Tests/MongoSwiftTests/ReadWriteConcernTests.swift +++ b/Tests/MongoSwiftTests/ReadWriteConcernTests.swift @@ -12,14 +12,11 @@ extension WriteConcern { var w: W? if let wtag = doc["w"] as? String { w = wtag == "majority" ? .majority : .tag(wtag) - } else if let wInt = doc["w"] as? Int { - w = .number(Int32(wInt)) + } else if let wInt = (doc["w"] as? BSONNumber)?.int32Value { + w = .number(wInt) } - var wt: Int32? - if let wtInt = doc["wtimeoutMS"] as? Int { - wt = Int32(wtInt) - } + let wt = (doc["wtimeoutMS"] as? BSONNumber)?.int64Value try self.init(journal: j, w: w, wtimeoutMS: wt) } @@ -430,10 +427,13 @@ final class ReadWriteConcernTests: MongoSwiftTestCase { let isDefault: Bool = try test.get("isServerDefault") expect(wc.isDefault).to(equal(isDefault)) - let expected: Document = try test.get("writeConcernDocument") + var expected: Document = try test.get("writeConcernDocument") if expected == [:] { expect(try encoder.encode(wc)).to(beNil()) } else { + if let wtimeoutMS = expected["wtimeout"] as? BSONNumber { + expected["wtimeout"] = wtimeoutMS.int64Value! + } expect(try encoder.encode(wc)).to(equal(expected)) } } else { diff --git a/Tests/MongoSwiftTests/TestUtils.swift b/Tests/MongoSwiftTests/TestUtils.swift index 2ee18ddb8..1e22c0808 100644 --- a/Tests/MongoSwiftTests/TestUtils.swift +++ b/Tests/MongoSwiftTests/TestUtils.swift @@ -1,5 +1,5 @@ import Foundation -import MongoSwift +@testable import MongoSwift import Nimble import XCTest @@ -44,9 +44,7 @@ class MongoSwiftTestCase: XCTestCase { } // indicates whether we are running on a 32-bit platform - // Use MemoryLayout instead of Int.bitWidth to avoid a compiler warning. - // See: https://forums.swift.org/t/how-can-i-condition-on-the-size-of-int/9080/4 */ - static let is32Bit = MemoryLayout.size == 4 + static let is32Bit = Int.bsonType == .int32 /// Generates a unique collection name of the format "__". If no suffix is provided, /// the last underscore is omitted.