Skip to content

Commit

Permalink
Merge pull request #84 from orlandos-nl/jo/sendable-support
Browse files Browse the repository at this point in the history
  • Loading branch information
Joannis authored May 26, 2024
2 parents 57da7f3 + ea93a59 commit ab23e8e
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 76 deletions.
34 changes: 34 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// swift-tools-version:5.9

import PackageDescription

let package = Package(
name: "BSON",
platforms: [
.macOS(.v10_15),
.iOS(.v13)
],
products: [
.library(
name: "BSON",
targets: ["BSON"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.46.0")
],
targets: [
.target(
name: "BSON",
dependencies: [
.product(name: "NIOCore", package: "swift-nio"),
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency=complete"),
]
),
.testTarget(
name: "BSONTests",
dependencies: ["BSON"])
],
swiftLanguageVersions: [.v4_2]
)
2 changes: 1 addition & 1 deletion Sources/BSON/Codable/Decoding/BSONDecoder.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// A helper that is able to decode BSON data types into a `Decodable` type.
public struct BSONDecoder {
public struct BSONDecoder: @unchecked Sendable {
/// The configuration used for decoding
public var settings: BSONDecoderSettings

Expand Down
156 changes: 84 additions & 72 deletions Sources/BSON/Codable/Decoding/BSONDecoderSettings.swift
Original file line number Diff line number Diff line change
@@ -1,90 +1,102 @@
import NIOConcurrencyHelpers

/// A configuration structs that contains all strategies for (lossy) decoding values
public struct BSONDecoderSettings {
public struct BSONDecoderSettings: Sendable {
/// Decodes values only if they are exactly matching the expectation.
///
/// For non-BSON types, the following mapping applies:
///
/// - Float: Decode from Double
/// - Non-native Integer types: .anyInteger
public static let strict: BSONDecoderSettings = BSONDecoderSettings(
fastPath: false,
decodeNullAsNil: false,
filterDollarPrefix: false,
stringDecodingStrategy: .string,
decodeObjectIdFromString: false,
timestampToDateDecodingStrategy: .never,
floatDecodingStrategy: .double,
doubleDecodingStrategy: .double,
int8DecodingStrategy: .anyInteger,
int16DecodingStrategy: .anyInteger,
int32DecodingStrategy: .int32,
int64DecodingStrategy: .int64,
intDecodingStrategy: .anyInteger,
uint8DecodingStrategy: .anyInteger,
uint16DecodingStrategy: .anyInteger,
uint32DecodingStrategy: .anyInteger,
uint64DecodingStrategy: .anyInteger,
uintDecodingStrategy: .anyInteger
)

public static var strict: BSONDecoderSettings {
BSONDecoderSettings(
fastPath: false,
decodeNullAsNil: false,
filterDollarPrefix: false,
stringDecodingStrategy: .string,
decodeObjectIdFromString: false,
timestampToDateDecodingStrategy: .never,
floatDecodingStrategy: .double,
doubleDecodingStrategy: .double,
int8DecodingStrategy: .anyInteger,
int16DecodingStrategy: .anyInteger,
int32DecodingStrategy: .int32,
int64DecodingStrategy: .int64,
intDecodingStrategy: .anyInteger,
uint8DecodingStrategy: .anyInteger,
uint16DecodingStrategy: .anyInteger,
uint32DecodingStrategy: .anyInteger,
uint64DecodingStrategy: .anyInteger,
uintDecodingStrategy: .anyInteger
)
}

/// Uses ``FastBSONDecoder``
public static let fastPath: BSONDecoderSettings = BSONDecoderSettings(
fastPath: true,
decodeNullAsNil: false,
filterDollarPrefix: false,
stringDecodingStrategy: .string,
decodeObjectIdFromString: false,
timestampToDateDecodingStrategy: .never,
floatDecodingStrategy: .double,
doubleDecodingStrategy: .double,
int8DecodingStrategy: .anyInteger,
int16DecodingStrategy: .anyInteger,
int32DecodingStrategy: .int32,
int64DecodingStrategy: .int64,
intDecodingStrategy: .anyInteger,
uint8DecodingStrategy: .anyInteger,
uint16DecodingStrategy: .anyInteger,
uint32DecodingStrategy: .anyInteger,
uint64DecodingStrategy: .anyInteger,
uintDecodingStrategy: .anyInteger
)
public static var fastPath: BSONDecoderSettings {
BSONDecoderSettings(
fastPath: true,
decodeNullAsNil: false,
filterDollarPrefix: false,
stringDecodingStrategy: .string,
decodeObjectIdFromString: false,
timestampToDateDecodingStrategy: .never,
floatDecodingStrategy: .double,
doubleDecodingStrategy: .double,
int8DecodingStrategy: .anyInteger,
int16DecodingStrategy: .anyInteger,
int32DecodingStrategy: .int32,
int64DecodingStrategy: .int64,
intDecodingStrategy: .anyInteger,
uint8DecodingStrategy: .anyInteger,
uint16DecodingStrategy: .anyInteger,
uint32DecodingStrategy: .anyInteger,
uint64DecodingStrategy: .anyInteger,
uintDecodingStrategy: .anyInteger
)
}

private static let _default = NIOLockedValueBox<BSONDecoderSettings>(.adaptive)
public static var `default`: BSONDecoderSettings {
get { _default.withLockedValue { $0 } }
set { _default.withLockedValue { $0 = newValue } }
}

public static var `default`: BSONDecoderSettings = .adaptive

/// Tries to decode values, even if the types do not match. Some precision loss is possible.
public static let adaptive: BSONDecoderSettings = BSONDecoderSettings(
fastPath: false,
decodeNullAsNil: true,
filterDollarPrefix: false,
stringDecodingStrategy: .adaptive,
decodeObjectIdFromString: true,
timestampToDateDecodingStrategy: .relativeToUnixEpoch,
floatDecodingStrategy: .adaptive,
doubleDecodingStrategy: .adaptive,
int8DecodingStrategy: .adaptive,
int16DecodingStrategy: .adaptive,
int32DecodingStrategy: .adaptive,
int64DecodingStrategy: .adaptive,
intDecodingStrategy: .adaptive,
uint8DecodingStrategy: .adaptive,
uint16DecodingStrategy: .adaptive,
uint32DecodingStrategy: .adaptive,
uint64DecodingStrategy: .adaptive,
uintDecodingStrategy: .adaptive
)

public static var adaptive: BSONDecoderSettings {
BSONDecoderSettings(
fastPath: false,
decodeNullAsNil: true,
filterDollarPrefix: false,
stringDecodingStrategy: .adaptive,
decodeObjectIdFromString: true,
timestampToDateDecodingStrategy: .relativeToUnixEpoch,
floatDecodingStrategy: .adaptive,
doubleDecodingStrategy: .adaptive,
int8DecodingStrategy: .adaptive,
int16DecodingStrategy: .adaptive,
int32DecodingStrategy: .adaptive,
int64DecodingStrategy: .adaptive,
intDecodingStrategy: .adaptive,
uint8DecodingStrategy: .adaptive,
uint16DecodingStrategy: .adaptive,
uint32DecodingStrategy: .adaptive,
uint64DecodingStrategy: .adaptive,
uintDecodingStrategy: .adaptive
)
}

/// A strategy used to decode `P` from a BSON `Primitive?` value
///
/// If the key (`String`) is nil the value was not associated with a Dictionary Document.
///
/// If the value (`Primitive`) is nil, the value was not found at all but can be overwritten with a default
public typealias DecodingStrategy<P> = (String?, Primitive?) throws -> P?
public typealias DecodingStrategy<P> = @Sendable (String?, Primitive?) throws -> P?

/// A strategy used to decode float values
/// Floats are not a native BSON type
///
/// WARNING: This API may have cases added to it, do *not* manually switch over them
public enum FloatDecodingStrategy {
public enum FloatDecodingStrategy: Sendable {
case string
case double
case adaptive
Expand All @@ -95,7 +107,7 @@ public struct BSONDecoderSettings {
/// Floats are not a native BSON type
///
/// WARNING: This API may have cases added to it, do *not* manually switch over them
public enum IntegerDecodingStrategy<I: FixedWidthInteger> {
public enum IntegerDecodingStrategy<I: FixedWidthInteger>: Sendable {
/// Decodes this integer type only from Strings
case string

Expand All @@ -122,7 +134,7 @@ public struct BSONDecoderSettings {
/// Although Doubles are a native BSON type, lossy conversion may be favourable in certain circumstances
///
/// WARNING: This API may have cases added to it, do *not* manually switch over them
public enum DoubleDecodingStrategy {
public enum DoubleDecodingStrategy: Sendable {
/// Decodes only the correct type. No lossy decoding.
case double

Expand All @@ -148,7 +160,7 @@ public struct BSONDecoderSettings {
/// A strategy used to influence decoding Strings
///
/// WARNING: This API may have cases added to it, do *not* manually switch over them
public enum StringDecodingStrategy {
public enum StringDecodingStrategy: Sendable {
/// Decode only strings themselves
case string

Expand All @@ -172,7 +184,7 @@ public struct BSONDecoderSettings {
case custom(DecodingStrategy<String>)
}

public enum TimestampToDateDecodingStrategy {
public enum TimestampToDateDecodingStrategy: Sendable {

/// Do not convert, and throw an error
case never
Expand Down
2 changes: 1 addition & 1 deletion Sources/BSON/Document/Document+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public struct BSONValueNotFound: Error, CustomStringConvertible {
}

// TODO: Include more context. These errors are thrown in BSONDecoder but provide no information at all about the KeyPath, and are therefore useless.
struct BSONTypeConversionError<A>: Error {
struct BSONTypeConversionError<A>: Error, @unchecked Sendable {
let from: A
let to: Any.Type
}
3 changes: 1 addition & 2 deletions Sources/BSON/Document/Document.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Foundation
import NIOCore

// TODO: ByteBuffer is missing Sendable annotation, but is Sendable
public struct Document: Primitive, @unchecked Sendable {
public struct Document: Primitive, Sendable {
static let allocator = ByteBufferAllocator()

/// The internal storage engine that stores BSON in it's original binary form
Expand Down

0 comments on commit ab23e8e

Please sign in to comment.