Skip to content

Commit e0b0efb

Browse files
authored
SWIFT-918 extended json initializers for all non-recursive BSONValues
except for Date. All recursive types and date will come in SWIFT-919
1 parent 1cb13d6 commit e0b0efb

16 files changed

+912
-13
lines changed

Sources/BSON/BSONBinary.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,65 @@ public struct BSONBinary: Equatable, Hashable {
143143
}
144144

145145
extension BSONBinary: BSONValue {
146+
/*
147+
* Initializes a `Binary` from ExtendedJSON.
148+
*
149+
* Parameters:
150+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `Binary`.
151+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
152+
* This is used for error messages.
153+
*
154+
* Returns:
155+
* - `nil` if the provided value does not conform to the `Binary` syntax.
156+
*
157+
* Throws:
158+
* - `DecodingError` if `json` is a partial match or is malformed.
159+
*/
160+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
161+
// canonical and relaxed extended JSON
162+
guard let (binary, obj) = try json.isObjectWithSingleKey(key: "$binary", keyPath: keyPath) else {
163+
return nil
164+
}
165+
guard
166+
let binaryObj = binary.objectValue,
167+
binaryObj.count == 2,
168+
let base64 = binaryObj["base64"],
169+
let subTypeInput = binaryObj["subType"]
170+
else {
171+
throw DecodingError._extendedJSONError(
172+
keyPath: keyPath,
173+
debugDescription: "Expected \"base64\" and \"subType\" keys in the object at \"$binary\", " +
174+
"found keys: \(obj.keys)"
175+
)
176+
}
177+
guard let base64Str = base64.stringValue else {
178+
throw DecodingError._extendedJSONError(
179+
keyPath: keyPath,
180+
debugDescription: "Could not parse `base64` from \"\(base64)\", " +
181+
"input must be a base64-encoded (with padding as =) payload as a string"
182+
)
183+
}
184+
guard
185+
let subTypeStr = subTypeInput.stringValue,
186+
let subTypeInt = UInt8(subTypeStr, radix: 16),
187+
let subType = Subtype(rawValue: subTypeInt)
188+
else {
189+
throw DecodingError._extendedJSONError(
190+
keyPath: keyPath,
191+
debugDescription: "Could not parse `SubType` from \"\(subTypeInput)\", " +
192+
"input must be a BSON binary type as a one- or two-character hex string"
193+
)
194+
}
195+
do {
196+
self = try BSONBinary(base64: base64Str, subtype: subType)
197+
} catch {
198+
throw DecodingError._extendedJSONError(
199+
keyPath: keyPath,
200+
debugDescription: error.localizedDescription
201+
)
202+
}
203+
}
204+
146205
internal static var bsonType: BSONType { .binary }
147206

148207
internal var bson: BSON { .binary(self) }

Sources/BSON/BSONCode.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,49 @@ public struct BSONCodeWithScope: Equatable, Hashable {
2828
}
2929

3030
extension BSONCode: BSONValue {
31+
/*
32+
* Initializes a `BSONCode` from ExtendedJSON.
33+
*
34+
* Parameters:
35+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for `Code`.
36+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
37+
* This is used for error messages.
38+
*
39+
* Returns:
40+
* - `nil` if the provided value is not a `String`.
41+
*
42+
* Throws:
43+
* - `DecodingError` if `json` is a partial match or is malformed.
44+
*/
45+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
46+
switch json {
47+
case let .object(obj):
48+
// canonical extended JSON
49+
guard let value = obj["$code"] else {
50+
return nil
51+
}
52+
guard obj.count == 1 else {
53+
if obj.count == 2 && obj.keys.contains("$scope") {
54+
return nil
55+
} else {
56+
throw DecodingError._extendedJSONError(
57+
keyPath: keyPath,
58+
debugDescription: "Expected only \"$code\" and optionally \"$scope\" keys, got: \(obj.keys)"
59+
)
60+
}
61+
}
62+
guard let str = value.stringValue else {
63+
throw DecodingError._extendedJSONError(
64+
keyPath: keyPath,
65+
debugDescription: "Could not parse `BSONCode` from \"\(value)\", input must be a string."
66+
)
67+
}
68+
self = BSONCode(code: str)
69+
default:
70+
return nil
71+
}
72+
}
73+
3174
internal static var bsonType: BSONType { .code }
3275

3376
internal var bson: BSON { .code(self) }

Sources/BSON/BSONDBPointer.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,56 @@ public struct BSONDBPointer: Equatable, Hashable {
1616
}
1717

1818
extension BSONDBPointer: BSONValue {
19+
/*
20+
* Initializes a `BSONDBPointer` from ExtendedJSON.
21+
*
22+
* Parameters:
23+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `DBPointer`.
24+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
25+
* This is used for error messages.
26+
*
27+
* Returns:
28+
* - `nil` if the provided value is not a `DBPointer`.
29+
*
30+
* Throws:
31+
* - `DecodingError` if `json` is a partial match or is malformed.
32+
*/
33+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
34+
// canonical and relaxed extended JSON
35+
guard let (value, _) = try json.isObjectWithSingleKey(key: "$dbPointer", keyPath: keyPath) else {
36+
return nil
37+
}
38+
guard let dbPointerObj = value.objectValue else {
39+
throw DecodingError._extendedJSONError(
40+
keyPath: keyPath,
41+
debugDescription: "Expected \(value) to be an object"
42+
)
43+
}
44+
guard
45+
dbPointerObj.count == 2,
46+
let ref = dbPointerObj["$ref"],
47+
let id = dbPointerObj["$id"]
48+
else {
49+
throw DecodingError._extendedJSONError(
50+
keyPath: keyPath,
51+
debugDescription: "Expected \"$ref\" and \"$id\" keys, " +
52+
"found \(dbPointerObj.keys.count) key(s) within \"$dbPointer\": \(dbPointerObj.keys)"
53+
)
54+
}
55+
guard
56+
let refStr = ref.stringValue,
57+
let oid = try BSONObjectID(fromExtJSON: id, keyPath: keyPath)
58+
else {
59+
throw DecodingError._extendedJSONError(
60+
keyPath: keyPath,
61+
debugDescription: "Could not parse `BSONDBPointer` from \"\(dbPointerObj)\", " +
62+
"the value for \"$ref\" must be a string representing a namespace " +
63+
"and the value for \"$id\" must be an extended JSON representation of a `BSONObjectID`"
64+
)
65+
}
66+
self = BSONDBPointer(ref: refStr, id: oid)
67+
}
68+
1969
internal static var bsonType: BSONType { .dbPointer }
2070

2171
internal var bson: BSON { .dbPointer(self) }

Sources/BSON/BSONDecimal128.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,42 @@ public struct BSONDecimal128: Equatable, Hashable, CustomStringConvertible {
473473
}
474474

475475
extension BSONDecimal128: BSONValue {
476+
/*
477+
* Initializes a `Decimal128` from ExtendedJSON.
478+
*
479+
* Parameters:
480+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `Decimal128`.
481+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
482+
* This is used for error messages.
483+
*
484+
* Returns:
485+
* - `nil` if the provided value is not a `Decimal128`.
486+
*
487+
* Throws:
488+
* - `DecodingError` if `json` is a partial match or is malformed.
489+
*/
490+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
491+
// canonical and relaxed extended JSON
492+
guard let (value, _) = try json.isObjectWithSingleKey(key: "$numberDecimal", keyPath: keyPath) else {
493+
return nil
494+
}
495+
guard let str = value.stringValue else {
496+
throw DecodingError._extendedJSONError(
497+
keyPath: keyPath,
498+
debugDescription: "Could not parse `Decimal128` from \"\(value)\", " +
499+
"input must be a decimal as a string"
500+
)
501+
}
502+
do {
503+
self = try BSONDecimal128(str)
504+
} catch {
505+
throw DecodingError._extendedJSONError(
506+
keyPath: keyPath,
507+
debugDescription: error.localizedDescription
508+
)
509+
}
510+
}
511+
476512
internal static var bsonType: BSONType { .decimal128 }
477513

478514
internal var bson: BSON { .decimal128(self) }

Sources/BSON/BSONNulls.swift

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@ import NIO
22

33
/// A struct to represent the BSON null type.
44
internal struct BSONNull: BSONValue, Equatable {
5+
/*
6+
* Initializes a `BSONNull` from ExtendedJSON.
7+
*
8+
* Parameters:
9+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `BSONNull`.
10+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
11+
* This is used for error messages.
12+
*
13+
* Returns:
14+
* - `nil` if the provided value is not `null`.
15+
*
16+
*/
17+
internal init?(fromExtJSON json: JSON, keyPath: [String]) {
18+
switch json {
19+
case .null:
20+
// canonical or relaxed extended JSON
21+
self = BSONNull()
22+
default:
23+
return nil
24+
}
25+
}
26+
527
internal static var bsonType: BSONType { .null }
628

729
internal var bson: BSON { .null }
@@ -20,6 +42,34 @@ internal struct BSONNull: BSONValue, Equatable {
2042

2143
/// A struct to represent the BSON undefined type.
2244
internal struct BSONUndefined: BSONValue, Equatable {
45+
/*
46+
* Initializes a `BSONUndefined` from ExtendedJSON.
47+
*
48+
* Parameters:
49+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `BSONUndefined`.
50+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
51+
* This is used for error messages.
52+
*
53+
* Returns:
54+
* - `nil` if the provided value is not `{"$undefined": true}`.
55+
*
56+
* Throws:
57+
* - `DecodingError` if `json` is a partial match or is malformed.
58+
*/
59+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
60+
// canonical and relaxed extended JSON
61+
guard let (value, obj) = try json.isObjectWithSingleKey(key: "$undefined", keyPath: keyPath) else {
62+
return nil
63+
}
64+
guard value.boolValue == true else {
65+
throw DecodingError._extendedJSONError(
66+
keyPath: keyPath,
67+
debugDescription: "Expected \(obj) to be \"{\"$undefined\": true}\""
68+
)
69+
}
70+
self = BSONUndefined()
71+
}
72+
2373
internal static var bsonType: BSONType { .undefined }
2474

2575
internal var bson: BSON { .undefined }
@@ -38,6 +88,34 @@ internal struct BSONUndefined: BSONValue, Equatable {
3888

3989
/// A struct to represent the BSON MinKey type.
4090
internal struct BSONMinKey: BSONValue, Equatable {
91+
/*
92+
* Initializes a `BSONMinKey` from ExtendedJSON.
93+
*
94+
* Parameters:
95+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `BSONMinKey`.
96+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
97+
* This is used for error messages.
98+
*
99+
* Returns:
100+
* - `nil` if the provided value is not `{"$minKey": 1}`.
101+
*
102+
* Throws:
103+
* - `DecodingError` if `json` is a partial match or is malformed.
104+
*/
105+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
106+
// canonical and relaxed extended JSON
107+
guard let (value, obj) = try json.isObjectWithSingleKey(key: "$minKey", keyPath: keyPath) else {
108+
return nil
109+
}
110+
guard value.doubleValue == 1 else {
111+
throw DecodingError._extendedJSONError(
112+
keyPath: keyPath,
113+
debugDescription: "Expected \(obj) to be \"{\"$minKey\": 1}\""
114+
)
115+
}
116+
self = BSONMinKey()
117+
}
118+
41119
internal static var bsonType: BSONType { .minKey }
42120

43121
internal var bson: BSON { .minKey }
@@ -56,6 +134,34 @@ internal struct BSONMinKey: BSONValue, Equatable {
56134

57135
/// A struct to represent the BSON MinKey type.
58136
internal struct BSONMaxKey: BSONValue, Equatable {
137+
/*
138+
* Initializes a `BSONMaxKey` from ExtendedJSON.
139+
*
140+
* Parameters:
141+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `BSONMaxKey`.
142+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
143+
* This is used for error messages.
144+
*
145+
* Returns:
146+
* - `nil` if the provided value is not `{"$minKey": 1}`.
147+
*
148+
* Throws:
149+
* - `DecodingError` if `json` is a partial match or is malformed.
150+
*/
151+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
152+
// canonical and relaxed extended JSON
153+
guard let (value, obj) = try json.isObjectWithSingleKey(key: "$maxKey", keyPath: keyPath) else {
154+
return nil
155+
}
156+
guard value.doubleValue == 1 else {
157+
throw DecodingError._extendedJSONError(
158+
keyPath: keyPath,
159+
debugDescription: "Expected \(obj) to be \"{\"$maxKey\": 1}\""
160+
)
161+
}
162+
self = BSONMaxKey()
163+
}
164+
59165
internal static var bsonType: BSONType { .maxKey }
60166

61167
internal var bson: BSON { .maxKey }

Sources/BSON/BSONObjectID.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,42 @@ public struct BSONObjectID: Equatable, Hashable, CustomStringConvertible {
6161
}
6262
self = BSONObjectID(data)
6363
}
64+
65+
/*
66+
* Initializes an `ObjectID` from ExtendedJSON.
67+
*
68+
* Parameters:
69+
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for an `ObjectID`.
70+
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
71+
* This is used for error messages.
72+
*
73+
* Throws:
74+
* - `DecodingError` if `json` is a partial match or is malformed.
75+
*
76+
* Returns:
77+
* - `nil` if the provided value is not an `ObjectID`.
78+
*/
79+
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
80+
// canonical and relaxed extended JSON
81+
guard let (value, _) = try json.isObjectWithSingleKey(key: "$oid", keyPath: keyPath) else {
82+
return nil
83+
}
84+
guard let str = value.stringValue else {
85+
throw DecodingError._extendedJSONError(
86+
keyPath: keyPath,
87+
debugDescription:
88+
"Could not parse `ObjectID` from \"\(value)\", input must be a 24-character, big-endian hex string."
89+
)
90+
}
91+
do {
92+
self = try BSONObjectID(str)
93+
} catch {
94+
throw DecodingError._extendedJSONError(
95+
keyPath: keyPath,
96+
debugDescription: error.localizedDescription
97+
)
98+
}
99+
}
64100
}
65101

66102
extension BSONObjectID: BSONValue {

0 commit comments

Comments
 (0)