forked from mongodb/swift-bson
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBSONCode.swift
196 lines (175 loc) · 7.6 KB
/
BSONCode.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import NIO
/// A struct to represent the BSON Code type.
public struct BSONCode: Equatable, Hashable {
/// A string containing Javascript code.
public let code: String
/// Initializes a `BSONCode` with an optional scope value.
public init(code: String) {
self.code = code
}
}
/// A struct to represent BSON CodeWithScope.
public struct BSONCodeWithScope: Equatable, Hashable {
/// A string containing Javascript code.
public let code: String
/// An optional scope `BSONDocument` containing a mapping of identifiers to values,
/// representing the context in which `code` should be evaluated.
public let scope: BSONDocument
/// Initializes a `BSONCodeWithScope` with an optional scope value.
public init(code: String, scope: BSONDocument) {
self.code = code
self.scope = scope
}
}
extension BSONCode: BSONValue {
internal static let extJSONTypeWrapperKeys: [String] = ["$code"]
/*
* Initializes a `BSONCode` from ExtendedJSON.
*
* Parameters:
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for `Code`.
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
* This is used for error messages.
*
* Returns:
* - `nil` if the provided value is not a `String`.
*
* Throws:
* - `DecodingError` if `json` is a partial match or is malformed.
*/
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
switch json.value {
case let .object(obj):
// canonical and relaxed extended JSON
guard let value = obj["$code"] else {
return nil
}
guard obj.count == 1 else {
if obj.count == 2 && obj.keys.contains("$scope") {
return nil
} else {
throw DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Expected only \"$code\" and optionally \"$scope\" keys, got: \(obj.keys)"
)
}
}
guard let str = value.stringValue else {
throw DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Could not parse `BSONCode` from \"\(value)\", input must be a string."
)
}
self = BSONCode(code: str)
default:
return nil
}
}
/// Converts this `BSONCode` to a corresponding `JSON` in relaxed extendedJSON format.
internal func toRelaxedExtendedJSON() -> JSON {
self.toCanonicalExtendedJSON()
}
/// Converts this `BSONCode` to a corresponding `JSON` in canonical extendedJSON format.
internal func toCanonicalExtendedJSON() -> JSON {
["$code": JSON(.string(self.code))]
}
internal static var bsonType: BSONType { .code }
internal var bson: BSON { .code(self) }
internal static func read(from buffer: inout ByteBuffer) throws -> BSON {
guard let code = try String.read(from: &buffer).stringValue else {
throw BSONError.InternalError(message: "Cannot code")
}
return .code(BSONCode(code: code))
}
internal func write(to buffer: inout ByteBuffer) {
self.code.write(to: &buffer)
}
}
extension BSONCodeWithScope: BSONValue {
internal static let extJSONTypeWrapperKeys: [String] = ["$code", "$scope"]
/*
* Initializes a `BSONCode` from ExtendedJSON.
*
* Parameters:
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for `Code`.
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
* This is used for error messages.
*
* Returns:
* - `nil` if the provided value is not a `String`.
*
* Throws:
* - `DecodingError` if `json` is a partial match or is malformed.
*/
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
switch json.value {
case .object:
// canonical and relaxed extended JSON
guard let (code, scope) = try json.value.unwrapObject(withKeys: "$code", "$scope", keyPath: keyPath) else {
return nil
}
guard let codeStr = code.stringValue else {
throw DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Could not parse `BSONCodeWithScope` from \"code\": \"\(code)\"," +
" input must be a string."
)
}
guard let scopeDoc = try BSONDocument(fromExtJSON: JSON(scope), keyPath: keyPath + ["$scope"]) else {
throw DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Could not parse scope from \"\(scope)\", input must be a Document."
)
}
self = BSONCodeWithScope(code: codeStr, scope: scopeDoc)
default:
return nil
}
}
/// Converts this `BSONCodeWithScope` to a corresponding `JSON` in relaxed extendedJSON format.
internal func toRelaxedExtendedJSON() -> JSON {
["$code": JSON(.string(self.code)), "$scope": self.scope.toRelaxedExtendedJSON()]
}
/// Converts this `BSONCodeWithScope` to a corresponding `JSON` in canonical extendedJSON format.
internal func toCanonicalExtendedJSON() -> JSON {
["$code": JSON(.string(self.code)), "$scope": self.scope.toCanonicalExtendedJSON()]
}
internal static var bsonType: BSONType { .codeWithScope }
internal var bson: BSON { .codeWithScope(self) }
internal static func read(from buffer: inout ByteBuffer) throws -> BSON {
let reader = buffer.readerIndex
guard let size = buffer.readInteger(endianness: .little, as: Int32.self) else {
throw BSONError.InternalError(message: "Cannot code with scope size")
}
// 14 bytes minimum size =
// min 4 bytes size of CodeWScope
// min 4 bytes size of string + 1 null byte req by string
// min 5 bytes for document
guard size >= 14 else {
throw BSONError.InternalError(message: "Code with scope has size: \(size) but the minimum size is 14")
}
guard (size - 4) < buffer.readableBytes else {
throw BSONError.InternalError(message: "Code with scope has size: \(size) but there "
+ "are only \(buffer.readableBytes) bytes to read")
}
guard let code = try String.read(from: &buffer).stringValue else {
throw BSONError.InternalError(message: "Cannot read code")
}
guard let scope = try BSONDocument.read(from: &buffer).documentValue else {
throw BSONError.InternalError(message: "Cannot read scope document")
}
guard (buffer.readerIndex - reader) == size else {
throw BSONError.InternalError(
message: "Stated size: \(size) is not correct, actual size: \(buffer.readerIndex - reader)"
)
}
return .codeWithScope(BSONCodeWithScope(code: code, scope: scope))
}
internal func write(to buffer: inout ByteBuffer) {
let writer = buffer.writerIndex
buffer.writeInteger(0, endianness: .little, as: Int32.self) // reserve space
self.code.write(to: &buffer)
self.scope.write(to: &buffer)
buffer.setInteger(Int32(buffer.writerIndex - writer), at: writer, endianness: .little, as: Int32.self)
}
}