From 341f6643c01b0dd30a8c178883a3114823119b8c Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 29 May 2020 15:11:15 -0400 Subject: [PATCH 01/22] SWIFT-878: Create BSON Specific Error Types --- Sources/MongoSwift/BSON/BSONDocument.swift | 36 ++++++------ .../BSON/BSONDocumentIterator.swift | 8 +-- Sources/MongoSwift/BSON/BSONError.swift | 49 ++++++++++++++++ Sources/MongoSwift/BSON/BSONUtil.swift | 15 +++++ Sources/MongoSwift/BSON/BSONValue.swift | 56 +++++++++---------- Sources/MongoSwift/BSON/Overwritable.swift | 4 +- .../MongoCollection+BulkWrite.swift | 16 +++--- Sources/MongoSwift/MongoError.swift | 14 ----- .../Operations/DistinctOperation.swift | 2 +- .../Operations/FindAndModifyOperation.swift | 10 ++-- 10 files changed, 130 insertions(+), 80 deletions(-) create mode 100644 Sources/MongoSwift/BSON/BSONError.swift create mode 100644 Sources/MongoSwift/BSON/BSONUtil.swift diff --git a/Sources/MongoSwift/BSON/BSONDocument.swift b/Sources/MongoSwift/BSON/BSONDocument.swift index 610ac6bb3..47101d126 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,10 @@ 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.InternalError` if the new value is an `Int` and cannot be written to BSON. + * - `BSONError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted. + * - `BSONError.LogicError` if the new value is an `Array` and it contains a non-`BSONValue` element. + * - `BSONError.InternalError` 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 { @@ -210,7 +210,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 +232,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 +255,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 +263,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 +373,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 +391,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 +401,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 }) @@ -520,7 +520,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..06ea105d5 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)" ) } @@ -177,8 +177,8 @@ public class BSONDocumentIterator: IteratorProtocol { * 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. + * - `BSONError.InternalError` if the new value is an `Int` and cannot be written to BSON. + * - `BSONError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted. */ internal func overwriteCurrentValue(with newValue: Overwritable) throws { let newValueType = type(of: newValue).bsonType diff --git a/Sources/MongoSwift/BSON/BSONError.swift b/Sources/MongoSwift/BSON/BSONError.swift new file mode 100644 index 000000000..766e99f3b --- /dev/null +++ b/Sources/MongoSwift/BSON/BSONError.swift @@ -0,0 +1,49 @@ +import Foundation + +/// An empty protocol for encapsulating all errors that BSON package can throw. +public protocol BSONErrorProtocol: LocalizedError {} + +/// A protocol describing errors caused by improper usage of the BSON library by the user. +public protocol BSONUserError: BSONErrorProtocol {} + +/// The possible errors that can occur unexpectedly BSON library-side. +public protocol BSONRuntimeError: BSONErrorProtocol {} + +/// Namespace containing all the error types introduced by this BSON library and their dependent types. +public enum BSONError { + /// An error thrown when the user passes in invalid arguments to a BSON method. + public struct InvalidArgumentError: BSONUserError { + internal let message: String + + public var errorDescription: String? { self.message } + } + + /// An error thrown when the BSON library encounters a internal error not caused by the user. + /// This is usually indicative of a bug in the BSON library or system related failure. + public struct InternalError: BSONRuntimeError { + internal let message: String + + public var errorDescription: String? { self.message } + } + + /// An error thrown when the BSON library is incorrectly used. + public struct LogicError: BSONUserError { + internal let message: String + + public var errorDescription: String? { self.message } + } +} + +internal func bsonTooLargeError(value: BSONValue, forKey: String) -> BSONErrorProtocol { + BSONError.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) -> BSONErrorProtocol { + BSONError.LogicError( + message: "Tried to retreive a \(type) from an iterator whose next type " + + "is \(iter.currentType) for key \(iter.currentKey)" + ) +} diff --git a/Sources/MongoSwift/BSON/BSONUtil.swift b/Sources/MongoSwift/BSON/BSONUtil.swift new file mode 100644 index 000000000..b80357114 --- /dev/null +++ b/Sources/MongoSwift/BSON/BSONUtil.swift @@ -0,0 +1,15 @@ +internal func getValue(from document: BSONDocument, for key: String) throws -> BSON? { + do { + return try document.getValue(for: key) + } catch let error as BSONError.InvalidArgumentError { + throw BSONError.InvalidArgumentError(message: error.message) + } +} + +internal func setValue(in document: inout BSONDocument, for key: String, to value: BSON) throws { + do { + try document.setValue(for: key, to: value) + } catch let error as BSONError.InvalidArgumentError { + throw BSONError.InvalidArgumentError(message: error.message) + } +} diff --git a/Sources/MongoSwift/BSON/BSONValue.swift b/Sources/MongoSwift/BSON/BSONValue.swift index eead3e95b..ad280e428 100644 --- a/Sources/MongoSwift/BSON/BSONValue.swift +++ b/Sources/MongoSwift/BSON/BSONValue.swift @@ -70,9 +70,9 @@ 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.InternalError` if the `DocumentStorage` 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. + * - `BSONError.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 +80,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 +117,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) @@ -240,18 +240,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 +259,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 +275,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 +291,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,7 +315,7 @@ 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 { @@ -341,7 +341,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 +351,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 = ( @@ -524,7 +524,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 */ @@ -554,11 +554,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 } @@ -714,7 +714,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) @@ -862,11 +862,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) @@ -1024,7 +1024,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) @@ -1069,11 +1069,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" ) } @@ -1131,7 +1131,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) diff --git a/Sources/MongoSwift/BSON/Overwritable.swift b/Sources/MongoSwift/BSON/Overwritable.swift index dbd2a064c..a65d05325 100644 --- a/Sources/MongoSwift/BSON/Overwritable.swift +++ b/Sources/MongoSwift/BSON/Overwritable.swift @@ -7,8 +7,8 @@ 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 + * - `BSONError.InternalError` if the `BSONValue` is an `Int` and cannot be written to BSON. + * - `BSONError.LogicError` if the `BSONValue` is a `BSONDecimal128` or `BSONObjectID` and is improperly * formatted. */ func writeToCurrentPosition(of iter: BSONDocumentIterator) throws diff --git a/Sources/MongoSwift/MongoCollection+BulkWrite.swift b/Sources/MongoSwift/MongoCollection+BulkWrite.swift index 3ef853104..187301b4f 100644 --- a/Sources/MongoSwift/MongoCollection+BulkWrite.swift +++ b/Sources/MongoSwift/MongoCollection+BulkWrite.swift @@ -102,7 +102,7 @@ public enum WriteModel { mongoc_bulk_operation_insert_with_opts(bulk, docPtr, nil, &error) } - guard let insertedID = try document.getValue(for: "_id") else { + guard let insertedID = try getValue(from: document, for: "_id") else { // we called `withID()`, so this should be present. fatalError("Failed to get value for _id from document") } @@ -408,22 +408,22 @@ 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 getValue(from: reply, for: MongocKeys.deletedCount.rawValue)?.toInt() ?? 0 + self.insertedCount = try getValue(from: reply, 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 getValue(from: reply, for: MongocKeys.matchedCount.rawValue)?.toInt() ?? 0 + self.modifiedCount = try getValue(from: reply, for: MongocKeys.modifiedCount.rawValue)?.toInt() ?? 0 + self.upsertedCount = try getValue(from: reply, 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 getValue(from: reply, 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 getValue(from: upsert, 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..58014c271 100644 --- a/Sources/MongoSwift/Operations/DistinctOperation.swift +++ b/Sources/MongoSwift/Operations/DistinctOperation.swift @@ -65,7 +65,7 @@ internal struct DistinctOperation: Operation { } } - guard let values = try reply.getValue(for: "values")?.arrayValue else { + guard let values = try getValue(from: reply, 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..22e907f05 100644 --- a/Sources/MongoSwift/Operations/FindAndModifyOperation.swift +++ b/Sources/MongoSwift/Operations/FindAndModifyOperation.swift @@ -74,9 +74,9 @@ 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 setValue(in: &extra, 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 setValue(in: &extra, 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,12 +85,12 @@ 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 setValue(in: &extra, for: "maxTimeMS", to: .int64(Int64(maxTime))) } if let wc = writeConcern { do { - try extra.setValue(for: "writeConcern", to: .document(try BSONEncoder().encode(wc))) + try setValue(in: &extra, for: "writeConcern", to: .document(try BSONEncoder().encode(wc))) } catch { throw MongoError.InternalError(message: "Error encoding WriteConcern \(wc): \(error)") } @@ -185,7 +185,7 @@ internal struct FindAndModifyOperation: Operation { throw extractMongoError(error: error, reply: reply) } - guard let value = try reply.getValue(for: "value")?.documentValue else { + guard let value = try getValue(from: reply, for: "value")?.documentValue else { return nil } From f23fd7a67e6745f55baaaefb8c71eb1c39495ab5 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 29 May 2020 15:20:08 -0400 Subject: [PATCH 02/22] fix: re-export new errors --- Sources/MongoSwiftSync/Exports.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/MongoSwiftSync/Exports.swift b/Sources/MongoSwiftSync/Exports.swift index ee3498ac2..e61fce7d8 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,9 @@ @_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.BSONRuntimeError +@_exported import protocol MongoSwift.BSONUserError @_exported import protocol MongoSwift.CodingStrategyProvider @_exported import protocol MongoSwift.CommandEventHandler @_exported import protocol MongoSwift.MongoErrorProtocol From 3a5b4d7a988642a99aad19734d1ac60fbff67587 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 29 May 2020 16:06:46 -0400 Subject: [PATCH 03/22] fix: tests --- Tests/BSONTests/BSONValueTests.swift | 4 ++-- Tests/BSONTests/DocumentTests.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) 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)) } } } From cdb0c7ac1855b4d6a06e9e3259aa2431ed4abd04 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 29 May 2020 16:47:01 -0400 Subject: [PATCH 04/22] fix: docs --- Sources/MongoSwift/BSON/BSONDocument.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONDocument.swift b/Sources/MongoSwift/BSON/BSONDocument.swift index 47101d126..a2a5909a3 100644 --- a/Sources/MongoSwift/BSON/BSONDocument.swift +++ b/Sources/MongoSwift/BSON/BSONDocument.swift @@ -145,9 +145,6 @@ extension BSONDocument { * presence first. * * - Throws: - * - `BSONError.InternalError` if the new value is an `Int` and cannot be written to BSON. - * - `BSONError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted. - * - `BSONError.LogicError` if the new value is an `Array` and it contains a non-`BSONValue` element. * - `BSONError.InternalError` if the underlying `bson_t` would exceed the maximum size by encoding this * key-value pair. */ From 21c36521da2e2267f75a6f4a908347156fcc282f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 29 May 2020 18:26:10 -0400 Subject: [PATCH 05/22] fix: docs --- .../MongoSwift/BSON/BSONDocumentIterator.swift | 10 ++-------- Sources/MongoSwift/BSON/BSONValue.swift | 3 +-- Sources/MongoSwift/BSON/Overwritable.swift | 15 ++++----------- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONDocumentIterator.swift b/Sources/MongoSwift/BSON/BSONDocumentIterator.swift index 06ea105d5..e3d0fe403 100644 --- a/Sources/MongoSwift/BSON/BSONDocumentIterator.swift +++ b/Sources/MongoSwift/BSON/BSONDocumentIterator.swift @@ -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: - * - `BSONError.InternalError` if the new value is an `Int` and cannot be written to BSON. - * - `BSONError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted. - */ + /// Overwrites the current value of this `BSONDocumentIterator` with the supplied value. internal func overwriteCurrentValue(with newValue: Overwritable) throws { 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/BSONValue.swift b/Sources/MongoSwift/BSON/BSONValue.swift index ad280e428..15641a9dc 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: - * - `BSONError.InternalError` if the `DocumentStorage` would exceed the maximum size by encoding this + * - `BSONError.InternalError` if the `BSONDocument` would exceed the maximum size by encoding this * key-value pair. - * - `BSONError.LogicError` if the value is an `Array` and it contains a non-`BSONValue` element. */ func encode(to document: inout BSONDocument, forKey key: String) throws diff --git a/Sources/MongoSwift/BSON/Overwritable.swift b/Sources/MongoSwift/BSON/Overwritable.swift index a65d05325..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: - * - `BSONError.InternalError` if the `BSONValue` is an `Int` and cannot be written to BSON. - * - `BSONError.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) } } From 7a26ccd1d2f62115ca59df7f7a74aca1bfe98db9 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 29 May 2020 18:52:09 -0400 Subject: [PATCH 06/22] wip --- Sources/MongoSwift/BSON/BSONUtil.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONUtil.swift b/Sources/MongoSwift/BSON/BSONUtil.swift index b80357114..49d05219c 100644 --- a/Sources/MongoSwift/BSON/BSONUtil.swift +++ b/Sources/MongoSwift/BSON/BSONUtil.swift @@ -2,7 +2,9 @@ internal func getValue(from document: BSONDocument, for key: String) throws -> B do { return try document.getValue(for: key) } catch let error as BSONError.InvalidArgumentError { - throw BSONError.InvalidArgumentError(message: error.message) + throw MongoError.InvalidArgumentError(message: error.message) + } catch let error as BSONError.InternalError { + throw MongoError.InternalError(message: error.message) } } @@ -10,6 +12,8 @@ internal func setValue(in document: inout BSONDocument, for key: String, to valu do { try document.setValue(for: key, to: value) } catch let error as BSONError.InvalidArgumentError { - throw BSONError.InvalidArgumentError(message: error.message) + throw MongoError.InvalidArgumentError(message: error.message) + } catch let error as BSONError.InternalError { + throw MongoError.InternalError(message: error.message) } } From 6827c0f2d74aefc3ae2fab335fce7b2bb3fc5078 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 29 May 2020 19:04:42 -0400 Subject: [PATCH 07/22] wip --- Sources/MongoSwift/BSON/BSONDocument.swift | 2 +- .../MongoSwift/BSON/BSONDocumentIterator.swift | 2 +- Sources/MongoSwift/BSON/BSONUtil.swift | 16 ++++++++++++++++ .../MongoSwift/MongoCollection+BulkWrite.swift | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONDocument.swift b/Sources/MongoSwift/BSON/BSONDocument.swift index a2a5909a3..9d2f59174 100644 --- a/Sources/MongoSwift/BSON/BSONDocument.swift +++ b/Sources/MongoSwift/BSON/BSONDocument.swift @@ -166,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 { diff --git a/Sources/MongoSwift/BSON/BSONDocumentIterator.swift b/Sources/MongoSwift/BSON/BSONDocumentIterator.swift index e3d0fe403..7201337d0 100644 --- a/Sources/MongoSwift/BSON/BSONDocumentIterator.swift +++ b/Sources/MongoSwift/BSON/BSONDocumentIterator.swift @@ -174,7 +174,7 @@ public class BSONDocumentIterator: IteratorProtocol { } /// Overwrites the current value of this `BSONDocumentIterator` with the supplied value. - internal func overwriteCurrentValue(with newValue: Overwritable) throws { + 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)") diff --git a/Sources/MongoSwift/BSON/BSONUtil.swift b/Sources/MongoSwift/BSON/BSONUtil.swift index 49d05219c..a010a154a 100644 --- a/Sources/MongoSwift/BSON/BSONUtil.swift +++ b/Sources/MongoSwift/BSON/BSONUtil.swift @@ -17,3 +17,19 @@ internal func setValue(in document: inout BSONDocument, for key: String, to valu throw MongoError.InternalError(message: error.message) } } + +/// If the document already has an _id, returns it as-is. Otherwise, returns a new document +/// containing all the keys from this document, with an _id prepended. +internal func withID(document: BSONDocument) throws -> BSONDocument { + if document.hasKey("_id") { + return document + } + + var idDoc: BSONDocument = ["_id": .objectID()] + do { + try idDoc.merge(document) + return idDoc + } catch let error as BSONError.InternalError { + throw MongoError.InternalError(message: error.message) + } +} diff --git a/Sources/MongoSwift/MongoCollection+BulkWrite.swift b/Sources/MongoSwift/MongoCollection+BulkWrite.swift index 187301b4f..1d6e989ac 100644 --- a/Sources/MongoSwift/MongoCollection+BulkWrite.swift +++ b/Sources/MongoSwift/MongoCollection+BulkWrite.swift @@ -97,7 +97,7 @@ public enum WriteModel { } case let .insertOne(value): - let document = try encoder.encode(value).withID() + let document = try withID(document: encoder.encode(value)) success = document.withBSONPointer { docPtr in mongoc_bulk_operation_insert_with_opts(bulk, docPtr, nil, &error) } From 50c4500dcedd2ead45b69c3c1ad8855e3eb2c33d Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 1 Jun 2020 12:06:22 -0400 Subject: [PATCH 08/22] wip --- Sources/MongoSwift/BSON/BSONEncoder.swift | 21 +++++--- Sources/MongoSwift/BSON/BSONError.swift | 65 +++++++++++++++++++++++ Sources/MongoSwift/BSON/BSONValue.swift | 65 ----------------------- 3 files changed, 80 insertions(+), 71 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONEncoder.swift b/Sources/MongoSwift/BSON/BSONEncoder.swift index a4b7c9599..7381b0cba 100644 --- a/Sources/MongoSwift/BSON/BSONEncoder.swift +++ b/Sources/MongoSwift/BSON/BSONEncoder.swift @@ -149,7 +149,7 @@ public class BSONEncoder { let encoder = _BSONEncoder(options: self.options) - guard let boxedValue = try encoder.box_(value) else { + guard let boxedValue = try? encoder.box_(value) else { throw EncodingError.invalidValue( value, EncodingError.Context( @@ -169,7 +169,17 @@ public class BSONEncoder { ) } - return dict.toDocument() + guard let doc = try? dict.toDocument() else { + throw EncodingError.invalidValue( + value, + EncodingError.Context( + codingPath: [], + debugDescription: "Failed to construct BSON Document" + ) + ) + } + + return doc } /** @@ -774,8 +784,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 { .document((try? self.toDocument()) ?? [:]) } // rather than using a dictionary, do this so we preserve key orders fileprivate var keys = [String]() @@ -803,10 +812,10 @@ 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..(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/BSONValue.swift b/Sources/MongoSwift/BSON/BSONValue.swift index 15641a9dc..3cc1ca7ae 100644 --- a/Sources/MongoSwift/BSON/BSONValue.swift +++ b/Sources/MongoSwift/BSON/BSONValue.swift @@ -1233,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 From 94d61165e699df89f79c13cc44d6a259f5d845bf Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 1 Jun 2020 15:27:37 -0400 Subject: [PATCH 09/22] wip --- Sources/MongoSwift/BSON/BSONEncoder.swift | 68 ++++++++++++------- Sources/MongoSwift/BSON/BSONUtil.swift | 36 +++------- .../MongoCollection+BulkWrite.swift | 32 ++++++--- .../Operations/DistinctOperation.swift | 4 +- .../Operations/FindAndModifyOperation.swift | 16 +++-- 5 files changed, 91 insertions(+), 65 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONEncoder.swift b/Sources/MongoSwift/BSON/BSONEncoder.swift index 7381b0cba..d10d04ffe 100644 --- a/Sources/MongoSwift/BSON/BSONEncoder.swift +++ b/Sources/MongoSwift/BSON/BSONEncoder.swift @@ -149,37 +149,38 @@ 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 { + let optionalBoxedValue = try encoder.box_(value) + guard let boxedValue = optionalBoxedValue 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 { + throw EncodingError.invalidValue( + value, + EncodingError.Context( + codingPath: [], + debugDescription: "Top-level \(T.self) was not encoded as a complete document." + ) ) - ) - } + } - guard let doc = try? dict.toDocument() else { + return try dict.toDocument() + } catch let error as LocalizedError { throw EncodingError.invalidValue( value, EncodingError.Context( codingPath: [], - debugDescription: "Failed to construct BSON Document" + debugDescription: error.errorDescription ?? "Error encoding BSON" ) ) } - - return doc } /** @@ -743,7 +744,16 @@ 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 { + .array(self.array.map { + if let item = $0 as? MutableDictionary { + do { return try item.toDocument().bson } catch { + fatalError("Cannot convert to BSONDocument") + } + } + return $0.bson + }) + } fileprivate var array = [BSONValue]() @@ -784,7 +794,13 @@ private class MutableArray: BSONValue { private class MutableDictionary: BSONValue { fileprivate static var bsonType: BSONType { .document } - fileprivate var bson: BSON { .document((try? self.toDocument()) ?? [:]) } + fileprivate var bson: BSON { + do { + return .document(try self.toDocument()) + } catch { + fatalError("Cannot convert MutableDictionary to BSONDocument") + } + } // rather than using a dictionary, do this so we preserve key orders fileprivate var keys = [String]() @@ -815,7 +831,13 @@ private class MutableDictionary: BSONValue { fileprivate func toDocument() throws -> BSONDocument { var doc = BSONDocument() for i in 0.. BSON? { - do { - return try document.getValue(for: key) - } catch let error as BSONError.InvalidArgumentError { - throw MongoError.InvalidArgumentError(message: error.message) - } catch let error as BSONError.InternalError { - throw MongoError.InternalError(message: error.message) - } -} +/* + * BSONUtil contains helpers to wrap the underlying BSON library to assist in providing a consistent API + */ -internal func setValue(in document: inout BSONDocument, for key: String, to value: BSON) throws { +/// We don't want driver users to handle any BSONErrors +/// this will convert BSONError.* thrown from `fn` to MongoError.* and rethrow +internal func convertingBSONErrors(_ fn: () throws -> T) rethrows -> T { do { - try document.setValue(for: key, to: value) + return try fn() } catch let error as BSONError.InvalidArgumentError { throw MongoError.InvalidArgumentError(message: error.message) } catch let error as BSONError.InternalError { throw MongoError.InternalError(message: error.message) - } -} - -/// If the document already has an _id, returns it as-is. Otherwise, returns a new document -/// containing all the keys from this document, with an _id prepended. -internal func withID(document: BSONDocument) throws -> BSONDocument { - if document.hasKey("_id") { - return document - } - - var idDoc: BSONDocument = ["_id": .objectID()] - do { - try idDoc.merge(document) - return idDoc - } catch let error as BSONError.InternalError { - throw MongoError.InternalError(message: error.message) + } catch let error as BSONError.LogicError { + throw MongoError.LogicError(message: error.message) } } diff --git a/Sources/MongoSwift/MongoCollection+BulkWrite.swift b/Sources/MongoSwift/MongoCollection+BulkWrite.swift index 1d6e989ac..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 withID(document: encoder.encode(value)) + 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 getValue(from: document, 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 getValue(from: reply, for: MongocKeys.deletedCount.rawValue)?.toInt() ?? 0 - self.insertedCount = try getValue(from: reply, 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 getValue(from: reply, for: MongocKeys.matchedCount.rawValue)?.toInt() ?? 0 - self.modifiedCount = try getValue(from: reply, for: MongocKeys.modifiedCount.rawValue)?.toInt() ?? 0 - self.upsertedCount = try getValue(from: reply, 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 getValue(from: reply, 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 getValue(from: upsert, 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/Operations/DistinctOperation.swift b/Sources/MongoSwift/Operations/DistinctOperation.swift index 58014c271..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 getValue(from: reply, 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 22e907f05..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 setValue(in: &extra, 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 setValue(in: &extra, 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,12 +89,14 @@ internal class FindAndModifyOptions { guard maxTime > 0 else { throw MongoError.InvalidArgumentError(message: "maxTimeMS must be positive, but got value \(maxTime)") } - try setValue(in: &extra, for: "maxTimeMS", to: .int64(Int64(maxTime))) + try convertingBSONErrors { + try extra.setValue(for: "maxTimeMS", to: .int64(Int64(maxTime))) + } } if let wc = writeConcern { do { - try setValue(in: &extra, for: "writeConcern", to: .document(try BSONEncoder().encode(wc))) + try extra.setValue(for: "writeConcern", to: .document(try BSONEncoder().encode(wc))) } catch { throw MongoError.InternalError(message: "Error encoding WriteConcern \(wc): \(error)") } @@ -185,7 +191,7 @@ internal struct FindAndModifyOperation: Operation { throw extractMongoError(error: error, reply: reply) } - guard let value = try getValue(from: reply, for: "value")?.documentValue else { + guard let value = try reply.getValue(for: "value")?.documentValue else { return nil } From 0ce1e9356c2cf9fce25422d4799f72fbb4c66b83 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 1 Jun 2020 17:50:55 -0400 Subject: [PATCH 10/22] wip --- Sources/MongoSwift/BSON/BSONEncoder.swift | 53 ++++++++++++----------- Sources/MongoSwift/BSON/BSONUtil.swift | 4 +- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONEncoder.swift b/Sources/MongoSwift/BSON/BSONEncoder.swift index d10d04ffe..b79f49cb0 100644 --- a/Sources/MongoSwift/BSON/BSONEncoder.swift +++ b/Sources/MongoSwift/BSON/BSONEncoder.swift @@ -150,8 +150,7 @@ public class BSONEncoder { let encoder = _BSONEncoder(options: self.options) do { - let optionalBoxedValue = try encoder.box_(value) - guard let boxedValue = optionalBoxedValue else { + guard let boxedValue = try encoder.box_(value) else { throw EncodingError.invalidValue( value, EncodingError.Context( @@ -503,7 +502,12 @@ 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 { + if let array = $0 as? MutableArray { + return try array.toBSONArray().bson + } + return $0.bson + } } // The value should request a container from the _BSONEncoder. @@ -744,16 +748,7 @@ extension _BSONEncoder: SingleValueEncodingContainer { private class MutableArray: BSONValue { fileprivate static var bsonType: BSONType { .array } - fileprivate var bson: BSON { - .array(self.array.map { - if let item = $0 as? MutableDictionary { - do { return try item.toDocument().bson } catch { - fatalError("Cannot convert to BSONDocument") - } - } - return $0.bson - }) - } + fileprivate var bson: BSON { fatalError("MutableArray: BSONValue.bson should be unused") } fileprivate var array = [BSONValue]() @@ -786,6 +781,15 @@ 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] { + return try self.array.map { + if let item = $0 as? MutableDictionary { + return try item.toDocument().bson + } + return $0.bson + } + } } /// A private class wrapping a Swift dictionary so we can pass it by reference @@ -794,13 +798,7 @@ private class MutableArray: BSONValue { private class MutableDictionary: BSONValue { fileprivate static var bsonType: BSONType { .document } - fileprivate var bson: BSON { - do { - return .document(try self.toDocument()) - } catch { - fatalError("Cannot convert MutableDictionary to BSONDocument") - } - } + 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]() @@ -831,12 +829,15 @@ private class MutableDictionary: BSONValue { fileprivate func toDocument() throws -> BSONDocument { var doc = BSONDocument() for i in 0..(_ fn: () throws -> T) rethrows -> T { +internal func convertingBSONErrors(_ body: () throws -> T) rethrows -> T { do { - return try fn() + return try body() } catch let error as BSONError.InvalidArgumentError { throw MongoError.InvalidArgumentError(message: error.message) } catch let error as BSONError.InternalError { From 1a38c8f5cf5a012ca7e496cf49592d00501fe4e9 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 1 Jun 2020 18:14:02 -0400 Subject: [PATCH 11/22] wip --- Sources/MongoSwift/BSON/BSONEncoder.swift | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONEncoder.swift b/Sources/MongoSwift/BSON/BSONEncoder.swift index b79f49cb0..812c0dfdd 100644 --- a/Sources/MongoSwift/BSON/BSONEncoder.swift +++ b/Sources/MongoSwift/BSON/BSONEncoder.swift @@ -171,13 +171,21 @@ public class BSONEncoder { } return try dict.toDocument() - } catch let error as LocalizedError { + } catch let error as BSONErrorProtocol { + var debugDescription = "" + switch error.self { + case let error as BSONError.InvalidArgumentError: + debugDescription = error.message + case let error as BSONError.InternalError: + debugDescription = error.message + case let error as BSONError.LogicError: + debugDescription = error.message + default: + debugDescription = "Unknown Error occurred while encoding BSON" + } throw EncodingError.invalidValue( value, - EncodingError.Context( - codingPath: [], - debugDescription: error.errorDescription ?? "Error encoding BSON" - ) + EncodingError.Context(codingPath: [], debugDescription: debugDescription) ) } } @@ -502,12 +510,7 @@ extension _BSONEncoder { if let bsonValue = value as? BSONValue { return bsonValue } else if let bsonArray = value as? [BSONValue] { - return try bsonArray.map { - if let array = $0 as? MutableArray { - return try array.toBSONArray().bson - } - return $0.bson - } + return try bsonArray.map { $0.bson } } // The value should request a container from the _BSONEncoder. @@ -787,6 +790,9 @@ private class MutableArray: BSONValue { 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 } } From 390d05dc7669f74f4747ca9bcfced2aa00d9803e Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 1 Jun 2020 18:14:52 -0400 Subject: [PATCH 12/22] formatting --- Sources/MongoSwift/BSON/BSONEncoder.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONEncoder.swift b/Sources/MongoSwift/BSON/BSONEncoder.swift index 812c0dfdd..d3f1e3f7e 100644 --- a/Sources/MongoSwift/BSON/BSONEncoder.swift +++ b/Sources/MongoSwift/BSON/BSONEncoder.swift @@ -786,7 +786,7 @@ private class MutableArray: BSONValue { } internal func toBSONArray() throws -> [BSON] { - return try self.array.map { + try self.array.map { if let item = $0 as? MutableDictionary { return try item.toDocument().bson } @@ -837,13 +837,13 @@ private class MutableDictionary: BSONValue { for i in 0.. Date: Tue, 2 Jun 2020 10:21:04 -0400 Subject: [PATCH 13/22] wip --- Guides/Error-Handling.md | 29 +++++++++++++++++++++-- Sources/MongoSwift/BSON/BSONEncoder.swift | 16 ++++--------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Guides/Error-Handling.md b/Guides/Error-Handling.md index d57979a99..f24c3da57 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,14 @@ 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. ## Examples ### Handling any error thrown by the driver @@ -154,6 +161,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/BSONEncoder.swift b/Sources/MongoSwift/BSON/BSONEncoder.swift index d3f1e3f7e..9bbaf640a 100644 --- a/Sources/MongoSwift/BSON/BSONEncoder.swift +++ b/Sources/MongoSwift/BSON/BSONEncoder.swift @@ -172,20 +172,12 @@ public class BSONEncoder { return try dict.toDocument() } catch let error as BSONErrorProtocol { - var debugDescription = "" - switch error.self { - case let error as BSONError.InvalidArgumentError: - debugDescription = error.message - case let error as BSONError.InternalError: - debugDescription = error.message - case let error as BSONError.LogicError: - debugDescription = error.message - default: - debugDescription = "Unknown Error occurred while encoding BSON" - } throw EncodingError.invalidValue( value, - EncodingError.Context(codingPath: [], debugDescription: debugDescription) + EncodingError.Context( + codingPath: [], + debugDescription: error.errorDescription ?? "Unknown Error occurred while encoding BSON" + ) ) } } From caced54878ad75d1e474f2f1345eb4a56b812237 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 10:31:25 -0400 Subject: [PATCH 14/22] wip --- Sources/MongoSwift/BSON/BSONDecoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MongoSwift/BSON/BSONDecoder.swift b/Sources/MongoSwift/BSON/BSONDecoder.swift index f5c1c06f9..dc202fb96 100644 --- a/Sources/MongoSwift/BSON/BSONDecoder.swift +++ b/Sources/MongoSwift/BSON/BSONDecoder.swift @@ -532,7 +532,7 @@ private struct _BSONKeyedDecodingContainer: KeyedDecodingContainer /// Private helper function to check for a value in self.container. Returns the value stored /// under `key`, or throws an error if the value is not found. private func getValue(forKey key: Key) throws -> BSON { - guard let entry = try self.container.getValue(for: key.stringValue) else { + guard let entry = (try convertingBSONErrors { try self.container.getValue(for: key.stringValue) }) else { throw DecodingError.keyNotFound( key, DecodingError.Context( From 1725e73c60992a5ecda37e0de64be2c164d32842 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 11:12:04 -0400 Subject: [PATCH 15/22] wip --- Sources/MongoSwift/BSON/BSONDecoder.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONDecoder.swift b/Sources/MongoSwift/BSON/BSONDecoder.swift index dc202fb96..c9fa9d5fc 100644 --- a/Sources/MongoSwift/BSON/BSONDecoder.swift +++ b/Sources/MongoSwift/BSON/BSONDecoder.swift @@ -142,7 +142,16 @@ 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 { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: [], + debugDescription: "Unable to decode BSON \(error.errorDescription ?? "")" + ) + ) + } } /** @@ -532,7 +541,7 @@ private struct _BSONKeyedDecodingContainer: KeyedDecodingContainer /// Private helper function to check for a value in self.container. Returns the value stored /// under `key`, or throws an error if the value is not found. private func getValue(forKey key: Key) throws -> BSON { - guard let entry = (try convertingBSONErrors { try self.container.getValue(for: key.stringValue) }) else { + guard let entry = try self.container.getValue(for: key.stringValue) else { throw DecodingError.keyNotFound( key, DecodingError.Context( From 277a1d1323e05ee78bf39a32e579ee0442d12d15 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 11:28:27 -0400 Subject: [PATCH 16/22] wip --- Sources/MongoSwift/BSON/BSONDecoder.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/MongoSwift/BSON/BSONDecoder.swift b/Sources/MongoSwift/BSON/BSONDecoder.swift index c9fa9d5fc..b5188d9f9 100644 --- a/Sources/MongoSwift/BSON/BSONDecoder.swift +++ b/Sources/MongoSwift/BSON/BSONDecoder.swift @@ -145,10 +145,11 @@ public class BSONDecoder { 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 ?? "")" + debugDescription: "Unable to decode BSON: \(error.errorDescription ?? unknownErrorMessage)" ) ) } From 702f30b0491b1f64ff56f46ab3a83e3bd1d4d372 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 11:30:19 -0400 Subject: [PATCH 17/22] formatting --- Sources/MongoSwift/BSON/BSONDecoder.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONDecoder.swift b/Sources/MongoSwift/BSON/BSONDecoder.swift index b5188d9f9..07d94a17c 100644 --- a/Sources/MongoSwift/BSON/BSONDecoder.swift +++ b/Sources/MongoSwift/BSON/BSONDecoder.swift @@ -148,8 +148,8 @@ public class BSONDecoder { let unknownErrorMessage = "Unknown Error occurred while decoding BSON" throw DecodingError.dataCorrupted( DecodingError.Context( - codingPath: [], - debugDescription: "Unable to decode BSON: \(error.errorDescription ?? unknownErrorMessage)" + codingPath: [], + debugDescription: "Unable to decode BSON: \(error.errorDescription ?? unknownErrorMessage)" ) ) } From 30fe38686000a37f7c18eb13dd1b711162564578 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 15:11:38 -0400 Subject: [PATCH 18/22] wip --- Sources/MongoSwift/BSON/BSONDocument.swift | 2 +- Sources/MongoSwift/BSON/BSONError.swift | 29 ++++++++-------- Sources/MongoSwift/BSON/BSONUtil.swift | 2 ++ Sources/MongoSwift/BSON/BSONValue.swift | 40 +++++++++++----------- Sources/MongoSwiftSync/Exports.swift | 2 -- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONDocument.swift b/Sources/MongoSwift/BSON/BSONDocument.swift index 9d2f59174..edb1774cf 100644 --- a/Sources/MongoSwift/BSON/BSONDocument.swift +++ b/Sources/MongoSwift/BSON/BSONDocument.swift @@ -495,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.TooLargeError(value: self, forKey: key) } } } diff --git a/Sources/MongoSwift/BSON/BSONError.swift b/Sources/MongoSwift/BSON/BSONError.swift index b37ec32a0..20724c80a 100644 --- a/Sources/MongoSwift/BSON/BSONError.swift +++ b/Sources/MongoSwift/BSON/BSONError.swift @@ -3,16 +3,10 @@ import Foundation /// An empty protocol for encapsulating all errors that BSON package can throw. public protocol BSONErrorProtocol: LocalizedError {} -/// A protocol describing errors caused by improper usage of the BSON library by the user. -public protocol BSONUserError: BSONErrorProtocol {} - -/// The possible errors that can occur unexpectedly BSON library-side. -public protocol BSONRuntimeError: BSONErrorProtocol {} - /// Namespace containing all the error types introduced by this BSON library and their dependent types. public enum BSONError { /// An error thrown when the user passes in invalid arguments to a BSON method. - public struct InvalidArgumentError: BSONUserError { + public struct InvalidArgumentError: BSONErrorProtocol { internal let message: String public var errorDescription: String? { self.message } @@ -20,25 +14,30 @@ public enum BSONError { /// An error thrown when the BSON library encounters a internal error not caused by the user. /// This is usually indicative of a bug in the BSON library or system related failure. - public struct InternalError: BSONRuntimeError { + public struct InternalError: BSONErrorProtocol { internal let message: String public var errorDescription: String? { self.message } } /// An error thrown when the BSON library is incorrectly used. - public struct LogicError: BSONUserError { + public struct LogicError: BSONErrorProtocol { internal let message: String public var errorDescription: String? { self.message } } -} -internal func bsonTooLargeError(value: BSONValue, forKey: String) -> BSONErrorProtocol { - BSONError.InternalError( - message: - "Failed to set value for key \(forKey) to \(value) with BSON type \(value.bsonType): document too large" - ) + public struct TooLargeError: BSONErrorProtocol { + internal let message: String + + public var errorDescription: String? { self.message } + + internal init(value: BSONValue, forKey: String) { + self.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) -> BSONErrorProtocol { diff --git a/Sources/MongoSwift/BSON/BSONUtil.swift b/Sources/MongoSwift/BSON/BSONUtil.swift index 6e9642fb9..fe0053647 100644 --- a/Sources/MongoSwift/BSON/BSONUtil.swift +++ b/Sources/MongoSwift/BSON/BSONUtil.swift @@ -13,5 +13,7 @@ internal func convertingBSONErrors(_ body: () throws -> T) rethrows -> T { throw MongoError.InternalError(message: error.message) } catch let error as BSONError.LogicError { throw MongoError.LogicError(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 3cc1ca7ae..70cf9b2ed 100644 --- a/Sources/MongoSwift/BSON/BSONValue.swift +++ b/Sources/MongoSwift/BSON/BSONValue.swift @@ -133,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.TooLargeError(value: self, forKey: key) } } } @@ -179,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.TooLargeError(value: self, forKey: key) } } } @@ -318,7 +318,7 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { } 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.TooLargeError(value: self, forKey: key) } } } @@ -382,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.TooLargeError(value: self, forKey: key) } } } @@ -416,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.TooLargeError(value: self, forKey: key) } } } @@ -462,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.TooLargeError(value: self, forKey: key) } } } @@ -544,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.TooLargeError(value: self, forKey: key) } } } @@ -594,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.TooLargeError(value: self, forKey: key) } } } @@ -619,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.TooLargeError(value: self, forKey: key) } } } @@ -644,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.TooLargeError(value: self, forKey: key) } } } @@ -691,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.TooLargeError(value: self, forKey: key) } } } @@ -747,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.TooLargeError(value: self, forKey: key) } } } @@ -772,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.TooLargeError(value: self, forKey: key) } } } @@ -805,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.TooLargeError(value: self, forKey: key) } } } @@ -902,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.TooLargeError(value: self, forKey: key) } } } @@ -1004,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.TooLargeError(value: self, forKey: key) } } } @@ -1049,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.TooLargeError(value: self, forKey: key) } } } @@ -1117,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.TooLargeError(value: self, forKey: key) } } } @@ -1173,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.TooLargeError(value: self, forKey: key) } } } @@ -1213,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.TooLargeError(value: self, forKey: key) } } } diff --git a/Sources/MongoSwiftSync/Exports.swift b/Sources/MongoSwiftSync/Exports.swift index e61fce7d8..c51a86895 100644 --- a/Sources/MongoSwiftSync/Exports.swift +++ b/Sources/MongoSwiftSync/Exports.swift @@ -106,8 +106,6 @@ // Protocols are not included in the types list, so we list them separately here. @_exported import protocol MongoSwift.BSONErrorProtocol -@_exported import protocol MongoSwift.BSONRuntimeError -@_exported import protocol MongoSwift.BSONUserError @_exported import protocol MongoSwift.CodingStrategyProvider @_exported import protocol MongoSwift.CommandEventHandler @_exported import protocol MongoSwift.MongoErrorProtocol From b90767c5b11bdb756eb5b2fced08d97b19f35e9f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 15:16:03 -0400 Subject: [PATCH 19/22] wip --- Sources/MongoSwift/BSON/BSONUtil.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/MongoSwift/BSON/BSONUtil.swift b/Sources/MongoSwift/BSON/BSONUtil.swift index fe0053647..9731206e1 100644 --- a/Sources/MongoSwift/BSON/BSONUtil.swift +++ b/Sources/MongoSwift/BSON/BSONUtil.swift @@ -13,6 +13,8 @@ internal func convertingBSONErrors(_ body: () throws -> T) rethrows -> T { throw MongoError.InternalError(message: error.message) } catch let error as BSONError.LogicError { throw MongoError.LogicError(message: error.message) + } catch let error as BSONError.TooLargeError { + throw MongoError.InternalError(message: error.message) } catch let error as BSONErrorProtocol { throw MongoError.InternalError(message: error.errorDescription ?? "Uknown BSON Error") } From cd45de9df57a3a71ec5ffe1da39d3355b1c56abe Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 16:52:33 -0400 Subject: [PATCH 20/22] wip --- Sources/MongoSwift/BSON/BSONDocument.swift | 2 +- Sources/MongoSwift/BSON/BSONError.swift | 3 +- Sources/MongoSwift/BSON/BSONUtil.swift | 2 +- Sources/MongoSwift/BSON/BSONValue.swift | 40 +++++++++++----------- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Sources/MongoSwift/BSON/BSONDocument.swift b/Sources/MongoSwift/BSON/BSONDocument.swift index edb1774cf..79bd5431b 100644 --- a/Sources/MongoSwift/BSON/BSONDocument.swift +++ b/Sources/MongoSwift/BSON/BSONDocument.swift @@ -495,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } diff --git a/Sources/MongoSwift/BSON/BSONError.swift b/Sources/MongoSwift/BSON/BSONError.swift index 20724c80a..a5577a5d2 100644 --- a/Sources/MongoSwift/BSON/BSONError.swift +++ b/Sources/MongoSwift/BSON/BSONError.swift @@ -27,7 +27,8 @@ public enum BSONError { public var errorDescription: String? { self.message } } - public struct TooLargeError: BSONErrorProtocol { + /// An error thrown when a document exceeds the maximum BSON encoding size + public struct DocumentTooLargeError: BSONErrorProtocol { internal let message: String public var errorDescription: String? { self.message } diff --git a/Sources/MongoSwift/BSON/BSONUtil.swift b/Sources/MongoSwift/BSON/BSONUtil.swift index 9731206e1..1097adab2 100644 --- a/Sources/MongoSwift/BSON/BSONUtil.swift +++ b/Sources/MongoSwift/BSON/BSONUtil.swift @@ -13,7 +13,7 @@ internal func convertingBSONErrors(_ body: () throws -> T) rethrows -> T { throw MongoError.InternalError(message: error.message) } catch let error as BSONError.LogicError { throw MongoError.LogicError(message: error.message) - } catch let error as BSONError.TooLargeError { + } 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 70cf9b2ed..89915b9d8 100644 --- a/Sources/MongoSwift/BSON/BSONValue.swift +++ b/Sources/MongoSwift/BSON/BSONValue.swift @@ -133,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -179,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -318,7 +318,7 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable { } try document.withMutableBSONPointer { docPtr in guard bson_append_binary(docPtr, key, Int32(key.utf8.count), subtype, byteArray, UInt32(length)) else { - throw BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -382,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -416,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -462,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -544,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -594,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -619,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -644,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -691,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -747,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -772,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -805,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -902,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1004,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1049,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1117,7 +1117,7 @@ public struct BSONSymbol: BSONValue, CustomStringConvertible, Codable, Equatable self.stringValue, Int32(self.stringValue.utf8.count) ) else { - throw BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1173,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } @@ -1213,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 BSONError.TooLargeError(value: self, forKey: key) + throw BSONError.DocumentTooLargeError(value: self, forKey: key) } } } From 156ad6511d73779bc40e975fd0018e889d1d0052 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 2 Jun 2020 17:18:02 -0400 Subject: [PATCH 21/22] wip --- Guides/Error-Handling.md | 1 + Sources/MongoSwift/BSON/BSONDocument.swift | 2 +- Sources/MongoSwift/BSON/BSONError.swift | 2 +- Sources/MongoSwift/BSON/BSONValue.swift | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Guides/Error-Handling.md b/Guides/Error-Handling.md index f24c3da57..98de21924 100644 --- a/Guides/Error-Handling.md +++ b/Guides/Error-Handling.md @@ -84,6 +84,7 @@ BSON Errors can be found in [Sources/MongoSwift/BSON/BSONError.swift](Sources/Mo - `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 there is an unexpected usage of the the API. ## Examples ### Handling any error thrown by the driver diff --git a/Sources/MongoSwift/BSON/BSONDocument.swift b/Sources/MongoSwift/BSON/BSONDocument.swift index 79bd5431b..9b4d6adc4 100644 --- a/Sources/MongoSwift/BSON/BSONDocument.swift +++ b/Sources/MongoSwift/BSON/BSONDocument.swift @@ -145,7 +145,7 @@ extension BSONDocument { * presence first. * * - Throws: - * - `BSONError.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 { diff --git a/Sources/MongoSwift/BSON/BSONError.swift b/Sources/MongoSwift/BSON/BSONError.swift index a5577a5d2..bdc2c1645 100644 --- a/Sources/MongoSwift/BSON/BSONError.swift +++ b/Sources/MongoSwift/BSON/BSONError.swift @@ -27,7 +27,7 @@ public enum BSONError { public var errorDescription: String? { self.message } } - /// An error thrown when a document exceeds the maximum BSON encoding size + /// An error thrown when a document exceeds the maximum BSON encoding size. public struct DocumentTooLargeError: BSONErrorProtocol { internal let message: String diff --git a/Sources/MongoSwift/BSON/BSONValue.swift b/Sources/MongoSwift/BSON/BSONValue.swift index 89915b9d8..f5106a994 100644 --- a/Sources/MongoSwift/BSON/BSONValue.swift +++ b/Sources/MongoSwift/BSON/BSONValue.swift @@ -70,7 +70,7 @@ internal protocol BSONValue: Codable { * - key: A `String`, the key under which to store the value. * * - Throws: - * - `BSONError.InternalError` if the `BSONDocument` would exceed the maximum size by encoding this + * - `BSONError.DocumentTooLargeError` if the `BSONDocument` would exceed the maximum size by encoding this * key-value pair. */ func encode(to document: inout BSONDocument, forKey key: String) throws From 4b960ba27a499359efba2710073c4bb0032b9176 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 3 Jun 2020 11:31:20 -0400 Subject: [PATCH 22/22] wip --- Guides/Error-Handling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guides/Error-Handling.md b/Guides/Error-Handling.md index 98de21924..e824f9363 100644 --- a/Guides/Error-Handling.md +++ b/Guides/Error-Handling.md @@ -84,7 +84,7 @@ BSON Errors can be found in [Sources/MongoSwift/BSON/BSONError.swift](Sources/Mo - `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 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