From 34af73e7899fe27de39dd55bbae953d496abf5e4 Mon Sep 17 00:00:00 2001 From: Patrick Freed Date: Wed, 3 Feb 2021 19:53:41 -0500 Subject: [PATCH] add bytebuffer interop to extjson encoder/decoder --- Sources/SwiftBSON/ExtendedJSONDecoder.swift | 47 +++++++++++++++---- Sources/SwiftBSON/ExtendedJSONEncoder.swift | 41 +++++++++++----- .../ExtendedJSONConversionTests.swift | 7 +++ 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftBSON/ExtendedJSONDecoder.swift b/Sources/SwiftBSON/ExtendedJSONDecoder.swift index 63f65fd..9660f13 100644 --- a/Sources/SwiftBSON/ExtendedJSONDecoder.swift +++ b/Sources/SwiftBSON/ExtendedJSONDecoder.swift @@ -1,5 +1,6 @@ import ExtrasJSON import Foundation +import NIO /// `ExtendedJSONDecoder` facilitates the decoding of ExtendedJSON into `Decodable` values. public class ExtendedJSONDecoder { @@ -42,19 +43,13 @@ public class ExtendedJSONDecoder { /// Initialize an `ExtendedJSONDecoder`. public init() {} - /// Decodes an instance of the requested type `T` from the provided extended JSON data. - /// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/ - /// - /// - Parameters: - /// - type: Codable type to decode the input into. - /// - data: `Data` which represents the JSON that will be decoded. - /// - Returns: Decoded representation of the JSON input as an instance of `T`. - /// - Throws: `DecodingError` if the JSON data is corrupt or if any value throws an error during decoding. - public func decode(_: T.Type, from data: Data) throws -> T { + private func decodeBytes(_: T.Type, from bytes: C) throws -> T + where C.Element == UInt8 + { // Data --> JSONValue --> BSON --> T // Takes in JSON as `Data` encoded with `.utf8` and runs it through ExtrasJSON's parser to get an // instance of the `JSONValue` enum. - let json = try JSONParser().parse(bytes: data) + let json = try JSONParser().parse(bytes: bytes) // Then a `BSON` enum instance is decoded from the `JSONValue`. let bson = try self.decodeBSONFromJSON(json, keyPath: []) @@ -65,6 +60,38 @@ public class ExtendedJSONDecoder { return try bsonDecoder.decode(T.self, fromBSON: bson) } + /// Decodes an instance of the requested type `T` from the provided extended JSON data. + /// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/ + /// + /// - Parameters: + /// - type: Codable type to decode the input into. + /// - data: `Data` which represents the JSON that will be decoded. + /// - Returns: Decoded representation of the JSON input as an instance of `T`. + /// - Throws: `DecodingError` if the JSON data is corrupt or if any value throws an error during decoding. + public func decode(_: T.Type, from data: Data) throws -> T { + try self.decodeBytes(T.self, from: data) + } + + /// Decodes an instance of the requested type `T` from the provided extended JSON data. + /// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/ + /// + /// - Parameters: + /// - type: Codable type to decode the input into. + /// - buffer: `ByteBuffer` which contains the JSON data that will be decoded. + /// - Returns: Decoded representation of the JSON input as an instance of `T`. + /// - Throws: `DecodingError` if the JSON data is corrupt or if any value throws an error during decoding. + public func decode(_: T.Type, from buffer: ByteBuffer) throws -> T { + guard buffer.readableBytes > 0 else { + throw DecodingError._extendedJSONError(keyPath: [], debugDescription: "empty buffer provided to decode") + } + + var buffer = buffer + // readBytes never returns nil here because we checked that the buffer wasn't empty and only read + // readable bytes out from it. + // swiftlint:disable:next force_unwrapping + return try self.decodeBytes(T.self, from: buffer.readBytes(length: buffer.readableBytes)!) + } + /// Decode a `BSON` from the given extended JSON. private func decodeBSONFromJSON(_ json: JSONValue, keyPath: [String]) throws -> BSON { switch try self.decodeScalar(json, keyPath: keyPath) { diff --git a/Sources/SwiftBSON/ExtendedJSONEncoder.swift b/Sources/SwiftBSON/ExtendedJSONEncoder.swift index 0c60e0c..0bed5ab 100644 --- a/Sources/SwiftBSON/ExtendedJSONEncoder.swift +++ b/Sources/SwiftBSON/ExtendedJSONEncoder.swift @@ -1,5 +1,6 @@ import ExtrasJSON import Foundation +import NIO /// Facilitates the encoding of `Encodable` values into ExtendedJSON. public class ExtendedJSONEncoder { @@ -24,16 +25,7 @@ public class ExtendedJSONEncoder { /// Initialize an `ExtendedJSONEncoder`. public init() {} - /// Encodes an instance of the Encodable Type `T` into Data representing canonical or relaxed extended JSON. - /// The value of `self.mode` will determine which format is used. If it is not set explicitly, relaxed will be used. - /// - /// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/ - /// - /// - Parameters: - /// - value: instance of Encodable type `T` which will be encoded. - /// - Returns: Encoded representation of the `T` input as an instance of `Data` representing ExtendedJSON. - /// - Throws: `EncodingError` if the value is corrupt or cannot be converted to valid ExtendedJSON. - public func encode(_ value: T) throws -> Data { + private func encodeBytes(_ value: T) throws -> [UInt8] { // T --> BSON --> JSONValue --> Data // Takes in any encodable type `T`, converts it to an instance of the `BSON` enum via the `BSONDecoder`. // The `BSON` is converted to an instance of the `JSON` enum via the `toRelaxedExtendedJSON` @@ -53,6 +45,33 @@ public class ExtendedJSONEncoder { var bytes: [UInt8] = [] json.value.appendBytes(to: &bytes) - return Data(bytes) + return bytes + } + + /// Encodes an instance of the Encodable Type `T` into Data representing canonical or relaxed extended JSON. + /// The value of `self.mode` will determine which format is used. If it is not set explicitly, relaxed will be used. + /// + /// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/ + /// + /// - Parameters: + /// - value: instance of Encodable type `T` which will be encoded. + /// - Returns: Encoded representation of the `T` input as an instance of `Data` representing ExtendedJSON. + /// - Throws: `EncodingError` if the value is corrupt or cannot be converted to valid ExtendedJSON. + public func encode(_ value: T) throws -> Data { + try Data(self.encodeBytes(value)) + } + + /// Encodes an instance of the Encodable Type `T` into a `ByteBuffer` representing canonical or relaxed extended + /// JSON. The value of `self.mode` will determine which format is used. If it is not set explicitly, relaxed will + /// be used. + /// + /// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/ + /// + /// - Parameters: + /// - value: instance of Encodable type `T` which will be encoded. + /// - Returns: Encoded representation of the `T` input as an instance of `ByteBuffer` representing ExtendedJSON. + /// - Throws: `EncodingError` if the value is corrupt or cannot be converted to valid ExtendedJSON. + public func encodeBuffer(_ value: T) throws -> ByteBuffer { + try BSON_ALLOCATOR.buffer(bytes: self.encodeBytes(value)) } } diff --git a/Tests/SwiftBSONTests/ExtendedJSONConversionTests.swift b/Tests/SwiftBSONTests/ExtendedJSONConversionTests.swift index b3ee527..0ac7b00 100644 --- a/Tests/SwiftBSONTests/ExtendedJSONConversionTests.swift +++ b/Tests/SwiftBSONTests/ExtendedJSONConversionTests.swift @@ -20,6 +20,7 @@ open class ExtendedJSONConversionTestCase: BSONTestCase { let regexStr = "{\"$regularExpression\":{\"pattern\":\"p\",\"options\":\"i\"}}" let canonicalExtJSON = "{\"x\":true,\"y\":{\"$numberInt\":\"5\"},\"z\":\(regexStr)}" let data = canonicalExtJSON.data(using: .utf8)! + let buffer = BSON_ALLOCATOR.buffer(bytes: data) let regexObj = BSONRegularExpression(pattern: "p", options: "i") let test = Test(x: true, y: 5, z: regexObj) @@ -28,17 +29,23 @@ open class ExtendedJSONConversionTestCase: BSONTestCase { encoder.mode = .canonical let encoded: Data = try encoder.encode(test) expect(encoded).to(cleanEqual(canonicalExtJSON)) + let encodedBuffer = try encoder.encodeBuffer(test) + expect(Data(encodedBuffer.readableBytesView)).to(cleanEqual(canonicalExtJSON)) // Test relaxed encoder encoder.mode = .relaxed let relaxedEncoded: Data = try encoder.encode(test) let relaxedExtJSON = "{\"x\":true,\"y\":5,\"z\":\(regexStr)}" expect(relaxedEncoded).to(cleanEqual(relaxedExtJSON)) + let relaxedEncodedBuffer = try encoder.encodeBuffer(test) + expect(Data(relaxedEncodedBuffer.readableBytesView)).to(cleanEqual(relaxedExtJSON)) // Test decoder let decoder = ExtendedJSONDecoder() let decoded = try decoder.decode(Test.self, from: data) expect(decoded).to(equal(test)) + let bufferDecoded = try decoder.decode(Test.self, from: buffer) + expect(bufferDecoded).to(equal(test)) } func testExtendedJSONDecoderErrorKeyPath() throws {