Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Sources/MongoSwift/BSON/BSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,11 @@ public enum BSON {
case let .decimal128(d):
return d
case let .int64(i):
return BSONDecimal128(String(i))
return try? BSONDecimal128(String(i))
case let .int32(i):
return BSONDecimal128(String(i))
return try? BSONDecimal128(String(i))
case let .double(d):
return BSONDecimal128(String(d))
return try? BSONDecimal128(String(d))
default:
return nil
}
Expand All @@ -306,7 +306,7 @@ extension BSON {
BSONNull.self,
BSONUndefined.self,
BSONMinKey.self,
MaxKey.self,
BSONMaxKey.self,
BSONSymbol.self,
Double.self,
String.self,
Expand Down Expand Up @@ -336,7 +336,7 @@ extension BSON {
case .minKey:
return BSONMinKey()
case .maxKey:
return MaxKey()
return BSONMaxKey()
case let .symbol(v):
return v
case let .double(v):
Expand Down
5 changes: 4 additions & 1 deletion Sources/MongoSwift/BSON/BSONDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,10 @@ extension _BSONDecoder {
return try Data(from: self)
case .binary:
let binary = try self.unboxCustom(value) { $0.binaryValue }
return binary.data
guard let data = binary.data.getBytes(at: 0, length: binary.data.writerIndex) else {
throw InternalError(message: "Cannot read \(binary.data.writerIndex) bytes from Binary.data")
}
return Data(data)
case .base64:
let base64Str = try self.unboxCustom(value) { $0.stringValue }

Expand Down
6 changes: 3 additions & 3 deletions Sources/MongoSwift/BSON/BSONDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,8 @@ extension BSONDocument {
return String(cString: json)
}

/// Returns a copy of the raw BSON data for this `BSONDocument`, represented as `Data`.
public var rawBSON: Data {
/// Returns a copy of the raw BSON data for this `Document`, represented as `Data`.
public func toData() -> Data {
let data = self.withBSONPointer { ptr in
// swiftlint:disable:next force_unwrapping
bson_get_data(ptr)! // documented as always returning a value.
Expand Down Expand Up @@ -448,7 +448,7 @@ extension BSONDocument {
self = newSelf
}
} catch {
fatalError("Failed to set the value for key \(key) to \(newValue ?? "nil"): \(error)")
fatalError("Failed to set the value for key \"\(key)\" to \(newValue ?? "nil"): \(error)")
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/MongoSwift/BSON/BSONDocumentIterator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public class BSONDocumentIterator: IteratorProtocol {
/// Returns the current value's type. Assumes the iterator is in a valid position.
internal var currentType: BSONType {
self.withBSONIterPointer { iterPtr in
BSONType(rawValue: bson_iter_type(iterPtr).rawValue) ?? .invalid
BSONType(rawValue: UInt8(bson_iter_type(iterPtr).rawValue)) ?? .invalid
}
}

Expand Down Expand Up @@ -219,7 +219,7 @@ public class BSONDocumentIterator: IteratorProtocol {
.int64: Int64.self,
.decimal128: BSONDecimal128.self,
.minKey: BSONMinKey.self,
.maxKey: MaxKey.self,
.maxKey: BSONMaxKey.self,
.null: BSONNull.self,
.undefined: BSONUndefined.self
]
Expand Down
162 changes: 96 additions & 66 deletions Sources/MongoSwift/BSON/BSONValue.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import CLibMongoC
import Foundation
import NIO

/// This shared allocator instance should be used for all underlying `ByteBuffer` creation.
private let BSON_ALLOCATOR = ByteBufferAllocator()

/// The possible types of BSON values and their corresponding integer values.
public enum BSONType: UInt32 {
public enum BSONType: UInt8 {
/// An invalid type
case invalid = 0x00
/// 64-bit binary floating point
Expand Down Expand Up @@ -196,27 +200,61 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable {
internal var bson: BSON { .binary(self) }

/// The binary data.
public let data: Data
public let data: ByteBuffer

/// The binary subtype for this data.
public let subtype: UInt8
public let subtype: Subtype

/// Subtypes for BSON Binary values.
public enum Subtype: UInt8 {
public struct Subtype: Equatable, Codable, Hashable, RawRepresentable {
// swiftlint:disable force_unwrapping
/// Generic binary subtype
case generic,
/// A function
function,
/// Binary (old)
binaryDeprecated,
/// UUID (old)
uuidDeprecated,
/// UUID (RFC 4122)
uuid,
/// MD5
md5,
/// User defined
userDefined = 0x80
public static let generic = Subtype(rawValue: 0x00)!
/// A function
public static let function = Subtype(rawValue: 0x01)!
/// Binary (old)
public static let binaryDeprecated = Subtype(rawValue: 0x02)!
/// UUID (old)
public static let uuidDeprecated = Subtype(rawValue: 0x03)!
/// UUID (RFC 4122)
public static let uuid = Subtype(rawValue: 0x04)!
/// MD5
public static let md5 = Subtype(rawValue: 0x05)!
/// Encrypted BSON value
public static let encryptedValue = Subtype(rawValue: 0x06)!
// swiftlint:enable force_unwrapping

/// Subtype indicator value
public let rawValue: UInt8

/// Initializes a `Subtype` with a custom value.
/// Returns nil if rawValue within reserved range [0x07, 0x80).
public init?(rawValue: UInt8) {
guard !(rawValue > 0x06 && rawValue < 0x80) else {
return nil
}
self.rawValue = rawValue
}

internal init(_ value: bson_subtype_t) { self.rawValue = UInt8(value.rawValue) }

/// Initializes a `Subtype` with a custom value. This value must be in the range 0x80-0xFF.
/// - Throws:
/// - `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 InvalidArgumentError(message: "Cannot represent \(value) as UInt8")
}
guard byteValue >= 0x80 else {
throw InvalidArgumentError(
message: "userDefined value must be greater than or equal to 0x80 got \(byteValue)"
)
}
guard let subtype = Subtype(rawValue: byteValue) else {
throw InvalidArgumentError(message: "Cannot represent \(byteValue) as Subtype")
}
return subtype
}
}

/// Initializes a `BSONBinary` instance from a `UUID`.
Expand All @@ -238,29 +276,24 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable {
/// Initializes a `BSONBinary` instance from a `Data` object and a `UInt8` subtype.
/// - Throws:
/// - `InvalidArgumentError` if the provided data is incompatible with the specified subtype.
public init(data: Data, subtype: UInt8) throws {
if [Subtype.uuid.rawValue, Subtype.uuidDeprecated.rawValue].contains(subtype) && data.count != 16 {
public init(data: Data, subtype: Subtype) throws {
if [Subtype.uuid, Subtype.uuidDeprecated].contains(subtype) && data.count != 16 {
throw InvalidArgumentError(
message:
"Binary data with UUID subtype must be 16 bytes, but data has \(data.count) bytes"
)
}
self.subtype = subtype
self.data = data
}

/// Initializes a `BSONBinary` instance from a `Data` object and a `Subtype`.
/// - Throws:
/// - `InvalidArgumentError` if the provided data is incompatible with the specified subtype.
public init(data: Data, subtype: Subtype) throws {
try self.init(data: data, subtype: subtype.rawValue)
var buffer = BSON_ALLOCATOR.buffer(capacity: data.count)
buffer.writeBytes(data)
self.data = buffer
}

/// Initializes a `BSONBinary` instance from a base64 `String` and a `UInt8` subtype.
/// Initializes a `BSONBinary` instance from a base64 `String` and a `Subtype`.
/// - Throws:
/// - `InvalidArgumentError` if the base64 `String` is invalid or if the provided data is
/// incompatible with the specified subtype.
public init(base64: String, subtype: UInt8) throws {
public init(base64: String, subtype: Subtype) throws {
guard let dataObj = Data(base64Encoded: base64) else {
throw InvalidArgumentError(
message:
Expand All @@ -270,14 +303,6 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable {
try self.init(data: dataObj, subtype: subtype)
}

/// Initializes a `BSONBinary` instance from a base64 `String` and a `Subtype`.
/// - Throws:
/// - `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 {
try self.init(base64: base64, subtype: subtype.rawValue)
}

public init(from decoder: Decoder) throws {
throw getDecodingError(type: BSONBinary.self, decoder: decoder)
}
Expand All @@ -287,9 +312,11 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable {
}

internal func encode(to document: inout BSONDocument, forKey key: String) throws {
let subtype = bson_subtype_t(UInt32(self.subtype))
let length = self.data.count
let byteArray = [UInt8](self.data)
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 InternalError(message: "Cannot read \(length) bytes from Binary.data")
}
try document.withMutableBSONPointer { docPtr in
guard bson_append_binary(docPtr, key, Int32(key.utf8.count), subtype, byteArray, UInt32(length)) else {
throw bsonTooLargeError(value: self, forKey: key)
Expand Down Expand Up @@ -318,21 +345,24 @@ public struct BSONBinary: BSONValue, Equatable, Codable, Hashable {
}

let dataObj = Data(bytes: data, count: Int(length))
return try self.init(data: dataObj, subtype: UInt8(subtype.rawValue))
return try self.init(data: dataObj, subtype: Subtype(subtype))
})
}

/// Converts this `BSONBinary` instance to a `UUID`.
/// - Throws:
/// - `InvalidArgumentError` if a non-UUID subtype is set on this `BSONBinary`.
public func toUUID() throws -> UUID {
guard [Subtype.uuid.rawValue, Subtype.uuidDeprecated.rawValue].contains(self.subtype) else {
guard [Subtype.uuid, Subtype.uuidDeprecated].contains(self.subtype) else {
throw InvalidArgumentError(
message: "Expected a UUID binary subtype, got subtype \(self.subtype) instead."
)
}

let data = self.data
guard let data = self.data.getBytes(at: 0, length: 16) else {
throw InternalError(message: "Unable to read 16 bytes from Binary.data")
}

let uuid: uuid_t = (
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7],
Expand Down Expand Up @@ -487,16 +517,20 @@ public struct BSONDecimal128: BSONValue, Equatable, Codable, CustomStringConvert
self.decimal128 = bsonDecimal
}

/// Initializes a `BSONDecimal128` value from the provided `String`. Returns `nil` if the input is not a valid
/// Decimal128 string.
/// - SeeAlso: https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst
public init?(_ data: String) {
do {
let bsonType = try BSONDecimal128.toLibBSONType(data)
self.init(bsonDecimal: bsonType)
} catch {
return nil
}
/**
* Initializes a `BSONDecimal128` value from the provided `String`.
*
* - Parameters:
* - a BSONDecimal128 number as a string.
*
* - Throws:
* - A `InvalidArgumentError` if the string does not represent a BSONDecimal128 encodable value.
*
* - SeeAlso: https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst
*/
public init(_ data: String) throws {
let bsonType = try BSONDecimal128.toLibBSONType(data)
self.init(bsonDecimal: bsonType)
}

public init(from decoder: Decoder) throws {
Expand Down Expand Up @@ -731,7 +765,7 @@ public struct BSONCode: BSONValue, Equatable, Codable, Hashable {
}

/// A struct to represent the BSON MaxKey type.
internal struct MaxKey: BSONValue, Equatable, Codable, Hashable {
internal struct BSONMaxKey: BSONValue, Equatable, Codable, Hashable {
internal var bson: BSON { .maxKey }

internal static var bsonType: BSONType { .maxKey }
Expand All @@ -748,7 +782,7 @@ internal struct MaxKey: BSONValue, Equatable, Codable, Hashable {
internal init() {}

internal init(from decoder: Decoder) throws {
throw getDecodingError(type: MaxKey.self, decoder: decoder)
throw getDecodingError(type: BSONMaxKey.self, decoder: decoder)
}

internal func encode(to: Encoder) throws {
Expand All @@ -757,7 +791,7 @@ internal struct MaxKey: BSONValue, Equatable, Codable, Hashable {

internal static func from(iterator iter: BSONDocumentIterator) throws -> BSON {
guard iter.currentType == .maxKey else {
throw wrongIterTypeError(iter, expected: MaxKey.self)
throw wrongIterTypeError(iter, expected: BSONMaxKey.self)
}
return .maxKey
}
Expand Down Expand Up @@ -813,11 +847,6 @@ public struct BSONObjectID: BSONValue, Equatable, CustomStringConvertible, Codab
}
}

/// The timestamp used to create this `BSONObjectID`
public var timestamp: UInt32 {
withUnsafePointer(to: self.oid) { oidPtr in UInt32(bson_oid_get_time_t(oidPtr)) }
}

public var description: String {
self.hex
}
Expand All @@ -831,12 +860,13 @@ public struct BSONObjectID: BSONValue, Equatable, CustomStringConvertible, Codab
self.oid = oid
}

/// Initializes an `BSONObjectID` from the provided hex `String`. Returns `nil` if the string is not a valid
/// ObjectID.
/// Initializes an `BSONObjectID` from the provided hex `String`.
/// - Throws:
/// - `InvalidArgumentError` if string passed is not a valid BSONObjectID
/// - SeeAlso: https://github.com/mongodb/specifications/blob/master/source/objectid.rst
public init?(_ hex: String) {
public init(_ hex: String) throws {
guard bson_oid_is_valid(hex, hex.utf8.count) else {
return nil
throw InvalidArgumentError(message: "Cannot create ObjectId from \(hex)")
}
var oid_t = bson_oid_t()
bson_oid_init_from_string(&oid_t, hex)
Expand All @@ -851,7 +881,7 @@ public struct BSONObjectID: BSONValue, Equatable, CustomStringConvertible, Codab
// assumes that the BSONObjectID is stored as a valid hex string.
let container = try decoder.singleValueContainer()
let hex = try container.decode(String.self)
guard let oid = BSONObjectID(hex) else {
guard let oid = try? BSONObjectID(hex) else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: decoder.codingPath,
Expand Down
2 changes: 1 addition & 1 deletion Sources/MongoSwift/BSON/Overwritable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal protocol Overwritable: BSONValue {
*
* - Throws:
* - `InternalError` if the `BSONValue` is an `Int` and cannot be written to BSON.
* - `LogicError` if the `BSONValue` is a `Decimal128` or `BSONObjectID` and is improperly formatted.
* - `LogicError` if the `BSONValue` is a `BSONDecimal128` or `BSONObjectID` and is improperly formatted.
*/
func writeToCurrentPosition(of iter: BSONDocumentIterator) throws
}
Expand Down
Loading