Skip to content

Commit 1cb13d6

Browse files
authored
SWIFT-918 Extended JSON to BSON Conversion for Int32
1 parent a520d99 commit 1cb13d6

File tree

4 files changed

+94
-0
lines changed

4 files changed

+94
-0
lines changed

Sources/BSON/BSONError.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ public enum BSONError {
4242
}
4343
}
4444

45+
extension DecodingError {
46+
/// Standardize the errors emitted by BSONValue initializers.
47+
internal static func _extendedJSONError(
48+
keyPath: [String],
49+
debugDescription: String
50+
) -> DecodingError {
51+
let debugStart = keyPath.joined(separator: ".") +
52+
(keyPath == [] ? "" : ": ")
53+
return .dataCorrupted(DecodingError.Context(
54+
codingPath: [],
55+
debugDescription: debugStart + debugDescription
56+
))
57+
}
58+
}
59+
4560
/// Standardize the errors emitted from the BSON Iterator.
4661
/// The BSON iterator is used for validation so this should help debug the underlying incorrect binary.
4762
internal func BSONIterationError(

Sources/BSON/BSONValue.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ internal protocol BSONValue: Codable {
1414

1515
/// Writes this value's BSON byte representation to the provided ByteBuffer.
1616
func write(to buffer: inout ByteBuffer)
17+
18+
/// Initializes a corresponding `BSON` from the provided extendedJSON.
19+
/// Currently commented out to avoid "not implemented" errors while implementing the
20+
/// initializer one by one on each BSONValue.
21+
// internal init(fromExtJSON json: JSON) throws
1722
}
1823

1924
/// Convenience extension to get static bsonType from an instance

Sources/BSON/Integers+BSONValue.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,52 @@
11
import NIO
22

33
extension Int32: BSONValue {
4+
/*
5+
* Initializes an `Int32` from ExtendedJSON.
6+
*
7+
* Parameters:
8+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for an `Int32`.
9+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
10+
* This is used for error messages.
11+
*
12+
* Returns:
13+
* - `nil` if the provided value is not an `Int32`.
14+
*
15+
* Throws:
16+
* - `DecodingError` if `json` is a partial match or is malformed.
17+
*/
18+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
19+
switch json {
20+
case let .number(n):
21+
// relaxed extended JSON
22+
guard let int = Int32(exactly: n) else {
23+
return nil
24+
}
25+
self = int
26+
case let .object(obj):
27+
// canonical extended JSON
28+
guard let value = obj["$numberInt"] else {
29+
return nil
30+
}
31+
guard obj.count == 1 else {
32+
throw DecodingError._extendedJSONError(
33+
keyPath: keyPath,
34+
debugDescription: "Expected only \"$numberInt\" key, found too many keys: \(obj.keys)"
35+
)
36+
}
37+
guard let str = value.stringValue, let int = Int32(str) else {
38+
throw DecodingError._extendedJSONError(
39+
keyPath: keyPath,
40+
debugDescription: "Could not parse `Int32` from \"\(value)\", " +
41+
"input must be a 32-bit signed integer as a string."
42+
)
43+
}
44+
self = int
45+
default:
46+
return nil
47+
}
48+
}
49+
450
internal static var bsonType: BSONType { .int32 }
551

652
internal var bson: BSON { .int32(self) }
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@testable import BSON
2+
import Foundation
3+
import Nimble
4+
import NIO
5+
import XCTest
6+
7+
open class ExtendedJSONConversionTestCase: BSONTestCase {
8+
func testInt32() throws {
9+
// Success cases
10+
expect(try Int32(fromExtJSON: 5, keyPath: [])).to(equal(5))
11+
expect(try Int32(fromExtJSON: ["$numberInt": "5"], keyPath: [])).to(equal(5))
12+
13+
// Nil cases
14+
expect(try Int32(fromExtJSON: JSON.number(Double(Int32.max) + 1), keyPath: [])).to(beNil())
15+
expect(try Int32(fromExtJSON: JSON.bool(true), keyPath: [])).to(beNil())
16+
expect(try Int32(fromExtJSON: ["bad": "5"], keyPath: [])).to(beNil())
17+
18+
// Error cases
19+
expect(try Int32(fromExtJSON: ["$numberInt": 5], keyPath: []))
20+
.to(throwError(errorType: DecodingError.self))
21+
expect(try Int32(fromExtJSON: ["$numberInt": "5", "extra": true], keyPath: []))
22+
.to(throwError(errorType: DecodingError.self))
23+
expect(try Int32(fromExtJSON: ["$numberInt": .string("\(Double(Int32.max) + 1)")], keyPath: ["key", "path"]))
24+
.to(throwError(errorType: DecodingError.self))
25+
}
26+
27+
// TODO: Add equivalent tests for each type that conforms to `BSONValue`
28+
}

0 commit comments

Comments
 (0)