Skip to content
Merged
35 changes: 16 additions & 19 deletions Sources/MongoSwift/BSON/BSONDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal typealias MutableBSONPointer = UnsafeMutablePointer<bson_t>
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 {
Expand Down Expand Up @@ -145,10 +145,7 @@ extension BSONDocument {
* presence first.
*
* - Throws:
* - `MongoError.InternalError` if the new value is an `Int` and cannot be written to BSON.
* - `MongoError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted.
* - `MongoError.LogicError` if the new value is an `Array` and it contains a non-`BSONValue` element.
* - `MongoError.InternalError` if the underlying `bson_t` would exceed the maximum size by encoding this
* - `BSONError.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 {
Expand All @@ -169,7 +166,7 @@ extension BSONDocument {
self.copyStorageIfRequired()
// key is guaranteed present so initialization will succeed.
// swiftlint:disable:next force_unwrapping
try BSONDocumentIterator(over: self, advancedTo: key)!.overwriteCurrentValue(with: ov)
BSONDocumentIterator(over: self, advancedTo: key)!.overwriteCurrentValue(with: ov)

// otherwise, we just create a new document and replace this key
} else {
Expand Down Expand Up @@ -210,7 +207,7 @@ extension BSONDocument {
/// Retrieves the value associated with `for` as a `BSON?`, which can be nil if the key does not exist in the
/// `BSONDocument`.
///
/// - Throws: `MongoError.InternalError` if the BSON buffer is too small (< 5 bytes).
/// - Throws: `BSONError.InternalError` if the BSON buffer is too small (< 5 bytes).
internal func getValue(for key: String) throws -> BSON? {
guard let iter = BSONDocumentIterator(over: self) else {
throw BSONDocument.BSONBufferTooSmallError
Expand All @@ -232,7 +229,7 @@ extension BSONDocument {
}
}
guard success else {
throw MongoError.InternalError(
throw BSONError.InternalError(
message: "Failed to merge \(doc) with \(self). This is likely due to " +
"the merged document being too large."
)
Expand All @@ -255,15 +252,15 @@ 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<CChar>] = keys.map { $0.utf8CString }

var cPtrs: [UnsafePointer<CChar>] = try cStrings.map { cString in
let bufferPtr: UnsafeBufferPointer<CChar> = 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
}
Expand Down Expand Up @@ -373,16 +370,16 @@ extension BSONDocument {
* - Returns: the parsed `BSONDocument`
*
* - Throws:
* - A `MongoError.InvalidArgumentError` if the data passed in is invalid JSON.
* - A `BSONError.InvalidArgumentError` if the data passed in is invalid JSON.
*/
public init(fromJSON: Data) throws {
self._storage = Storage(stealing: try fromJSON.withUnsafeBytePointer { bytes in
var error = bson_error_t()
guard let bson = bson_new_from_json(bytes, fromJSON.count, &error) else {
if error.domain == BSON_ERROR_JSON {
throw MongoError.InvalidArgumentError(message: "Invalid JSON: \(toErrorString(error))")
throw BSONError.InvalidArgumentError(message: "Invalid JSON: \(toErrorString(error))")
}
throw MongoError.InternalError(message: toErrorString(error))
throw BSONError.InternalError(message: toErrorString(error))
}

return bson
Expand All @@ -391,7 +388,7 @@ extension BSONDocument {

/// Convenience initializer for constructing a `BSONDocument` from a `String`.
/// - Throws:
/// - A `MongoError.InvalidArgumentError` if the string passed in is invalid JSON.
/// - A `BSONError.InvalidArgumentError` if the string passed in is invalid JSON.
public init(fromJSON json: String) throws {
// `String`s are Unicode under the hood so force unwrap always succeeds.
// see https://www.objc.io/blog/2018/02/13/string-to-data-and-back/
Expand All @@ -401,15 +398,15 @@ extension BSONDocument {
/**
* Constructs a `BSONDocument` from raw BSON `Data`.
* - Throws:
* - A `MongoError.InvalidArgumentError` if `bson` is too short or too long to be valid BSON.
* - A `MongoError.InvalidArgumentError` if the first four bytes of `bson` do not contain `bson.count`.
* - A `MongoError.InvalidArgumentError` if the final byte of `bson` is not a null byte.
* - A `BSONError.InvalidArgumentError` if `bson` is too short or too long to be valid BSON.
* - A `BSONError.InvalidArgumentError` if the first four bytes of `bson` do not contain `bson.count`.
* - A `BSONError.InvalidArgumentError` if the final byte of `bson` is not a null byte.
* - SeeAlso: http://bsonspec.org/
*/
public init(fromBSON bson: Data) throws {
self._storage = Storage(stealing: try bson.withUnsafeBytePointer { bytes in
guard let data = bson_new_from_data(bytes, bson.count) else {
throw MongoError.InvalidArgumentError(message: "Invalid BSON data")
throw BSONError.InvalidArgumentError(message: "Invalid BSON data")
}
return data
})
Expand Down Expand Up @@ -520,7 +517,7 @@ extension BSONDocument: BSONValue {
bson_iter_document(iterPtr, &length, document)

guard let docData = bson_new_from_data(document.pointee, Int(length)) else {
throw MongoError.InternalError(message: "Failed to create a Document from iterator")
throw BSONError.InternalError(message: "Failed to create a Document from iterator")
}

return self.init(stealing: docData)
Expand Down
16 changes: 5 additions & 11 deletions Sources/MongoSwift/BSON/BSONDocumentIterator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
)
}
Expand Down Expand Up @@ -173,19 +173,13 @@ public class BSONDocumentIterator: IteratorProtocol {
self.advance() ? (self.currentKey, self.currentValue) : nil
}

/**
* Overwrites the current value of this `BSONDocumentIterator` with the supplied value.
*
* - Throws:
* - `MongoError.InternalError` if the new value is an `Int` and cannot be written to BSON.
* - `MongoError.LogicError` if the new value is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted.
*/
internal func overwriteCurrentValue(with newValue: Overwritable) throws {
/// Overwrites the current value of this `BSONDocumentIterator` with the supplied value.
internal func overwriteCurrentValue(with newValue: Overwritable) {
let newValueType = type(of: newValue).bsonType
guard newValueType == self.currentType else {
fatalError("Expected \(newValue) to have BSON type \(self.currentType), but has type \(newValueType)")
}
try newValue.writeToCurrentPosition(of: self)
newValue.writeToCurrentPosition(of: self)
}

/// Internal helper function for explicitly accessing the `bson_iter_t` as an unsafe pointer
Expand Down
80 changes: 59 additions & 21 deletions Sources/MongoSwift/BSON/BSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,27 +149,45 @@ public class BSONEncoder {

let encoder = _BSONEncoder(options: self.options)

guard let boxedValue = try encoder.box_(value) else {
throw EncodingError.invalidValue(
value,
EncodingError.Context(
codingPath: [],
debugDescription: "Top-level \(T.self) did not encode any values."
do {
guard let boxedValue = try encoder.box_(value) else {
throw EncodingError.invalidValue(
value,
EncodingError.Context(
codingPath: [],
debugDescription: "Top-level \(T.self) did not encode any values."
)
)
)
}
}

guard let dict = boxedValue as? MutableDictionary else {
throw EncodingError.invalidValue(
value,
EncodingError.Context(
codingPath: [],
debugDescription: "Top-level \(T.self) was not encoded as a complete document."
)
)
}

guard let dict = boxedValue as? MutableDictionary else {
return try dict.toDocument()
} catch let error as BSONErrorProtocol {
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: "Top-level \(T.self) was not encoded as a complete document."
)
EncodingError.Context(codingPath: [], debugDescription: debugDescription)
)
}

return dict.toDocument()
}

/**
Expand Down Expand Up @@ -492,7 +510,7 @@ extension _BSONEncoder {
if let bsonValue = value as? BSONValue {
return bsonValue
} else if let bsonArray = value as? [BSONValue] {
return bsonArray.map { $0.bson }
return try bsonArray.map { $0.bson }
}

// The value should request a container from the _BSONEncoder.
Expand Down Expand Up @@ -733,7 +751,7 @@ extension _BSONEncoder: SingleValueEncodingContainer {
private class MutableArray: BSONValue {
fileprivate static var bsonType: BSONType { .array }

fileprivate var bson: BSON { .array(self.array.map { $0.bson }) }
fileprivate var bson: BSON { fatalError("MutableArray: BSONValue.bson should be unused") }

fileprivate var array = [BSONValue]()

Expand Down Expand Up @@ -766,6 +784,18 @@ private class MutableArray: BSONValue {
required convenience init(from _: Decoder) throws {
fatalError("`MutableArray` is not meant to be initialized from a `Decoder`")
}

internal func toBSONArray() throws -> [BSON] {
try self.array.map {
if let item = $0 as? MutableDictionary {
return try item.toDocument().bson
}
if let item = $0 as? MutableArray {
return try item.toBSONArray().bson
}
return $0.bson
}
}
}

/// A private class wrapping a Swift dictionary so we can pass it by reference
Expand All @@ -774,8 +804,7 @@ private class MutableArray: BSONValue {
private class MutableDictionary: BSONValue {
fileprivate static var bsonType: BSONType { .document }

// This is unused
fileprivate var bson: BSON { .document(self.toDocument()) }
fileprivate var bson: BSON { fatalError("MutableDictionary: BSONValue.bson should be unused") }

// rather than using a dictionary, do this so we preserve key orders
fileprivate var keys = [String]()
Expand Down Expand Up @@ -803,10 +832,19 @@ private class MutableDictionary: BSONValue {
}

/// Converts self to a `BSONDocument` with equivalent key-value pairs.
fileprivate func toDocument() -> BSONDocument {
fileprivate func toDocument() throws -> BSONDocument {
var doc = BSONDocument()
for i in 0..<self.keys.count {
doc[self.keys[i]] = self.values[i].bson
let value = self.values[i]
switch value {
case let val as MutableDictionary:
try doc.setValue(for: self.keys[i], to: val.toDocument().bson)
case let val as MutableArray:
let array = try val.toBSONArray()
try doc.setValue(for: self.keys[i], to: array.bson)
default:
try doc.setValue(for: self.keys[i], to: value.bson)
}
}
return doc
}
Expand Down
Loading