diff --git a/Guides/Error-Handling.md b/Guides/Error-Handling.md index d57979a99..e824f9363 100644 --- a/Guides/Error-Handling.md +++ b/Guides/Error-Handling.md @@ -15,8 +15,7 @@ * [See Also](#see-also) ## Error Types -The driver uses errors to communicate that an operation failed, an assumption wasn't met, or that the user did something incorrectly. Applications that use the driver can in turn catch these errors and respond appropriately without crashing or resulting in an otherwise inconsistent state. To correctly model the different sources of errors, the driver defines three separate caregories of errors (`MongoServerError`, `MongoUserError`, `MongoRuntimeError`), each of which are protocols that inherit from the `MongoErrorProtocol` protocol. These protocols are defined in `MongoError.swift`, and the structs that conform to them are outlined here. The documentation for every public function that throws lists some of the errors that could possibly be thrown and the reasons they might be. The errors listed there are not comprehensive but will generally cover the most common cases. - +The driver uses errors to communicate that an operation failed, an assumption wasn't met, or that the user did something incorrectly. Applications that use the driver can in turn catch these errors and respond appropriately without crashing or resulting in an otherwise inconsistent state. To correctly model the different sources of errors, the driver defines three separate categories of errors (`MongoServerError`, `MongoUserError`, `MongoRuntimeError`), each of which are protocols that inherit from the `MongoErrorProtocol` protocol. These protocols are defined in `MongoError.swift`, and the structs that conform to them are outlined here. The documentation for every public function that throws lists some of the errors that could possibly be thrown and the reasons they might be. The errors listed there are not comprehensive but will generally cover the most common cases. ### Server Errors Server errors correspond to failures that occur in the database itself and are returned to the driver via some response to a command. Each error that conforms to `ServerError` contains at least one error code representing what went wrong on the server. @@ -77,6 +76,15 @@ As part of the driver, `BSONEncoder` and `BSONDecoder` are implemented according See the official documentation for both [`EncodingErrors`](https://developer.apple.com/documentation/swift/encodingerror) and [`DecodingErrors`](https://developer.apple.com/documentation/swift/decodingerror) for more information. +### BSON Errors + +The BSON library has its own subset of errors that communicate issues when constructing or using BSON. +BSON Errors can be found in [Sources/MongoSwift/BSON/BSONError.swift](Sources/MongoSwift/BSON/BSONError.swift) and are as follows: + +- `BSONError.InvalidArgumentError` - This error is thrown when a BSON type is being incorrectly constructed. +- `BSONError.InternalError` - This error is thrown when there is an issue that is a result of system failure (e.g, allocation issue). +- `BSONError.LogicError` - This error is thrown when there is an unexpected usage of the the API. +- `BSONError.DocumentTooLargeError` - This error is thrown when the document exceeds the maximum encoding size of 16MB. ## Examples ### Handling any error thrown by the driver @@ -154,6 +162,24 @@ Result: nInserted: 1 InsertedIds: [0: 2] ``` + +### Handling a BSONError + +```swift +var string = "+1..23e8" +do { + return try BSONDecimal128(string) +} catch let bsonError as BSONError.InvalidArgumentError { + print(bsonError.message) +} +``` + +Output: + +```plain +Invalid Decimal128 string +1..23e8 +``` + ## See Also - [Error handling in Swift](https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html) - [List of server error codes](https://github.com/mongodb/mongo/blob/master/src/mongo/base/error_codes.err) diff --git a/Sources/MongoSwift/BSON/BSONDecoder.swift b/Sources/MongoSwift/BSON/BSONDecoder.swift index f5c1c06f9..07d94a17c 100644 --- a/Sources/MongoSwift/BSON/BSONDecoder.swift +++ b/Sources/MongoSwift/BSON/BSONDecoder.swift @@ -142,7 +142,17 @@ public class BSONDecoder { return doc } let _decoder = _BSONDecoder(referencing: .document(document), options: self.options) - return try type.init(from: _decoder) + do { + return try type.init(from: _decoder) + } catch let error as BSONErrorProtocol { + let unknownErrorMessage = "Unknown Error occurred while decoding BSON" + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: [], + debugDescription: "Unable to decode BSON: \(error.errorDescription ?? unknownErrorMessage)" + ) + ) + } } /** diff --git a/Sources/MongoSwift/BSON/BSONDocument.swift b/Sources/MongoSwift/BSON/BSONDocument.swift index 610ac6bb3..9b4d6adc4 100644 --- a/Sources/MongoSwift/BSON/BSONDocument.swift +++ b/Sources/MongoSwift/BSON/BSONDocument.swift @@ -9,7 +9,7 @@ internal typealias MutableBSONPointer = UnsafeMutablePointer public struct BSONDocument { /// Error thrown when BSON buffer is too small. internal static let BSONBufferTooSmallError = - MongoError.InternalError(message: "BSON buffer is unexpectedly too small (< 5 bytes)") + BSONError.InternalError(message: "BSON buffer is unexpectedly too small (< 5 bytes)") /// The storage backing a `BSONDocument`. private class Storage { @@ -145,10 +145,7 @@ extension BSONDocument { * presence first. * * - Throws: - * - `MongoError.InternalError` if the new value is an `Int` and cannot be written to BSON. - * - `MongoError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted. - * - `MongoError.LogicError` if the new value is an `Array` and it contains a non-`BSONValue` element. - * - `MongoError.InternalError` if the underlying `bson_t` would exceed the maximum size by encoding this + * - `BSONError.DocumentTooLargeError` if the underlying `bson_t` would exceed the maximum size by encoding this * key-value pair. */ internal mutating func setValue(for key: String, to newValue: BSON, checkForKey: Bool = true) throws { @@ -169,7 +166,7 @@ extension BSONDocument { self.copyStorageIfRequired() // key is guaranteed present so initialization will succeed. // swiftlint:disable:next force_unwrapping - try BSONDocumentIterator(over: self, advancedTo: key)!.overwriteCurrentValue(with: ov) + BSONDocumentIterator(over: self, advancedTo: key)!.overwriteCurrentValue(with: ov) // otherwise, we just create a new document and replace this key } else { @@ -210,7 +207,7 @@ extension BSONDocument { /// Retrieves the value associated with `for` as a `BSON?`, which can be nil if the key does not exist in the /// `BSONDocument`. /// - /// - Throws: `MongoError.InternalError` if the BSON buffer is too small (< 5 bytes). + /// - Throws: `BSONError.InternalError` if the BSON buffer is too small (< 5 bytes). internal func getValue(for key: String) throws -> BSON? { guard let iter = BSONDocumentIterator(over: self) else { throw BSONDocument.BSONBufferTooSmallError @@ -232,7 +229,7 @@ extension BSONDocument { } } guard success else { - throw MongoError.InternalError( + throw BSONError.InternalError( message: "Failed to merge \(doc) with \(self). This is likely due to " + "the merged document being too large." ) @@ -255,7 +252,7 @@ extension BSONDocument { /// excluding a non-zero number of keys internal func copyElements(to otherDoc: inout BSONDocument, excluding keys: [String]) throws { guard !keys.isEmpty else { - throw MongoError.InternalError(message: "No keys to exclude, use 'bson_copy' instead") + throw BSONError.InternalError(message: "No keys to exclude, use 'bson_copy' instead") } let cStrings: [ContiguousArray] = keys.map { $0.utf8CString } @@ -263,7 +260,7 @@ extension BSONDocument { var cPtrs: [UnsafePointer] = try cStrings.map { cString in let bufferPtr: UnsafeBufferPointer = cString.withUnsafeBufferPointer { $0 } guard let cPtr = bufferPtr.baseAddress else { - throw MongoError.InternalError(message: "Failed to copy strings") + throw BSONError.InternalError(message: "Failed to copy strings") } return cPtr } @@ -373,16 +370,16 @@ extension BSONDocument { * - Returns: the parsed `BSONDocument` * * - Throws: - * - A `MongoError.InvalidArgumentError` if the data passed in is invalid JSON. + * - A `BSONError.InvalidArgumentError` if the data passed in is invalid JSON. */ public init(fromJSON: Data) throws { self._storage = Storage(stealing: try fromJSON.withUnsafeBytePointer { bytes in var error = bson_error_t() guard let bson = bson_new_from_json(bytes, fromJSON.count, &error) else { if error.domain == BSON_ERROR_JSON { - throw MongoError.InvalidArgumentError(message: "Invalid JSON: \(toErrorString(error))") + throw BSONError.InvalidArgumentError(message: "Invalid JSON: \(toErrorString(error))") } - throw MongoError.InternalError(message: toErrorString(error)) + throw BSONError.InternalError(message: toErrorString(error)) } return bson @@ -391,7 +388,7 @@ extension BSONDocument { /// Convenience initializer for constructing a `BSONDocument` from a `String`. /// - Throws: - /// - A `MongoError.InvalidArgumentError` if the string passed in is invalid JSON. + /// - A `BSONError.InvalidArgumentError` if the string passed in is invalid JSON. public init(fromJSON json: String) throws { // `String`s are Unicode under the hood so force unwrap always succeeds. // see https://www.objc.io/blog/2018/02/13/string-to-data-and-back/ @@ -401,15 +398,15 @@ extension BSONDocument { /** * Constructs a `BSONDocument` from raw BSON `Data`. * - Throws: - * - A `MongoError.InvalidArgumentError` if `bson` is too short or too long to be valid BSON. - * - A `MongoError.InvalidArgumentError` if the first four bytes of `bson` do not contain `bson.count`. - * - A `MongoError.InvalidArgumentError` if the final byte of `bson` is not a null byte. + * - A `BSONError.InvalidArgumentError` if `bson` is too short or too long to be valid BSON. + * - A `BSONError.InvalidArgumentError` if the first four bytes of `bson` do not contain `bson.count`. + * - A `BSONError.InvalidArgumentError` if the final byte of `bson` is not a null byte. * - SeeAlso: http://bsonspec.org/ */ public init(fromBSON bson: Data) throws { self._storage = Storage(stealing: try bson.withUnsafeBytePointer { bytes in guard let data = bson_new_from_data(bytes, bson.count) else { - throw MongoError.InvalidArgumentError(message: "Invalid BSON data") + throw BSONError.InvalidArgumentError(message: "Invalid BSON data") } return data }) @@ -498,7 +495,7 @@ extension BSONDocument: BSONValue { try document.withMutableBSONPointer { docPtr in try self.withBSONPointer { nestedDocPtr in guard bson_append_document(docPtr, key, Int32(key.utf8.count), nestedDocPtr) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -520,7 +517,7 @@ extension BSONDocument: BSONValue { bson_iter_document(iterPtr, &length, document) guard let docData = bson_new_from_data(document.pointee, Int(length)) else { - throw MongoError.InternalError(message: "Failed to create a Document from iterator") + throw BSONError.InternalError(message: "Failed to create a Document from iterator") } return self.init(stealing: docData) diff --git a/Sources/MongoSwift/BSON/BSONDocumentIterator.swift b/Sources/MongoSwift/BSON/BSONDocumentIterator.swift index 7056ad6b3..7201337d0 100644 --- a/Sources/MongoSwift/BSON/BSONDocumentIterator.swift +++ b/Sources/MongoSwift/BSON/BSONDocumentIterator.swift @@ -102,10 +102,10 @@ public class BSONDocumentIterator: IteratorProtocol { /// Returns the current value (equivalent to the `currentValue` property) or throws on error. /// /// - Throws: - /// - `MongoError.InternalError` if the current value of this `BSONDocumentIterator` cannot be decoded to BSON. + /// - `BSONError.InternalError` if the current value of this `BSONDocumentIterator` cannot be decoded to BSON. internal func safeCurrentValue() throws -> BSON { guard let bsonType = BSONDocumentIterator.bsonTypeMap[currentType] else { - throw MongoError.InternalError( + throw BSONError.InternalError( message: "Unknown BSONType for iterator's current value with type: \(self.currentType)" ) } @@ -173,19 +173,13 @@ public class BSONDocumentIterator: IteratorProtocol { self.advance() ? (self.currentKey, self.currentValue) : nil } - /** - * Overwrites the current value of this `BSONDocumentIterator` with the supplied value. - * - * - Throws: - * - `MongoError.InternalError` if the new value is an `Int` and cannot be written to BSON. - * - `MongoError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted. - */ - internal func overwriteCurrentValue(with newValue: Overwritable) throws { + /// Overwrites the current value of this `BSONDocumentIterator` with the supplied value. + internal func overwriteCurrentValue(with newValue: Overwritable) { let newValueType = type(of: newValue).bsonType guard newValueType == self.currentType else { fatalError("Expected \(newValue) to have BSON type \(self.currentType), but has type \(newValueType)") } - try newValue.writeToCurrentPosition(of: self) + newValue.writeToCurrentPosition(of: self) } /// Internal helper function for explicitly accessing the `bson_iter_t` as an unsafe pointer diff --git a/Sources/MongoSwift/BSON/BSONEncoder.swift b/Sources/MongoSwift/BSON/BSONEncoder.swift index a4b7c9599..9bbaf640a 100644 --- a/Sources/MongoSwift/BSON/BSONEncoder.swift +++ b/Sources/MongoSwift/BSON/BSONEncoder.swift @@ -149,27 +149,37 @@ public class BSONEncoder { let encoder = _BSONEncoder(options: self.options) - guard let boxedValue = try encoder.box_(value) else { - throw EncodingError.invalidValue( - value, - EncodingError.Context( - codingPath: [], - debugDescription: "Top-level \(T.self) did not encode any values." + do { + guard let boxedValue = try encoder.box_(value) else { + throw EncodingError.invalidValue( + value, + EncodingError.Context( + codingPath: [], + debugDescription: "Top-level \(T.self) did not encode any values." + ) ) - ) - } + } + + guard let dict = boxedValue as? MutableDictionary else { + throw EncodingError.invalidValue( + value, + EncodingError.Context( + codingPath: [], + debugDescription: "Top-level \(T.self) was not encoded as a complete document." + ) + ) + } - guard let dict = boxedValue as? MutableDictionary else { + return try dict.toDocument() + } catch let error as BSONErrorProtocol { throw EncodingError.invalidValue( value, EncodingError.Context( codingPath: [], - debugDescription: "Top-level \(T.self) was not encoded as a complete document." + debugDescription: error.errorDescription ?? "Unknown Error occurred while encoding BSON" ) ) } - - return dict.toDocument() } /** @@ -492,7 +502,7 @@ extension _BSONEncoder { if let bsonValue = value as? BSONValue { return bsonValue } else if let bsonArray = value as? [BSONValue] { - return bsonArray.map { $0.bson } + return try bsonArray.map { $0.bson } } // The value should request a container from the _BSONEncoder. @@ -733,7 +743,7 @@ extension _BSONEncoder: SingleValueEncodingContainer { private class MutableArray: BSONValue { fileprivate static var bsonType: BSONType { .array } - fileprivate var bson: BSON { .array(self.array.map { $0.bson }) } + fileprivate var bson: BSON { fatalError("MutableArray: BSONValue.bson should be unused") } fileprivate var array = [BSONValue]() @@ -766,6 +776,18 @@ private class MutableArray: BSONValue { required convenience init(from _: Decoder) throws { fatalError("`MutableArray` is not meant to be initialized from a `Decoder`") } + + internal func toBSONArray() throws -> [BSON] { + try self.array.map { + if let item = $0 as? MutableDictionary { + return try item.toDocument().bson + } + if let item = $0 as? MutableArray { + return try item.toBSONArray().bson + } + return $0.bson + } + } } /// A private class wrapping a Swift dictionary so we can pass it by reference @@ -774,8 +796,7 @@ private class MutableArray: BSONValue { private class MutableDictionary: BSONValue { fileprivate static var bsonType: BSONType { .document } - // This is unused - fileprivate var bson: BSON { .document(self.toDocument()) } + fileprivate var bson: BSON { fatalError("MutableDictionary: BSONValue.bson should be unused") } // rather than using a dictionary, do this so we preserve key orders fileprivate var keys = [String]() @@ -803,10 +824,19 @@ private class MutableDictionary: BSONValue { } /// Converts self to a `BSONDocument` with equivalent key-value pairs. - fileprivate func toDocument() -> BSONDocument { + fileprivate func toDocument() throws -> BSONDocument { var doc = BSONDocument() for i in 0.. BSONErrorProtocol { + BSONError.LogicError( + message: "Tried to retreive a \(type) from an iterator whose next type " + + "is \(iter.currentType) for key \(iter.currentKey)" + ) +} + +/// Error thrown when a BSONValue type introduced by the driver (e.g. BSONObjectID) is encoded not using BSONEncoder +internal func bsonEncodingUnsupportedError(value: T, at codingPath: [CodingKey]) -> EncodingError { + let description = "Encoding \(T.self) BSONValue type with a non-BSONEncoder is currently unsupported" + + return EncodingError.invalidValue( + value, + EncodingError.Context(codingPath: codingPath, debugDescription: description) + ) +} + +/// Error thrown when a BSONValue type introduced by the driver (e.g. BSONObjectID) is decoded not using BSONDecoder +internal func bsonDecodingUnsupportedError(type _: T.Type, at codingPath: [CodingKey]) -> DecodingError { + let description = "Initializing a \(T.self) BSONValue type with a non-BSONDecoder is currently unsupported" + + return DecodingError.typeMismatch( + T.self, + DecodingError.Context(codingPath: codingPath, debugDescription: description) + ) +} + +/** + * Error thrown when a `BSONValue` type introduced by the driver (e.g. BSONObjectID) is decoded directly via the + * top-level `BSONDecoder`. + */ +internal func bsonDecodingDirectlyError(type _: T.Type, at codingPath: [CodingKey]) -> DecodingError { + let description = "Cannot initialize BSONValue type \(T.self) directly from BSONDecoder. It must be decoded as " + + "a member of a struct or a class." + + return DecodingError.typeMismatch( + T.self, + DecodingError.Context(codingPath: codingPath, debugDescription: description) + ) +} + +/** + * This function determines which error to throw when a driver-introduced BSON type is decoded via its init(decoder). + * The types that use this function are all BSON primitives, so they should be decoded directly in `_BSONDecoder`. If + * execution reaches their decoding initializer, it means something went wrong. This function determines an appropriate + * error to throw for each possible case. + * + * Some example cases: + * - Decoding directly from the BSONDecoder top-level (e.g. BSONDecoder().decode(BSONObjectID.self, from: ...)) + * - Encountering the wrong type of BSONValue (e.g. expected "_id" to be an `BSONObjectID`, got a `BSONDocument` + * instead) + * - Attempting to decode a driver-introduced BSONValue with a non-BSONDecoder + */ +internal func getDecodingError(type _: T.Type, decoder: Decoder) -> DecodingError { + if let bsonDecoder = decoder as? _BSONDecoder { + // Cannot decode driver-introduced BSONValues directly + if decoder.codingPath.isEmpty { + return bsonDecodingDirectlyError(type: T.self, at: decoder.codingPath) + } + + // Got the wrong BSONValue type + return DecodingError._typeMismatch( + at: decoder.codingPath, + expectation: T.self, + reality: bsonDecoder.storage.topContainer.bsonValue + ) + } + + // Non-BSONDecoders are currently unsupported + return bsonDecodingUnsupportedError(type: T.self, at: decoder.codingPath) +} diff --git a/Sources/MongoSwift/BSON/BSONUtil.swift b/Sources/MongoSwift/BSON/BSONUtil.swift new file mode 100644 index 000000000..1097adab2 --- /dev/null +++ b/Sources/MongoSwift/BSON/BSONUtil.swift @@ -0,0 +1,21 @@ +/* + * BSONUtil contains helpers to wrap the underlying BSON library to assist in providing a consistent API + */ + +/// We don't want driver users to handle any BSONErrors +/// this will convert BSONError.* thrown from `fn` to MongoError.* and rethrow +internal func convertingBSONErrors(_ body: () throws -> T) rethrows -> T { + do { + return try body() + } catch let error as BSONError.InvalidArgumentError { + throw MongoError.InvalidArgumentError(message: error.message) + } catch let error as BSONError.InternalError { + throw MongoError.InternalError(message: error.message) + } catch let error as BSONError.LogicError { + throw MongoError.LogicError(message: error.message) + } catch let error as BSONError.DocumentTooLargeError { + throw MongoError.InternalError(message: error.message) + } catch let error as BSONErrorProtocol { + throw MongoError.InternalError(message: error.errorDescription ?? "Uknown BSON Error") + } +} diff --git a/Sources/MongoSwift/BSON/BSONValue.swift b/Sources/MongoSwift/BSON/BSONValue.swift index eead3e95b..f5106a994 100644 --- a/Sources/MongoSwift/BSON/BSONValue.swift +++ b/Sources/MongoSwift/BSON/BSONValue.swift @@ -70,9 +70,8 @@ internal protocol BSONValue: Codable { * - key: A `String`, the key under which to store the value. * * - Throws: - * - `MongoError.InternalError` if the `DocumentStorage` would exceed the maximum size by encoding this + * - `BSONError.DocumentTooLargeError` if the `BSONDocument` would exceed the maximum size by encoding this * key-value pair. - * - `MongoError.LogicError` if the value is an `Array` and it contains a non-`BSONValue` element. */ func encode(to document: inout BSONDocument, forKey key: String) throws @@ -80,7 +79,7 @@ internal protocol BSONValue: Codable { * Given a `BSONDocumentIterator` known to have a next value of this type, * initializes the value. * - * - Throws: `MongoError.LogicError` if the current type of the `BSONDocumentIterator` does not correspond to the + * - Throws: `BSONError.LogicError` if the current type of the `BSONDocumentIterator` does not correspond to the * associated type of this `BSONValue`. */ static func from(iterator iter: BSONDocumentIterator) throws -> BSON @@ -117,7 +116,7 @@ extension Array: BSONValue where Element == BSON { // since an array is a nested object with keys '0', '1', etc., // create a new Document using the array data so we can recursively parse guard let arrayData = bson_new_from_data(array.pointee, Int(length)) else { - throw MongoError.InternalError(message: "Failed to create an Array from iterator") + throw BSONError.InternalError(message: "Failed to create an Array from iterator") } let arrDoc = BSONDocument(stealing: arrayData) @@ -134,7 +133,7 @@ extension Array: BSONValue where Element == BSON { try document.withMutableBSONPointer { docPtr in try arr.withBSONPointer { arrPtr in guard bson_append_array(docPtr, key, Int32(key.utf8.count), arrPtr) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -180,7 +179,7 @@ internal struct BSONNull: BSONValue, Codable, Equatable { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_null(docPtr, key, Int32(key.utf8.count)) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -240,18 +239,18 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { /// Initializes a `Subtype` with a custom value. This value must be in the range 0x80-0xFF. /// - Throws: - /// - `MongoError.InvalidArgumentError` if value passed is outside of the range 0x80-0xFF + /// - `BSONError.InvalidArgumentError` if value passed is outside of the range 0x80-0xFF public static func userDefined(_ value: Int) throws -> Subtype { guard let byteValue = UInt8(exactly: value) else { - throw MongoError.InvalidArgumentError(message: "Cannot represent \(value) as UInt8") + throw BSONError.InvalidArgumentError(message: "Cannot represent \(value) as UInt8") } guard byteValue >= 0x80 else { - throw MongoError.InvalidArgumentError( + throw BSONError.InvalidArgumentError( message: "userDefined value must be greater than or equal to 0x80 got \(byteValue)" ) } guard let subtype = Subtype(rawValue: byteValue) else { - throw MongoError.InvalidArgumentError(message: "Cannot represent \(byteValue) as Subtype") + throw BSONError.InvalidArgumentError(message: "Cannot represent \(byteValue) as Subtype") } return subtype } @@ -259,7 +258,7 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { /// Initializes a `BSONBinary` instance from a `UUID`. /// - Throws: - /// - `MongoError.InvalidArgumentError` if a `BSONBinary` cannot be constructed from this UUID. + /// - `BSONError.InvalidArgumentError` if a `BSONBinary` cannot be constructed from this UUID. public init(from uuid: UUID) throws { let uuidt = uuid.uuid @@ -275,10 +274,10 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { /// Initializes a `BSONBinary` instance from a `Data` object and a `UInt8` subtype. /// - Throws: - /// - `MongoError.InvalidArgumentError` if the provided data is incompatible with the specified subtype. + /// - `BSONError.InvalidArgumentError` if the provided data is incompatible with the specified subtype. public init(data: Data, subtype: Subtype) throws { if [Subtype.uuid, Subtype.uuidDeprecated].contains(subtype) && data.count != 16 { - throw MongoError.InvalidArgumentError( + throw BSONError.InvalidArgumentError( message: "Binary data with UUID subtype must be 16 bytes, but data has \(data.count) bytes" ) @@ -291,11 +290,11 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { /// Initializes a `BSONBinary` instance from a base64 `String` and a `Subtype`. /// - Throws: - /// - `MongoError.InvalidArgumentError` if the base64 `String` is invalid or if the provided data is + /// - `BSONError.InvalidArgumentError` if the base64 `String` is invalid or if the provided data is /// incompatible with the specified subtype. public init(base64: String, subtype: Subtype) throws { guard let dataObj = Data(base64Encoded: base64) else { - throw MongoError.InvalidArgumentError( + throw BSONError.InvalidArgumentError( message: "failed to create Data object from invalid base64 string \(base64)" ) @@ -315,11 +314,11 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { let subtype = bson_subtype_t(UInt32(self.subtype.rawValue)) let length = self.data.writerIndex guard let byteArray = self.data.getBytes(at: 0, length: length) else { - throw MongoError.InternalError(message: "Cannot read \(length) bytes from Binary.data") + throw BSONError.InternalError(message: "Cannot read \(length) bytes from Binary.data") } try document.withMutableBSONPointer { docPtr in guard bson_append_binary(docPtr, key, Int32(key.utf8.count), subtype, byteArray, UInt32(length)) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -341,7 +340,7 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { bson_iter_binary(iterPtr, &subtype, &length, dataPointer) guard let data = dataPointer.pointee else { - throw MongoError.InternalError(message: "failed to retrieve data stored for binary BSON value") + throw BSONError.InternalError(message: "failed to retrieve data stored for binary BSON value") } let dataObj = Data(bytes: data, count: Int(length)) @@ -351,16 +350,16 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { /// Converts this `BSONBinary` instance to a `UUID`. /// - Throws: - /// - `MongoError.InvalidArgumentError` if a non-UUID subtype is set on this `BSONBinary`. + /// - `BSONError.InvalidArgumentError` if a non-UUID subtype is set on this `BSONBinary`. public func toUUID() throws -> UUID { guard [Subtype.uuid, Subtype.uuidDeprecated].contains(self.subtype) else { - throw MongoError.InvalidArgumentError( + throw BSONError.InvalidArgumentError( message: "Expected a UUID binary subtype, got subtype \(self.subtype) instead." ) } guard let data = self.data.getBytes(at: 0, length: 16) else { - throw MongoError.InternalError(message: "Unable to read 16 bytes from Binary.data") + throw BSONError.InternalError(message: "Unable to read 16 bytes from Binary.data") } let uuid: uuid_t = ( @@ -383,7 +382,7 @@ extension Bool: BSONValue { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_bool(docPtr, key, Int32(key.utf8.count), self) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -417,7 +416,7 @@ extension Date: BSONValue { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_date_time(docPtr, key, Int32(key.utf8.count), self.msSinceEpoch) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -463,7 +462,7 @@ public struct BSONDBPointer: BSONValue, Codable, Equatable, Hashable { try document.withMutableBSONPointer { docPtr in try withUnsafePointer(to: self.id.oid) { oidPtr in guard bson_append_dbpointer(docPtr, key, Int32(key.utf8.count), self.ref, oidPtr) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -524,7 +523,7 @@ public struct BSONDecimal128: BSONValue, Equatable, Codable, CustomStringConvert * - a BSONDecimal128 number as a string. * * - Throws: - * - A `MongoError.InvalidArgumentError` if the string does not represent a BSONDecimal128 encodable value. + * - A `BSONError.InvalidArgumentError` if the string does not represent a BSONDecimal128 encodable value. * * - SeeAlso: https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst */ @@ -545,7 +544,7 @@ public struct BSONDecimal128: BSONValue, Equatable, Codable, CustomStringConvert try document.withMutableBSONPointer { docPtr in try withUnsafePointer(to: self.decimal128) { ptr in guard bson_append_decimal128(docPtr, key, Int32(key.utf8.count), ptr) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -554,11 +553,11 @@ public struct BSONDecimal128: BSONValue, Equatable, Codable, CustomStringConvert /// Returns the provided string as a `bson_decimal128_t`, or throws an error if initialization fails due an /// invalid string. /// - Throws: - /// - `MongoError.InvalidArgumentError` if the parameter string does not correspond to a valid `BSONDecimal128`. + /// - `BSONError.InvalidArgumentError` if the parameter string does not correspond to a valid `BSONDecimal128`. internal static func toLibBSONType(_ str: String) throws -> bson_decimal128_t { var value = bson_decimal128_t() guard bson_decimal128_from_string(str, &value) else { - throw MongoError.InvalidArgumentError(message: "Invalid Decimal128 string \(str)") + throw BSONError.InvalidArgumentError(message: "Invalid Decimal128 string \(str)") } return value } @@ -595,7 +594,7 @@ extension Double: BSONValue { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_double(docPtr, key, Int32(key.utf8.count), self) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -620,7 +619,7 @@ extension Int32: BSONValue { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_int32(docPtr, key, Int32(key.utf8.count), self) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -645,7 +644,7 @@ extension Int64: BSONValue { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_int64(docPtr, key, Int32(key.utf8.count), self) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -692,7 +691,7 @@ public struct BSONCodeWithScope: BSONValue, Equatable, Codable, Hashable { try document.withMutableBSONPointer { docPtr in try self.scope.withBSONPointer { scopePtr in guard bson_append_code_with_scope(docPtr, key, Int32(key.utf8.count), self.code, scopePtr) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -714,7 +713,7 @@ public struct BSONCodeWithScope: BSONValue, Equatable, Codable, Hashable { let code = String(cString: bson_iter_codewscope(iterPtr, &length, &scopeLength, scopePointer)) guard let scopeData = bson_new_from_data(scopePointer.pointee, Int(scopeLength)) else { - throw MongoError.InternalError(message: "Failed to create a bson_t from scope data") + throw BSONError.InternalError(message: "Failed to create a bson_t from scope data") } let scopeDoc = BSONDocument(stealing: scopeData) @@ -748,7 +747,7 @@ public struct BSONCode: BSONValue, Equatable, Codable, Hashable { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_code(docPtr, key, Int32(key.utf8.count), self.code) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -773,7 +772,7 @@ internal struct BSONMaxKey: BSONValue, Equatable, Codable, Hashable { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_maxkey(docPtr, key, Int32(key.utf8.count)) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -806,7 +805,7 @@ internal struct BSONMinKey: BSONValue, Equatable, Codable, Hashable { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_minkey(docPtr, key, Int32(key.utf8.count)) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -862,11 +861,11 @@ public struct BSONObjectID: BSONValue, Equatable, CustomStringConvertible, Codab /// Initializes an `BSONObjectID` from the provided hex `String`. /// - Throws: - /// - `MongoError.InvalidArgumentError` if string passed is not a valid BSONObjectID + /// - `BSONError.InvalidArgumentError` if string passed is not a valid BSONObjectID /// - SeeAlso: https://github.com/mongodb/specifications/blob/master/source/objectid.rst public init(_ hex: String) throws { guard bson_oid_is_valid(hex, hex.utf8.count) else { - throw MongoError.InvalidArgumentError(message: "Cannot create ObjectId from \(hex)") + throw BSONError.InvalidArgumentError(message: "Cannot create ObjectId from \(hex)") } var oid_t = bson_oid_t() bson_oid_init_from_string(&oid_t, hex) @@ -903,7 +902,7 @@ public struct BSONObjectID: BSONValue, Equatable, CustomStringConvertible, Codab try document.withMutableBSONPointer { docPtr in try withUnsafePointer(to: self.oid) { oidPtr in guard bson_append_oid(docPtr, key, Int32(key.utf8.count), oidPtr) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1005,7 +1004,7 @@ public struct BSONRegularExpression: BSONValue, Equatable, Codable, Hashable { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_regex(docPtr, key, Int32(key.utf8.count), self.pattern, self.options) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1024,7 +1023,7 @@ public struct BSONRegularExpression: BSONValue, Equatable, Codable, Hashable { let patternString = String(cString: pattern) guard let stringOptions = options.pointee else { - throw MongoError.InternalError(message: "Failed to retrieve regular expression options") + throw BSONError.InternalError(message: "Failed to retrieve regular expression options") } let optionsString = String(cString: stringOptions) @@ -1050,7 +1049,7 @@ extension String: BSONValue { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_utf8(docPtr, key, Int32(key.utf8.count), self, Int32(self.utf8.count)) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1069,11 +1068,11 @@ extension String: BSONValue { } guard bson_utf8_validate(strValue, Int(length), true) else { - throw MongoError.InternalError(message: "String \(strValue) not valid UTF-8") + throw BSONError.InternalError(message: "String \(strValue) not valid UTF-8") } guard let out = self.init(rawStringData: strValue, length: Int(length)) else { - throw MongoError.InternalError( + throw BSONError.InternalError( message: "Underlying string data could not be parsed to a Swift String" ) } @@ -1118,7 +1117,7 @@ public struct BSONSymbol: BSONValue, CustomStringConvertible, Codable, Equatable self.stringValue, Int32(self.stringValue.utf8.count) ) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1131,7 +1130,7 @@ public struct BSONSymbol: BSONValue, CustomStringConvertible, Codable, Equatable } guard let strValue = String(rawStringData: cStr, length: Int(length)) else { - throw MongoError.InternalError(message: "Cannot parse String from underlying data") + throw BSONError.InternalError(message: "Cannot parse String from underlying data") } return BSONSymbol(strValue) @@ -1174,7 +1173,7 @@ public struct BSONTimestamp: BSONValue, Equatable, Codable, Hashable { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_timestamp(docPtr, key, Int32(key.utf8.count), self.timestamp, self.increment) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1214,7 +1213,7 @@ internal struct BSONUndefined: BSONValue, Equatable, Codable { internal func encode(to document: inout BSONDocument, forKey key: String) throws { try document.withMutableBSONPointer { docPtr in guard bson_append_undefined(docPtr, key, Int32(key.utf8.count)) else { - throw bsonTooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1234,71 +1233,6 @@ extension BSONUndefined: Hashable { } } -/// Error thrown when a BSONValue type introduced by the driver (e.g. BSONObjectID) is encoded not using BSONEncoder -internal func bsonEncodingUnsupportedError(value: T, at codingPath: [CodingKey]) -> EncodingError { - let description = "Encoding \(T.self) BSONValue type with a non-BSONEncoder is currently unsupported" - - return EncodingError.invalidValue( - value, - EncodingError.Context(codingPath: codingPath, debugDescription: description) - ) -} - -/// Error thrown when a BSONValue type introduced by the driver (e.g. BSONObjectID) is decoded not using BSONDecoder -private func bsonDecodingUnsupportedError(type _: T.Type, at codingPath: [CodingKey]) -> DecodingError { - let description = "Initializing a \(T.self) BSONValue type with a non-BSONDecoder is currently unsupported" - - return DecodingError.typeMismatch( - T.self, - DecodingError.Context(codingPath: codingPath, debugDescription: description) - ) -} - -/** - * Error thrown when a `BSONValue` type introduced by the driver (e.g. BSONObjectID) is decoded directly via the - * top-level `BSONDecoder`. - */ -private func bsonDecodingDirectlyError(type _: T.Type, at codingPath: [CodingKey]) -> DecodingError { - let description = "Cannot initialize BSONValue type \(T.self) directly from BSONDecoder. It must be decoded as " + - "a member of a struct or a class." - - return DecodingError.typeMismatch( - T.self, - DecodingError.Context(codingPath: codingPath, debugDescription: description) - ) -} - -/** - * This function determines which error to throw when a driver-introduced BSON type is decoded via its init(decoder). - * The types that use this function are all BSON primitives, so they should be decoded directly in `_BSONDecoder`. If - * execution reaches their decoding initializer, it means something went wrong. This function determines an appropriate - * error to throw for each possible case. - * - * Some example cases: - * - Decoding directly from the BSONDecoder top-level (e.g. BSONDecoder().decode(BSONObjectID.self, from: ...)) - * - Encountering the wrong type of BSONValue (e.g. expected "_id" to be an `BSONObjectID`, got a `BSONDocument` - * instead) - * - Attempting to decode a driver-introduced BSONValue with a non-BSONDecoder - */ -internal func getDecodingError(type _: T.Type, decoder: Decoder) -> DecodingError { - if let bsonDecoder = decoder as? _BSONDecoder { - // Cannot decode driver-introduced BSONValues directly - if decoder.codingPath.isEmpty { - return bsonDecodingDirectlyError(type: T.self, at: decoder.codingPath) - } - - // Got the wrong BSONValue type - return DecodingError._typeMismatch( - at: decoder.codingPath, - expectation: T.self, - reality: bsonDecoder.storage.topContainer.bsonValue - ) - } - - // Non-BSONDecoders are currently unsupported - return bsonDecodingUnsupportedError(type: T.self, at: decoder.codingPath) -} - extension Data { /// Gets access to the start of the data buffer in the form of an UnsafeMutablePointer. Useful for calling C /// API methods that expect a location for a string. **You must only call this method on Data instances with diff --git a/Sources/MongoSwift/BSON/Overwritable.swift b/Sources/MongoSwift/BSON/Overwritable.swift index dbd2a064c..d8ec09f78 100644 --- a/Sources/MongoSwift/BSON/Overwritable.swift +++ b/Sources/MongoSwift/BSON/Overwritable.swift @@ -3,15 +3,8 @@ import Foundation /// A protocol indicating that a type can be overwritten in-place on a `bson_t`. internal protocol Overwritable: BSONValue { - /** - * Overwrites the value at the current position of the iterator with self. - * - * - Throws: - * - `MongoError.InternalError` if the `BSONValue` is an `Int` and cannot be written to BSON. - * - `MongoError.LogicError` if the `BSONValue` is a `BSONDecimal128` or `BSONObjectID` and is improperly - * formatted. - */ - func writeToCurrentPosition(of iter: BSONDocumentIterator) throws + /// Overwrites the value at the current position of the iterator with self. + func writeToCurrentPosition(of iter: BSONDocumentIterator) } extension Bool: Overwritable { @@ -39,7 +32,7 @@ extension Double: Overwritable { } extension BSONDecimal128: Overwritable { - internal func writeToCurrentPosition(of iter: BSONDocumentIterator) throws { + internal func writeToCurrentPosition(of iter: BSONDocumentIterator) { withUnsafePointer(to: self.decimal128) { decPtr in iter.withMutableBSONIterPointer { iterPtr in bson_iter_overwrite_decimal128(iterPtr, decPtr) @@ -49,7 +42,7 @@ extension BSONDecimal128: Overwritable { } extension BSONObjectID: Overwritable { - internal func writeToCurrentPosition(of iter: BSONDocumentIterator) throws { + internal func writeToCurrentPosition(of iter: BSONDocumentIterator) { withUnsafePointer(to: self.oid) { oidPtr in iter.withMutableBSONIterPointer { iterPtr in bson_iter_overwrite_oid(iterPtr, oidPtr) } } diff --git a/Sources/MongoSwift/MongoCollection+BulkWrite.swift b/Sources/MongoSwift/MongoCollection+BulkWrite.swift index 3ef853104..7d82deb39 100644 --- a/Sources/MongoSwift/MongoCollection+BulkWrite.swift +++ b/Sources/MongoSwift/MongoCollection+BulkWrite.swift @@ -97,12 +97,12 @@ public enum WriteModel { } case let .insertOne(value): - let document = try encoder.encode(value).withID() + let document = try convertingBSONErrors { try encoder.encode(value).withID() } success = document.withBSONPointer { docPtr in mongoc_bulk_operation_insert_with_opts(bulk, docPtr, nil, &error) } - guard let insertedID = try document.getValue(for: "_id") else { + guard let insertedID = (try convertingBSONErrors { try document.getValue(for: "_id") }) else { // we called `withID()`, so this should be present. fatalError("Failed to get value for _id from document") } @@ -408,22 +408,36 @@ public struct BulkWriteResult: Decodable { return nil } - self.deletedCount = try reply.getValue(for: MongocKeys.deletedCount.rawValue)?.toInt() ?? 0 - self.insertedCount = try reply.getValue(for: MongocKeys.insertedCount.rawValue)?.toInt() ?? 0 + self.deletedCount = try convertingBSONErrors { + try reply.getValue(for: MongocKeys.deletedCount.rawValue)?.toInt() ?? 0 + } + self.insertedCount = try convertingBSONErrors { + try reply.getValue(for: MongocKeys.insertedCount.rawValue)?.toInt() ?? 0 + } self.insertedIDs = insertedIDs - self.matchedCount = try reply.getValue(for: MongocKeys.matchedCount.rawValue)?.toInt() ?? 0 - self.modifiedCount = try reply.getValue(for: MongocKeys.modifiedCount.rawValue)?.toInt() ?? 0 - self.upsertedCount = try reply.getValue(for: MongocKeys.upsertedCount.rawValue)?.toInt() ?? 0 + self.matchedCount = try convertingBSONErrors { + try reply.getValue(for: MongocKeys.matchedCount.rawValue)?.toInt() ?? 0 + } + self.modifiedCount = try convertingBSONErrors { + try reply.getValue(for: MongocKeys.modifiedCount.rawValue)?.toInt() ?? 0 + } + self.upsertedCount = try convertingBSONErrors { + try reply.getValue(for: MongocKeys.upsertedCount.rawValue)?.toInt() ?? 0 + } var upsertedIDs = [Int: BSON]() - if let upserted = try reply.getValue(for: MongocKeys.upserted.rawValue)?.arrayValue { + if let upserted = (try convertingBSONErrors { + try reply.getValue(for: MongocKeys.upserted.rawValue)?.arrayValue + }) { guard let upserted = upserted.toArrayOf(BSONDocument.self) else { throw MongoError.InternalError(message: "\"upserted\" array did not contain only documents") } for upsert in upserted { - guard let index = try upsert.getValue(for: "index")?.toInt() else { + guard let index = (try convertingBSONErrors { + try upsert.getValue(for: "index")?.toInt() + }) else { throw MongoError.InternalError(message: "Could not cast upserted index to `Int`") } upsertedIDs[index] = upsert["_id"] diff --git a/Sources/MongoSwift/MongoError.swift b/Sources/MongoSwift/MongoError.swift index a67428df2..3c00e8499 100644 --- a/Sources/MongoSwift/MongoError.swift +++ b/Sources/MongoSwift/MongoError.swift @@ -511,20 +511,6 @@ internal func toErrorString(_ error: bson_error_t) -> String { } } -internal func bsonTooLargeError(value: BSONValue, forKey: String) -> MongoErrorProtocol { - MongoError.InternalError( - message: - "Failed to set value for key \(forKey) to \(value) with BSON type \(value.bsonType): document too large" - ) -} - -internal func wrongIterTypeError(_ iter: BSONDocumentIterator, expected type: BSONValue.Type) -> MongoErrorProtocol { - MongoError.LogicError( - message: "Tried to retreive a \(type) from an iterator whose next type " + - "is \(iter.currentType) for key \(iter.currentKey)" - ) -} - internal let failedToRetrieveCursorMessage = "Couldn't get cursor from the server" extension MongoErrorProtocol { diff --git a/Sources/MongoSwift/Operations/DistinctOperation.swift b/Sources/MongoSwift/Operations/DistinctOperation.swift index 0e97c86bf..cc7f9d329 100644 --- a/Sources/MongoSwift/Operations/DistinctOperation.swift +++ b/Sources/MongoSwift/Operations/DistinctOperation.swift @@ -65,7 +65,9 @@ internal struct DistinctOperation: Operation { } } - guard let values = try reply.getValue(for: "values")?.arrayValue else { + guard let values = (try convertingBSONErrors { + try reply.getValue(for: "values")?.arrayValue + }) else { throw MongoError.InternalError( message: "expected server reply \(reply) to contain an array of distinct values" diff --git a/Sources/MongoSwift/Operations/FindAndModifyOperation.swift b/Sources/MongoSwift/Operations/FindAndModifyOperation.swift index b568b39dd..985d59715 100644 --- a/Sources/MongoSwift/Operations/FindAndModifyOperation.swift +++ b/Sources/MongoSwift/Operations/FindAndModifyOperation.swift @@ -74,9 +74,13 @@ internal class FindAndModifyOptions { // build an "extra" document of fields without their own setters var extra = BSONDocument() if let filters = arrayFilters { - try extra.setValue(for: "arrayFilters", to: .array(filters.map { .document($0) })) + try convertingBSONErrors { + try extra.setValue(for: "arrayFilters", to: .array(filters.map { .document($0) })) + } } - if let coll = collation { try extra.setValue(for: "collation", to: .document(coll)) } + if let coll = collation { try convertingBSONErrors { + try extra.setValue(for: "collation", to: .document(coll)) + } } // note: mongoc_find_and_modify_opts_set_max_time_ms() takes in a // uint32_t, but it should be a positive 64-bit integer, so we @@ -85,7 +89,9 @@ internal class FindAndModifyOptions { guard maxTime > 0 else { throw MongoError.InvalidArgumentError(message: "maxTimeMS must be positive, but got value \(maxTime)") } - try extra.setValue(for: "maxTimeMS", to: .int64(Int64(maxTime))) + try convertingBSONErrors { + try extra.setValue(for: "maxTimeMS", to: .int64(Int64(maxTime))) + } } if let wc = writeConcern { diff --git a/Sources/MongoSwiftSync/Exports.swift b/Sources/MongoSwiftSync/Exports.swift index ee3498ac2..c51a86895 100644 --- a/Sources/MongoSwiftSync/Exports.swift +++ b/Sources/MongoSwiftSync/Exports.swift @@ -20,6 +20,7 @@ @_exported import struct MongoSwift.BSONDocument @_exported import class MongoSwift.BSONDocumentIterator @_exported import class MongoSwift.BSONEncoder +@_exported import enum MongoSwift.BSONError @_exported import struct MongoSwift.BSONObjectID @_exported import struct MongoSwift.BSONRegularExpression @_exported import struct MongoSwift.BSONSymbol @@ -104,6 +105,7 @@ @_exported import enum MongoSwift.WriteModel // Protocols are not included in the types list, so we list them separately here. +@_exported import protocol MongoSwift.BSONErrorProtocol @_exported import protocol MongoSwift.CodingStrategyProvider @_exported import protocol MongoSwift.CommandEventHandler @_exported import protocol MongoSwift.MongoErrorProtocol diff --git a/Tests/BSONTests/BSONValueTests.swift b/Tests/BSONTests/BSONValueTests.swift index 349ac339a..3637ad200 100644 --- a/Tests/BSONTests/BSONValueTests.swift +++ b/Tests/BSONTests/BSONValueTests.swift @@ -18,9 +18,9 @@ final class BSONValueTests: MongoSwiftTestCase { // UUIDs must have 16 bytes expect(try BSONBinary(data: twoBytes, subtype: .uuidDeprecated)) - .to(throwError(errorType: MongoError.InvalidArgumentError.self)) + .to(throwError(errorType: BSONError.InvalidArgumentError.self)) expect(try BSONBinary(data: twoBytes, subtype: .uuid)) - .to(throwError(errorType: MongoError.InvalidArgumentError.self)) + .to(throwError(errorType: BSONError.InvalidArgumentError.self)) expect(try BSONBinary(data: sixteenBytes, subtype: .uuidDeprecated)).toNot(throwError()) expect(try BSONBinary(data: sixteenBytes, subtype: .uuid)).toNot(throwError()) } diff --git a/Tests/BSONTests/DocumentTests.swift b/Tests/BSONTests/DocumentTests.swift index 7ab4c78c5..5224d74a1 100644 --- a/Tests/BSONTests/DocumentTests.swift +++ b/Tests/BSONTests/DocumentTests.swift @@ -128,9 +128,9 @@ final class DocumentTests: MongoSwiftTestCase { // UUIDs must have 16 bytes expect(try BSONBinary(data: testData, subtype: .uuidDeprecated)) - .to(throwError(errorType: MongoError.InvalidArgumentError.self)) + .to(throwError(errorType: BSONError.InvalidArgumentError.self)) expect(try BSONBinary(data: testData, subtype: .uuid)) - .to(throwError(errorType: MongoError.InvalidArgumentError.self)) + .to(throwError(errorType: BSONError.InvalidArgumentError.self)) let expectedKeys = [ "string", "true", "false", "int", "int32", "int64", "double", "decimal128", @@ -928,7 +928,7 @@ final class DocumentTests: MongoSwiftTestCase { ] for data in invalidData { - expect(try BSONDocument(fromBSON: data)).to(throwError(errorType: MongoError.InvalidArgumentError.self)) + expect(try BSONDocument(fromBSON: data)).to(throwError(errorType: BSONError.InvalidArgumentError.self)) } } }