Skip to content

Commit 536781f

Browse files
authored
SWIFT-1088 ByteBuffer extJSON interoperability (#57)
1 parent 4c9f380 commit 536781f

File tree

3 files changed

+74
-21
lines changed

3 files changed

+74
-21
lines changed

Sources/SwiftBSON/ExtendedJSONDecoder.swift

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ExtrasJSON
22
import Foundation
3+
import NIO
34

45
/// `ExtendedJSONDecoder` facilitates the decoding of ExtendedJSON into `Decodable` values.
56
public class ExtendedJSONDecoder {
@@ -42,19 +43,13 @@ public class ExtendedJSONDecoder {
4243
/// Initialize an `ExtendedJSONDecoder`.
4344
public init() {}
4445

45-
/// Decodes an instance of the requested type `T` from the provided extended JSON data.
46-
/// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/
47-
///
48-
/// - Parameters:
49-
/// - type: Codable type to decode the input into.
50-
/// - data: `Data` which represents the JSON that will be decoded.
51-
/// - Returns: Decoded representation of the JSON input as an instance of `T`.
52-
/// - Throws: `DecodingError` if the JSON data is corrupt or if any value throws an error during decoding.
53-
public func decode<T: Decodable>(_: T.Type, from data: Data) throws -> T {
46+
private func decodeBytes<T: Decodable, C: Collection>(_: T.Type, from bytes: C) throws -> T
47+
where C.Element == UInt8
48+
{
5449
// Data --> JSONValue --> BSON --> T
5550
// Takes in JSON as `Data` encoded with `.utf8` and runs it through ExtrasJSON's parser to get an
5651
// instance of the `JSONValue` enum.
57-
let json = try JSONParser().parse(bytes: data)
52+
let json = try JSONParser().parse(bytes: bytes)
5853

5954
// Then a `BSON` enum instance is decoded from the `JSONValue`.
6055
let bson = try self.decodeBSONFromJSON(json, keyPath: [])
@@ -65,6 +60,38 @@ public class ExtendedJSONDecoder {
6560
return try bsonDecoder.decode(T.self, fromBSON: bson)
6661
}
6762

63+
/// Decodes an instance of the requested type `T` from the provided extended JSON data.
64+
/// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/
65+
///
66+
/// - Parameters:
67+
/// - type: Codable type to decode the input into.
68+
/// - data: `Data` which represents the JSON that will be decoded.
69+
/// - Returns: Decoded representation of the JSON input as an instance of `T`.
70+
/// - Throws: `DecodingError` if the JSON data is corrupt or if any value throws an error during decoding.
71+
public func decode<T: Decodable>(_: T.Type, from data: Data) throws -> T {
72+
try self.decodeBytes(T.self, from: data)
73+
}
74+
75+
/// Decodes an instance of the requested type `T` from the provided extended JSON data.
76+
/// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/
77+
///
78+
/// - Parameters:
79+
/// - type: Codable type to decode the input into.
80+
/// - buffer: `ByteBuffer` which contains the JSON data that will be decoded.
81+
/// - Returns: Decoded representation of the JSON input as an instance of `T`.
82+
/// - Throws: `DecodingError` if the JSON data is corrupt or if any value throws an error during decoding.
83+
public func decode<T: Decodable>(_: T.Type, from buffer: ByteBuffer) throws -> T {
84+
guard buffer.readableBytes > 0 else {
85+
throw DecodingError._extendedJSONError(keyPath: [], debugDescription: "empty buffer provided to decode")
86+
}
87+
88+
var buffer = buffer
89+
// readBytes never returns nil here because we checked that the buffer wasn't empty and only read
90+
// readable bytes out from it.
91+
// swiftlint:disable:next force_unwrapping
92+
return try self.decodeBytes(T.self, from: buffer.readBytes(length: buffer.readableBytes)!)
93+
}
94+
6895
/// Decode a `BSON` from the given extended JSON.
6996
private func decodeBSONFromJSON(_ json: JSONValue, keyPath: [String]) throws -> BSON {
7097
switch try self.decodeScalar(json, keyPath: keyPath) {

Sources/SwiftBSON/ExtendedJSONEncoder.swift

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ExtrasJSON
22
import Foundation
3+
import NIO
34

45
/// Facilitates the encoding of `Encodable` values into ExtendedJSON.
56
public class ExtendedJSONEncoder {
@@ -24,16 +25,7 @@ public class ExtendedJSONEncoder {
2425
/// Initialize an `ExtendedJSONEncoder`.
2526
public init() {}
2627

27-
/// Encodes an instance of the Encodable Type `T` into Data representing canonical or relaxed extended JSON.
28-
/// The value of `self.mode` will determine which format is used. If it is not set explicitly, relaxed will be used.
29-
///
30-
/// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/
31-
///
32-
/// - Parameters:
33-
/// - value: instance of Encodable type `T` which will be encoded.
34-
/// - Returns: Encoded representation of the `T` input as an instance of `Data` representing ExtendedJSON.
35-
/// - Throws: `EncodingError` if the value is corrupt or cannot be converted to valid ExtendedJSON.
36-
public func encode<T: Encodable>(_ value: T) throws -> Data {
28+
private func encodeBytes<T: Encodable>(_ value: T) throws -> [UInt8] {
3729
// T --> BSON --> JSONValue --> Data
3830
// Takes in any encodable type `T`, converts it to an instance of the `BSON` enum via the `BSONDecoder`.
3931
// The `BSON` is converted to an instance of the `JSON` enum via the `toRelaxedExtendedJSON`
@@ -53,6 +45,33 @@ public class ExtendedJSONEncoder {
5345

5446
var bytes: [UInt8] = []
5547
json.value.appendBytes(to: &bytes)
56-
return Data(bytes)
48+
return bytes
49+
}
50+
51+
/// Encodes an instance of the Encodable Type `T` into Data representing canonical or relaxed extended JSON.
52+
/// The value of `self.mode` will determine which format is used. If it is not set explicitly, relaxed will be used.
53+
///
54+
/// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/
55+
///
56+
/// - Parameters:
57+
/// - value: instance of Encodable type `T` which will be encoded.
58+
/// - Returns: Encoded representation of the `T` input as an instance of `Data` representing ExtendedJSON.
59+
/// - Throws: `EncodingError` if the value is corrupt or cannot be converted to valid ExtendedJSON.
60+
public func encode<T: Encodable>(_ value: T) throws -> Data {
61+
try Data(self.encodeBytes(value))
62+
}
63+
64+
/// Encodes an instance of the Encodable Type `T` into a `ByteBuffer` representing canonical or relaxed extended
65+
/// JSON. The value of `self.mode` will determine which format is used. If it is not set explicitly, relaxed will
66+
/// be used.
67+
///
68+
/// - SeeAlso: https://docs.mongodb.com/manual/reference/mongodb-extended-json/
69+
///
70+
/// - Parameters:
71+
/// - value: instance of Encodable type `T` which will be encoded.
72+
/// - Returns: Encoded representation of the `T` input as an instance of `ByteBuffer` representing ExtendedJSON.
73+
/// - Throws: `EncodingError` if the value is corrupt or cannot be converted to valid ExtendedJSON.
74+
public func encodeBuffer<T: Encodable>(_ value: T) throws -> ByteBuffer {
75+
try BSON_ALLOCATOR.buffer(bytes: self.encodeBytes(value))
5776
}
5877
}

Tests/SwiftBSONTests/ExtendedJSONConversionTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ open class ExtendedJSONConversionTestCase: BSONTestCase {
2020
let regexStr = "{\"$regularExpression\":{\"pattern\":\"p\",\"options\":\"i\"}}"
2121
let canonicalExtJSON = "{\"x\":true,\"y\":{\"$numberInt\":\"5\"},\"z\":\(regexStr)}"
2222
let data = canonicalExtJSON.data(using: .utf8)!
23+
let buffer = BSON_ALLOCATOR.buffer(bytes: data)
2324
let regexObj = BSONRegularExpression(pattern: "p", options: "i")
2425
let test = Test(x: true, y: 5, z: regexObj)
2526

@@ -28,17 +29,23 @@ open class ExtendedJSONConversionTestCase: BSONTestCase {
2829
encoder.mode = .canonical
2930
let encoded: Data = try encoder.encode(test)
3031
expect(encoded).to(cleanEqual(canonicalExtJSON))
32+
let encodedBuffer = try encoder.encodeBuffer(test)
33+
expect(Data(encodedBuffer.readableBytesView)).to(cleanEqual(canonicalExtJSON))
3134

3235
// Test relaxed encoder
3336
encoder.mode = .relaxed
3437
let relaxedEncoded: Data = try encoder.encode(test)
3538
let relaxedExtJSON = "{\"x\":true,\"y\":5,\"z\":\(regexStr)}"
3639
expect(relaxedEncoded).to(cleanEqual(relaxedExtJSON))
40+
let relaxedEncodedBuffer = try encoder.encodeBuffer(test)
41+
expect(Data(relaxedEncodedBuffer.readableBytesView)).to(cleanEqual(relaxedExtJSON))
3742

3843
// Test decoder
3944
let decoder = ExtendedJSONDecoder()
4045
let decoded = try decoder.decode(Test.self, from: data)
4146
expect(decoded).to(equal(test))
47+
let bufferDecoded = try decoder.decode(Test.self, from: buffer)
48+
expect(bufferDecoded).to(equal(test))
4249
}
4350

4451
func testExtendedJSONDecoderErrorKeyPath() throws {

0 commit comments

Comments
 (0)