/src/swift-protobuf/Sources/SwiftProtobuf/TextFormatEncodingVisitor.swift
Line | Count | Source |
1 | | // Sources/SwiftProtobuf/TextFormatEncodingVisitor.swift - Text format encoding support |
2 | | // |
3 | | // Copyright (c) 2014 - 2016 Apple Inc. and the project authors |
4 | | // Licensed under Apache License v2.0 with Runtime Library Exception |
5 | | // |
6 | | // See LICENSE.txt for license information: |
7 | | // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt |
8 | | // |
9 | | // ----------------------------------------------------------------------------- |
10 | | /// |
11 | | /// Text format serialization engine. |
12 | | /// |
13 | | // ----------------------------------------------------------------------------- |
14 | | |
15 | | #if canImport(FoundationEssentials) |
16 | | import FoundationEssentials |
17 | | #else |
18 | | import Foundation |
19 | | #endif |
20 | | |
21 | | private let mapNameResolver: [Int: StaticString] = [1: "key", 2: "value"] |
22 | | |
23 | | /// Visitor that serializes a message into protobuf text format. |
24 | | internal struct TextFormatEncodingVisitor: Visitor { |
25 | | |
26 | | private var encoder: TextFormatEncoder |
27 | | private var nameMap: _NameMap? |
28 | | private var nameResolver: [Int: StaticString] |
29 | | private var extensions: ExtensionFieldValueSet? |
30 | | private let options: TextFormatEncodingOptions |
31 | | |
32 | | /// The protobuf text produced by the visitor. |
33 | 15.2k | var result: String { |
34 | 15.2k | encoder.stringResult |
35 | 15.2k | } |
36 | | |
37 | | /// Creates a new visitor that serializes the given message to protobuf text |
38 | | /// format. |
39 | 15.2k | init(message: any Message, options: TextFormatEncodingOptions) { |
40 | 15.2k | let nameMap: _NameMap? |
41 | 15.2k | if let nameProviding = message as? (any _ProtoNameProviding) { |
42 | 15.2k | nameMap = type(of: nameProviding)._protobuf_nameMap |
43 | 15.2k | } else { |
44 | 0 | nameMap = nil |
45 | 0 | } |
46 | 15.2k | let extensions = (message as? (any ExtensibleMessage))?._protobuf_extensionFieldValues |
47 | 15.2k | |
48 | 15.2k | self.nameMap = nameMap |
49 | 15.2k | self.nameResolver = [:] |
50 | 15.2k | self.extensions = extensions |
51 | 15.2k | self.encoder = TextFormatEncoder() |
52 | 15.2k | self.options = options |
53 | 15.2k | } |
54 | | |
55 | | // TODO: This largely duplicates emitFieldName() below. |
56 | | // But, it's slower so we don't want to just have emitFieldName() use |
57 | | // formatFieldName(). Also, we need to measure whether the optimization |
58 | | // this provides to repeated fields is worth the effort; consider just |
59 | | // removing this and having repeated fields just re-run emitFieldName() |
60 | | // for each item. |
61 | 72.5k | private func formatFieldName(lookingUp fieldNumber: Int) -> [UInt8] { |
62 | 72.5k | var bytes = [UInt8]() |
63 | 72.5k | if let protoName = nameMap?.names(for: fieldNumber)?.proto { |
64 | 58.8k | bytes.append(contentsOf: protoName.utf8Buffer) |
65 | 58.8k | } else if let protoName = nameResolver[fieldNumber] { |
66 | 0 | let buff = UnsafeBufferPointer(start: protoName.utf8Start, count: protoName.utf8CodeUnitCount) |
67 | 0 | bytes.append(contentsOf: buff) |
68 | 2.84k | } else if let extensionName = extensions?[fieldNumber]?.protobufExtension.fieldName { |
69 | 2.84k | bytes.append(UInt8(ascii: "[")) |
70 | 2.84k | bytes.append(contentsOf: extensionName.utf8) |
71 | 2.84k | bytes.append(UInt8(ascii: "]")) |
72 | 10.8k | } else { |
73 | 10.8k | bytes.append(contentsOf: fieldNumber.description.utf8) |
74 | 10.8k | } |
75 | 72.5k | return bytes |
76 | 72.5k | } |
77 | | |
78 | 1.55M | private mutating func emitFieldName(lookingUp fieldNumber: Int) { |
79 | 1.55M | if let protoName = nameMap?.names(for: fieldNumber)?.proto { |
80 | 1.15M | encoder.emitFieldName(name: protoName.utf8Buffer) |
81 | 1.15M | } else if let protoName = nameResolver[fieldNumber] { |
82 | 371k | encoder.emitFieldName(name: protoName) |
83 | 371k | } else if let extensionName = extensions?[fieldNumber]?.protobufExtension.fieldName { |
84 | 7.76k | encoder.emitExtensionFieldName(name: extensionName) |
85 | 18.0k | } else { |
86 | 18.0k | encoder.emitFieldNumber(number: fieldNumber) |
87 | 18.0k | } |
88 | 1.55M | } |
89 | | |
90 | 194k | mutating func visitUnknown(bytes: Data) throws { |
91 | 194k | if options.printUnknownFields { |
92 | 193k | try bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) -> Void in |
93 | 193k | if let baseAddress = body.baseAddress, body.count > 0 { |
94 | 193k | // All fields will be directly handled, so there is no need for |
95 | 193k | // the unknown field buffering/collection (when scannings to see |
96 | 193k | // if something is a message, this would be extremely wasteful). |
97 | 193k | var binaryOptions = BinaryDecodingOptions() |
98 | 193k | binaryOptions.discardUnknownFields = true |
99 | 193k | var decoder = BinaryDecoder( |
100 | 193k | forReadingFrom: baseAddress, |
101 | 193k | count: body.count, |
102 | 193k | options: binaryOptions |
103 | 193k | ) |
104 | 193k | try visitUnknown(decoder: &decoder) |
105 | 193k | } |
106 | 193k | } |
107 | 194k | } |
108 | 194k | } |
109 | | |
110 | | /// Helper for printing out unknowns. |
111 | | /// |
112 | | /// The implementation tries to be "helpful" and if a length delimited field |
113 | | /// appears to be a submessage, it prints it as such. However, that opens the |
114 | | /// door to someone sending a message with an unknown field that is a stack |
115 | | /// bomb, i.e. - it causes this code to recurse, exhausting the stack and |
116 | | /// thus opening up an attack vector. To keep this "help", but avoid the |
117 | | /// attack, a limit is placed on how many times it will recurse before just |
118 | | /// treating the length delimited fields as bytes and not trying to decode |
119 | | /// them. |
120 | | private mutating func visitUnknown( |
121 | | decoder: inout BinaryDecoder, |
122 | | recursionBudget: Int = 10 |
123 | 2.53M | ) throws { |
124 | 2.53M | // This stack serves to avoid recursion for groups within groups within |
125 | 2.53M | // groups..., this avoid the stack attack that the message detection |
126 | 2.53M | // hits. No limit is placed on this because there is no stack risk with |
127 | 2.53M | // recursion, and because if a limit was hit, there is no other way to |
128 | 2.53M | // encode the group (the message field can just print as length |
129 | 2.53M | // delimited, groups don't have an option like that). |
130 | 2.53M | var groupFieldNumberStack: [Int] = [] |
131 | 2.53M | |
132 | 62.7M | while let tag = try decoder.getTag() { |
133 | 62.7M | switch tag.wireFormat { |
134 | 62.7M | case .varint: |
135 | 13.3M | encoder.emitFieldNumber(number: tag.fieldNumber) |
136 | 13.3M | var value: UInt64 = 0 |
137 | 13.3M | encoder.startRegularField() |
138 | 13.3M | try decoder.decodeSingularUInt64Field(value: &value) |
139 | 13.3M | encoder.putUInt64(value: value) |
140 | 13.3M | encoder.endRegularField() |
141 | 62.7M | case .fixed64: |
142 | 515k | encoder.emitFieldNumber(number: tag.fieldNumber) |
143 | 515k | var value: UInt64 = 0 |
144 | 515k | encoder.startRegularField() |
145 | 515k | try decoder.decodeSingularFixed64Field(value: &value) |
146 | 515k | encoder.putUInt64Hex(value: value, digits: 16) |
147 | 515k | encoder.endRegularField() |
148 | 62.7M | case .lengthDelimited: |
149 | 3.75M | encoder.emitFieldNumber(number: tag.fieldNumber) |
150 | 3.75M | var bytes = Data() |
151 | 3.75M | try decoder.decodeSingularBytesField(value: &bytes) |
152 | 3.75M | var encodeAsBytes = true |
153 | 3.75M | if bytes.count > 0 && recursionBudget > 0 { |
154 | 2.38M | bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) -> Void in |
155 | 2.38M | if let baseAddress = body.baseAddress, body.count > 0 { |
156 | 2.38M | do { |
157 | 2.38M | // Walk all the fields to test if it looks like a message |
158 | 2.38M | var testDecoder = BinaryDecoder( |
159 | 2.38M | forReadingFrom: baseAddress, |
160 | 2.38M | count: body.count, |
161 | 2.38M | parent: decoder |
162 | 2.38M | ) |
163 | 4.75M | while let _ = try testDecoder.nextFieldNumber() { |
164 | 4.75M | } |
165 | 2.34M | // No error? Output the message body. |
166 | 2.34M | encodeAsBytes = false |
167 | 2.34M | var subDecoder = BinaryDecoder( |
168 | 2.34M | forReadingFrom: baseAddress, |
169 | 2.34M | count: bytes.count, |
170 | 2.34M | parent: decoder |
171 | 2.34M | ) |
172 | 2.34M | encoder.startMessageField() |
173 | 2.34M | try visitUnknown( |
174 | 2.34M | decoder: &subDecoder, |
175 | 2.34M | recursionBudget: recursionBudget - 1 |
176 | 2.34M | ) |
177 | 2.34M | encoder.endMessageField() |
178 | 2.34M | } catch { |
179 | 38.3k | encodeAsBytes = true |
180 | 2.38M | } |
181 | 2.38M | } |
182 | 2.38M | } |
183 | 2.38M | } |
184 | 3.75M | if encodeAsBytes { |
185 | 1.41M | encoder.startRegularField() |
186 | 1.41M | encoder.putBytesValue(value: bytes) |
187 | 1.41M | encoder.endRegularField() |
188 | 1.41M | } |
189 | 62.7M | case .startGroup: |
190 | 22.2M | encoder.emitFieldNumber(number: tag.fieldNumber) |
191 | 22.2M | encoder.startMessageField() |
192 | 22.2M | groupFieldNumberStack.append(tag.fieldNumber) |
193 | 62.7M | case .endGroup: |
194 | 22.2M | let groupFieldNumber = groupFieldNumberStack.popLast() |
195 | 22.2M | // Unknown data is scanned and verified by the |
196 | 22.2M | // binary parser, so this can never fail. |
197 | 22.2M | assert(tag.fieldNumber == groupFieldNumber) |
198 | 22.2M | encoder.endMessageField() |
199 | 62.7M | case .fixed32: |
200 | 708k | encoder.emitFieldNumber(number: tag.fieldNumber) |
201 | 708k | var value: UInt32 = 0 |
202 | 708k | encoder.startRegularField() |
203 | 708k | try decoder.decodeSingularFixed32Field(value: &value) |
204 | 708k | encoder.putUInt64Hex(value: UInt64(value), digits: 8) |
205 | 708k | encoder.endRegularField() |
206 | 62.7M | } |
207 | 62.7M | } |
208 | 2.53M | |
209 | 2.53M | // Unknown data is scanned and verified by the binary parser, so this can |
210 | 2.53M | // never fail. |
211 | 2.53M | assert(groupFieldNumberStack.isEmpty) |
212 | 2.53M | } |
213 | | |
214 | | // Visitor.swift defines default versions for other singular field types |
215 | | // that simply widen and dispatch to one of the following. Since Text format |
216 | | // does not distinguish e.g., Fixed64 vs. UInt64, this is sufficient. |
217 | | |
218 | 17.9k | mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws { |
219 | 17.9k | emitFieldName(lookingUp: fieldNumber) |
220 | 17.9k | encoder.startRegularField() |
221 | 17.9k | encoder.putFloatValue(value: value) |
222 | 17.9k | encoder.endRegularField() |
223 | 17.9k | } |
224 | | |
225 | 237k | mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws { |
226 | 237k | emitFieldName(lookingUp: fieldNumber) |
227 | 237k | encoder.startRegularField() |
228 | 237k | encoder.putDoubleValue(value: value) |
229 | 237k | encoder.endRegularField() |
230 | 237k | } |
231 | | |
232 | 977k | mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws { |
233 | 977k | emitFieldName(lookingUp: fieldNumber) |
234 | 977k | encoder.startRegularField() |
235 | 977k | encoder.putInt64(value: value) |
236 | 977k | encoder.endRegularField() |
237 | 977k | } |
238 | | |
239 | 403k | mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws { |
240 | 403k | emitFieldName(lookingUp: fieldNumber) |
241 | 403k | encoder.startRegularField() |
242 | 403k | encoder.putUInt64(value: value) |
243 | 403k | encoder.endRegularField() |
244 | 403k | } |
245 | | |
246 | 9.09k | mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws { |
247 | 9.09k | emitFieldName(lookingUp: fieldNumber) |
248 | 9.09k | encoder.startRegularField() |
249 | 9.09k | encoder.putBoolValue(value: value) |
250 | 9.09k | encoder.endRegularField() |
251 | 9.09k | } |
252 | | |
253 | 509k | mutating func visitSingularStringField(value: String, fieldNumber: Int) throws { |
254 | 509k | emitFieldName(lookingUp: fieldNumber) |
255 | 509k | encoder.startRegularField() |
256 | 509k | encoder.putStringValue(value: value) |
257 | 509k | encoder.endRegularField() |
258 | 509k | } |
259 | | |
260 | 399k | mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws { |
261 | 399k | emitFieldName(lookingUp: fieldNumber) |
262 | 399k | encoder.startRegularField() |
263 | 399k | encoder.putBytesValue(value: value) |
264 | 399k | encoder.endRegularField() |
265 | 399k | } |
266 | | |
267 | 51.3k | mutating func visitSingularEnumField<E: Enum>(value: E, fieldNumber: Int) throws { |
268 | 51.3k | emitFieldName(lookingUp: fieldNumber) |
269 | 51.3k | encoder.startRegularField() |
270 | 51.3k | encoder.putEnumValue(value: value) |
271 | 51.3k | encoder.endRegularField() |
272 | 51.3k | } |
273 | | |
274 | | mutating func visitSingularMessageField<M: Message>( |
275 | | value: M, |
276 | | fieldNumber: Int |
277 | 390k | ) throws { |
278 | 390k | emitFieldName(lookingUp: fieldNumber) |
279 | 390k | |
280 | 390k | // Cache old visitor configuration |
281 | 390k | let oldNameMap = self.nameMap |
282 | 390k | let oldNameResolver = self.nameResolver |
283 | 390k | let oldExtensions = self.extensions |
284 | 390k | // Update configuration for new message |
285 | 390k | self.nameMap = (M.self as? any _ProtoNameProviding.Type)?._protobuf_nameMap |
286 | 390k | self.nameResolver = [:] |
287 | 390k | self.extensions = (value as? (any ExtensibleMessage))?._protobuf_extensionFieldValues |
288 | 390k | // Encode submessage |
289 | 390k | encoder.startMessageField() |
290 | 390k | if let any = value as? Google_Protobuf_Any { |
291 | 320k | any.textTraverse(visitor: &self) |
292 | 320k | } else { |
293 | 70.2k | try! value.traverse(visitor: &self) |
294 | 390k | } |
295 | 390k | encoder.endMessageField() |
296 | 390k | // Restore configuration before returning |
297 | 390k | self.extensions = oldExtensions |
298 | 390k | self.nameResolver = oldNameResolver |
299 | 390k | self.nameMap = oldNameMap |
300 | 390k | } |
301 | | |
302 | | // Emit the full "verbose" form of an Any. This writes the typeURL |
303 | | // as a field name in `[...]` followed by the fields of the |
304 | | // contained message. |
305 | 194k | internal mutating func visitAnyVerbose(value: any Message, typeURL: String) { |
306 | 194k | encoder.emitExtensionFieldName(name: typeURL) |
307 | 194k | encoder.startMessageField() |
308 | 194k | |
309 | 194k | // Cache old visitor configuration |
310 | 194k | let oldNameMap = self.nameMap |
311 | 194k | let oldNameResolver = self.nameResolver |
312 | 194k | let oldExtensions = self.extensions |
313 | 194k | // Update configuration for new message |
314 | 194k | self.nameMap = (type(of: value) as? any _ProtoNameProviding.Type)?._protobuf_nameMap |
315 | 194k | self.nameResolver = [:] |
316 | 194k | self.extensions = (value as? (any ExtensibleMessage))?._protobuf_extensionFieldValues |
317 | 194k | |
318 | 194k | if let any = value as? Google_Protobuf_Any { |
319 | 4.03k | any.textTraverse(visitor: &self) |
320 | 190k | } else { |
321 | 190k | try! value.traverse(visitor: &self) |
322 | 194k | } |
323 | 194k | |
324 | 194k | // Restore configuration before returning |
325 | 194k | self.extensions = oldExtensions |
326 | 194k | self.nameResolver = oldNameResolver |
327 | 194k | self.nameMap = oldNameMap |
328 | 194k | |
329 | 194k | encoder.endMessageField() |
330 | 194k | } |
331 | | |
332 | | // Write a single special field called "#json". This |
333 | | // is used for Any objects with undecoded JSON contents. |
334 | 0 | internal mutating func visitAnyJSONBytesField(value: Data) { |
335 | 0 | encoder.indent() |
336 | 0 | encoder.append(staticText: "#json: ") |
337 | 0 | encoder.putBytesValue(value: value) |
338 | 0 | encoder.append(staticText: "\n") |
339 | 0 | } |
340 | | |
341 | | // The default implementations in Visitor.swift provide the correct |
342 | | // results, but we get significantly better performance by only doing |
343 | | // the name lookup once for the array, rather than once for each element: |
344 | | |
345 | 2.05k | mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws { |
346 | 2.05k | assert(!value.isEmpty) |
347 | 2.05k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
348 | 16.7k | for v in value { |
349 | 16.7k | encoder.emitFieldName(name: fieldName) |
350 | 16.7k | encoder.startRegularField() |
351 | 16.7k | encoder.putFloatValue(value: v) |
352 | 16.7k | encoder.endRegularField() |
353 | 16.7k | } |
354 | 2.05k | } |
355 | | |
356 | 1.93k | mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws { |
357 | 1.93k | assert(!value.isEmpty) |
358 | 1.93k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
359 | 4.00k | for v in value { |
360 | 4.00k | encoder.emitFieldName(name: fieldName) |
361 | 4.00k | encoder.startRegularField() |
362 | 4.00k | encoder.putDoubleValue(value: v) |
363 | 4.00k | encoder.endRegularField() |
364 | 4.00k | } |
365 | 1.93k | } |
366 | | |
367 | 5.98k | mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws { |
368 | 5.98k | assert(!value.isEmpty) |
369 | 5.98k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
370 | 8.61k | for v in value { |
371 | 8.61k | encoder.emitFieldName(name: fieldName) |
372 | 8.61k | encoder.startRegularField() |
373 | 8.61k | encoder.putInt64(value: Int64(v)) |
374 | 8.61k | encoder.endRegularField() |
375 | 8.61k | } |
376 | 5.98k | } |
377 | | |
378 | 8.95k | mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws { |
379 | 8.95k | assert(!value.isEmpty) |
380 | 8.95k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
381 | 32.2k | for v in value { |
382 | 32.2k | encoder.emitFieldName(name: fieldName) |
383 | 32.2k | encoder.startRegularField() |
384 | 32.2k | encoder.putInt64(value: v) |
385 | 32.2k | encoder.endRegularField() |
386 | 32.2k | } |
387 | 8.95k | } |
388 | | |
389 | 3.75k | mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws { |
390 | 3.75k | assert(!value.isEmpty) |
391 | 3.75k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
392 | 103k | for v in value { |
393 | 103k | encoder.emitFieldName(name: fieldName) |
394 | 103k | encoder.startRegularField() |
395 | 103k | encoder.putUInt64(value: UInt64(v)) |
396 | 103k | encoder.endRegularField() |
397 | 103k | } |
398 | 3.75k | } |
399 | | |
400 | 2.28k | mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws { |
401 | 2.28k | assert(!value.isEmpty) |
402 | 2.28k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
403 | 3.89k | for v in value { |
404 | 3.89k | encoder.emitFieldName(name: fieldName) |
405 | 3.89k | encoder.startRegularField() |
406 | 3.89k | encoder.putUInt64(value: v) |
407 | 3.89k | encoder.endRegularField() |
408 | 3.89k | } |
409 | 2.28k | } |
410 | | |
411 | 1.05k | mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws { |
412 | 1.05k | try visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber) |
413 | 1.05k | } |
414 | 797 | mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws { |
415 | 797 | try visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber) |
416 | 797 | } |
417 | 3.20k | mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws { |
418 | 3.20k | try visitRepeatedUInt32Field(value: value, fieldNumber: fieldNumber) |
419 | 3.20k | } |
420 | 1.03k | mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws { |
421 | 1.03k | try visitRepeatedUInt64Field(value: value, fieldNumber: fieldNumber) |
422 | 1.03k | } |
423 | 1.82k | mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws { |
424 | 1.82k | try visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber) |
425 | 1.82k | } |
426 | 2.52k | mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws { |
427 | 2.52k | try visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber) |
428 | 2.52k | } |
429 | | |
430 | 7.41k | mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws { |
431 | 7.41k | assert(!value.isEmpty) |
432 | 7.41k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
433 | 29.0k | for v in value { |
434 | 29.0k | encoder.emitFieldName(name: fieldName) |
435 | 29.0k | encoder.startRegularField() |
436 | 29.0k | encoder.putBoolValue(value: v) |
437 | 29.0k | encoder.endRegularField() |
438 | 29.0k | } |
439 | 7.41k | } |
440 | | |
441 | 1.26k | mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws { |
442 | 1.26k | assert(!value.isEmpty) |
443 | 1.26k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
444 | 10.5k | for v in value { |
445 | 10.5k | encoder.emitFieldName(name: fieldName) |
446 | 10.5k | encoder.startRegularField() |
447 | 10.5k | encoder.putStringValue(value: v) |
448 | 10.5k | encoder.endRegularField() |
449 | 10.5k | } |
450 | 1.26k | } |
451 | | |
452 | 1.88k | mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws { |
453 | 1.88k | assert(!value.isEmpty) |
454 | 1.88k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
455 | 463k | for v in value { |
456 | 463k | encoder.emitFieldName(name: fieldName) |
457 | 463k | encoder.startRegularField() |
458 | 463k | encoder.putBytesValue(value: v) |
459 | 463k | encoder.endRegularField() |
460 | 463k | } |
461 | 1.88k | } |
462 | | |
463 | 4.13k | mutating func visitRepeatedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws { |
464 | 4.13k | assert(!value.isEmpty) |
465 | 4.13k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
466 | 26.5k | for v in value { |
467 | 26.5k | encoder.emitFieldName(name: fieldName) |
468 | 26.5k | encoder.startRegularField() |
469 | 26.5k | encoder.putEnumValue(value: v) |
470 | 26.5k | encoder.endRegularField() |
471 | 26.5k | } |
472 | 4.13k | } |
473 | | |
474 | | // Messages and groups |
475 | | mutating func visitRepeatedMessageField<M: Message>( |
476 | | value: [M], |
477 | | fieldNumber: Int |
478 | 32.8k | ) throws { |
479 | 32.8k | assert(!value.isEmpty) |
480 | 32.8k | // Look up field name against outer message encoding state |
481 | 32.8k | let fieldName = formatFieldName(lookingUp: fieldNumber) |
482 | 32.8k | // Cache old visitor state |
483 | 32.8k | let oldNameMap = self.nameMap |
484 | 32.8k | let oldNameResolver = self.nameResolver |
485 | 32.8k | let oldExtensions = self.extensions |
486 | 32.8k | // Update encoding state for new message type |
487 | 32.8k | self.nameMap = (M.self as? any _ProtoNameProviding.Type)?._protobuf_nameMap |
488 | 32.8k | self.nameResolver = [:] |
489 | 32.8k | self.extensions = (value as? (any ExtensibleMessage))?._protobuf_extensionFieldValues |
490 | 32.8k | // Iterate and encode each message |
491 | 577k | for v in value { |
492 | 577k | encoder.emitFieldName(name: fieldName) |
493 | 577k | encoder.startMessageField() |
494 | 577k | if let any = v as? Google_Protobuf_Any { |
495 | 0 | any.textTraverse(visitor: &self) |
496 | 577k | } else { |
497 | 577k | try! v.traverse(visitor: &self) |
498 | 577k | } |
499 | 577k | encoder.endMessageField() |
500 | 577k | } |
501 | 32.8k | // Restore state |
502 | 32.8k | self.extensions = oldExtensions |
503 | 32.8k | self.nameResolver = oldNameResolver |
504 | 32.8k | self.nameMap = oldNameMap |
505 | 32.8k | } |
506 | | |
507 | | // Google's C++ implementation of Text format supports two formats |
508 | | // for repeated numeric fields: "short" format writes the list as a |
509 | | // single field with values enclosed in `[...]`, "long" format |
510 | | // writes a separate field name/value for each item. They provide |
511 | | // an option for callers to select which output version they prefer. |
512 | | |
513 | | // Since this distinction mirrors the difference in Protobuf Binary |
514 | | // between "packed" and "non-packed", I've chosen to use the short |
515 | | // format for packed fields and the long version for repeated |
516 | | // fields. This provides a clear visual distinction between these |
517 | | // fields (including proto3's default use of packed) without |
518 | | // introducing the baggage of a separate option. |
519 | | |
520 | | private mutating func iterateAndEncode<T>( |
521 | | packedValue: [T], |
522 | | fieldNumber: Int, |
523 | | encode: (T, inout TextFormatEncoder) -> Void |
524 | 87.0k | ) throws { |
525 | 87.0k | assert(!packedValue.isEmpty) |
526 | 87.0k | emitFieldName(lookingUp: fieldNumber) |
527 | 87.0k | encoder.startRegularField() |
528 | 87.0k | var firstItem = true |
529 | 87.0k | encoder.startArray() |
530 | 547k | for v in packedValue { |
531 | 547k | if !firstItem { |
532 | 460k | encoder.arraySeparator() |
533 | 460k | } |
534 | 547k | encode(v, &encoder) |
535 | 547k | firstItem = false |
536 | 547k | } |
537 | 87.0k | encoder.endArray() |
538 | 87.0k | encoder.endRegularField() |
539 | 87.0k | } |
540 | | |
541 | 3.92k | mutating func visitPackedFloatField(value: [Float], fieldNumber: Int) throws { |
542 | 5.69k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
543 | 5.69k | (v: Float, encoder: inout TextFormatEncoder) in |
544 | 5.69k | encoder.putFloatValue(value: v) |
545 | 5.69k | } |
546 | 3.92k | } |
547 | | |
548 | 6.97k | mutating func visitPackedDoubleField(value: [Double], fieldNumber: Int) throws { |
549 | 111k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
550 | 111k | (v: Double, encoder: inout TextFormatEncoder) in |
551 | 111k | encoder.putDoubleValue(value: v) |
552 | 111k | } |
553 | 6.97k | } |
554 | | |
555 | 42.3k | mutating func visitPackedInt32Field(value: [Int32], fieldNumber: Int) throws { |
556 | 77.8k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
557 | 77.8k | (v: Int32, encoder: inout TextFormatEncoder) in |
558 | 77.8k | encoder.putInt64(value: Int64(v)) |
559 | 77.8k | } |
560 | 42.3k | } |
561 | | |
562 | 9.15k | mutating func visitPackedInt64Field(value: [Int64], fieldNumber: Int) throws { |
563 | 27.7k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
564 | 27.7k | (v: Int64, encoder: inout TextFormatEncoder) in |
565 | 27.7k | encoder.putInt64(value: v) |
566 | 27.7k | } |
567 | 9.15k | } |
568 | | |
569 | 11.2k | mutating func visitPackedUInt32Field(value: [UInt32], fieldNumber: Int) throws { |
570 | 225k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
571 | 225k | (v: UInt32, encoder: inout TextFormatEncoder) in |
572 | 225k | encoder.putUInt64(value: UInt64(v)) |
573 | 225k | } |
574 | 11.2k | } |
575 | | |
576 | 3.93k | mutating func visitPackedUInt64Field(value: [UInt64], fieldNumber: Int) throws { |
577 | 19.3k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
578 | 19.3k | (v: UInt64, encoder: inout TextFormatEncoder) in |
579 | 19.3k | encoder.putUInt64(value: v) |
580 | 19.3k | } |
581 | 3.93k | } |
582 | | |
583 | 35.7k | mutating func visitPackedSInt32Field(value: [Int32], fieldNumber: Int) throws { |
584 | 35.7k | try visitPackedInt32Field(value: value, fieldNumber: fieldNumber) |
585 | 35.7k | } |
586 | | |
587 | 2.55k | mutating func visitPackedSInt64Field(value: [Int64], fieldNumber: Int) throws { |
588 | 2.55k | try visitPackedInt64Field(value: value, fieldNumber: fieldNumber) |
589 | 2.55k | } |
590 | | |
591 | 2.43k | mutating func visitPackedFixed32Field(value: [UInt32], fieldNumber: Int) throws { |
592 | 2.43k | try visitPackedUInt32Field(value: value, fieldNumber: fieldNumber) |
593 | 2.43k | } |
594 | | |
595 | 1.36k | mutating func visitPackedFixed64Field(value: [UInt64], fieldNumber: Int) throws { |
596 | 1.36k | try visitPackedUInt64Field(value: value, fieldNumber: fieldNumber) |
597 | 1.36k | } |
598 | | |
599 | 2.31k | mutating func visitPackedSFixed32Field(value: [Int32], fieldNumber: Int) throws { |
600 | 2.31k | try visitPackedInt32Field(value: value, fieldNumber: fieldNumber) |
601 | 2.31k | } |
602 | | |
603 | 2.73k | mutating func visitPackedSFixed64Field(value: [Int64], fieldNumber: Int) throws { |
604 | 2.73k | try visitPackedInt64Field(value: value, fieldNumber: fieldNumber) |
605 | 2.73k | } |
606 | | |
607 | 7.55k | mutating func visitPackedBoolField(value: [Bool], fieldNumber: Int) throws { |
608 | 77.2k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
609 | 77.2k | (v: Bool, encoder: inout TextFormatEncoder) in |
610 | 77.2k | encoder.putBoolValue(value: v) |
611 | 77.2k | } |
612 | 7.55k | } |
613 | | |
614 | 1.87k | mutating func visitPackedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws { |
615 | 3.19k | try iterateAndEncode(packedValue: value, fieldNumber: fieldNumber) { |
616 | 3.19k | (v: E, encoder: inout TextFormatEncoder) in |
617 | 3.19k | encoder.putEnumValue(value: v) |
618 | 3.19k | } |
619 | 1.87k | } |
620 | | |
621 | | /// Helper to encapsulate the common structure of iterating over a map |
622 | | /// and encoding the keys and values. |
623 | | private mutating func iterateAndEncode<K, V>( |
624 | | map: [K: V], |
625 | | fieldNumber: Int, |
626 | | isOrderedBefore: (K, K) -> Bool, |
627 | | encode: (inout TextFormatEncodingVisitor, K, V) throws -> Void |
628 | 170k | ) throws { |
629 | 170k | // Cache old visitor configuration |
630 | 170k | let oldNameMap = self.nameMap |
631 | 170k | let oldNameResolver = self.nameResolver |
632 | 170k | let oldExtensions = self.extensions |
633 | 170k | |
634 | 185k | for (k, v) in map.sorted(by: { isOrderedBefore($0.0, $1.0) }) { |
635 | 185k | emitFieldName(lookingUp: fieldNumber) |
636 | 185k | encoder.startMessageField() |
637 | 185k | |
638 | 185k | // Update visitor configuration for map |
639 | 185k | self.nameMap = nil |
640 | 185k | self.nameResolver = mapNameResolver |
641 | 185k | self.extensions = nil |
642 | 185k | |
643 | 185k | try encode(&self, k, v) |
644 | 185k | |
645 | 185k | // Restore configuration before resuming containing message |
646 | 185k | self.extensions = oldExtensions |
647 | 185k | self.nameResolver = oldNameResolver |
648 | 185k | self.nameMap = oldNameMap |
649 | 185k | |
650 | 185k | encoder.endMessageField() |
651 | 185k | } |
652 | 170k | } |
653 | | |
654 | | mutating func visitMapField<KeyType, ValueType: MapValueType>( |
655 | | fieldType: _ProtobufMap<KeyType, ValueType>.Type, |
656 | | value: _ProtobufMap<KeyType, ValueType>.BaseType, |
657 | | fieldNumber: Int |
658 | 125k | ) throws { |
659 | 135k | try iterateAndEncode(map: value, fieldNumber: fieldNumber, isOrderedBefore: KeyType._lessThan) { |
660 | 135k | (visitor: inout TextFormatEncodingVisitor, key, value) throws -> Void in |
661 | 135k | try KeyType.visitSingular(value: key, fieldNumber: 1, with: &visitor) |
662 | 135k | try ValueType.visitSingular(value: value, fieldNumber: 2, with: &visitor) |
663 | 135k | } |
664 | 125k | } |
665 | | |
666 | | mutating func visitMapField<KeyType, ValueType>( |
667 | | fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type, |
668 | | value: _ProtobufEnumMap<KeyType, ValueType>.BaseType, |
669 | | fieldNumber: Int |
670 | 41.8k | ) throws where ValueType.RawValue == Int { |
671 | 45.9k | try iterateAndEncode(map: value, fieldNumber: fieldNumber, isOrderedBefore: KeyType._lessThan) { |
672 | 45.9k | (visitor: inout TextFormatEncodingVisitor, key, value) throws -> Void in |
673 | 45.9k | try KeyType.visitSingular(value: key, fieldNumber: 1, with: &visitor) |
674 | 45.9k | try visitor.visitSingularEnumField(value: value, fieldNumber: 2) |
675 | 45.9k | } |
676 | 41.8k | } |
677 | | |
678 | | mutating func visitMapField<KeyType, ValueType>( |
679 | | fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type, |
680 | | value: _ProtobufMessageMap<KeyType, ValueType>.BaseType, |
681 | | fieldNumber: Int |
682 | 3.43k | ) throws { |
683 | 4.24k | try iterateAndEncode(map: value, fieldNumber: fieldNumber, isOrderedBefore: KeyType._lessThan) { |
684 | 4.24k | (visitor: inout TextFormatEncodingVisitor, key, value) throws -> Void in |
685 | 4.24k | try KeyType.visitSingular(value: key, fieldNumber: 1, with: &visitor) |
686 | 4.24k | try visitor.visitSingularMessageField(value: value, fieldNumber: 2) |
687 | 4.24k | } |
688 | 3.43k | } |
689 | | } |