Skip to content

Commit

Permalink
TextFormat decoding options to skip unknown fields/extensions.
Browse files Browse the repository at this point in the history
Modeled after the upstream C++, provide two new decoding options to skip
unknown fields while decoding TextFormat.
  • Loading branch information
thomasvl committed Apr 29, 2024
1 parent 1881999 commit a7b6632
Show file tree
Hide file tree
Showing 8 changed files with 1,092 additions and 67 deletions.
5 changes: 5 additions & 0 deletions Sources/SwiftProtobuf/DoubleParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ import Foundation
internal class DoubleParser {
// Temporary buffer so we can null-terminate the UTF-8 string
// before calling the C standard library to parse it.
//
// In theory, JSON writers should be able to represent any IEEE Double
// in at most 25 bytes, but many writers will emit more digits than
// necessary, so we size this generously.
//
// This is also used for TextFormat, there the spec says decimal numbers
// can actually go over the limit can get mapped to [-]inifinity; so in
// theory this needs to be larger, but we haven't worried about it yet.
private var work =
UnsafeMutableBufferPointer<Int8>.allocate(capacity: 128)

Expand Down
55 changes: 35 additions & 20 deletions Sources/SwiftProtobuf/TextFormatDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ internal struct TextFormatDecoder: Decoder {
private var fieldNameMap: _NameMap?
private var messageType: any Message.Type

internal var options: TextFormatDecodingOptions {
return scanner.options
}

internal var complete: Bool {
mutating get {
return scanner.complete
Expand Down Expand Up @@ -63,27 +67,17 @@ internal struct TextFormatDecoder: Decoder {
}

mutating func nextFieldNumber() throws -> Int? {
// Per https://protobuf.dev/reference/protobuf/textformat-spec/#fields, every field can be
// followed by a field separator, so if we've seen a field, remove the separator before
// checking for the terminator.
if fieldCount > 0 {
scanner.skipOptionalSeparator()
}
if let terminator = terminator,
scanner.skipOptionalObjectEnd(terminator) {
return nil
}
if let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!, messageType: messageType) {
if let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!,
messageType: messageType,
terminator: terminator) {
fieldCount += 1
return fieldNumber
} else if terminator == nil {
return nil
} else {
// If this decoder is looking for at a terminator, then if the scanner failed to
// find a field number, something went wrong (end of stream).
throw TextFormatDecodingError.truncated
return nil
}

}

mutating func decodeSingularFloatField(value: inout Float) throws {
Expand Down Expand Up @@ -559,6 +553,7 @@ internal struct TextFormatDecoder: Decoder {
var keyField: KeyType.BaseType?
var valueField: ValueType.BaseType?
let terminator = try scanner.skipObjectStart()
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
Expand All @@ -568,14 +563,20 @@ internal struct TextFormatDecoder: Decoder {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try ValueType.decodeSingular(value: &valueField, from: &self)
default:
throw TextFormatDecodingError.unknownField
if ignoreExtensionFields && key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else {
throw TextFormatDecodingError.unknownField
}
}
scanner.skipOptionalSeparator()
} else {
Expand Down Expand Up @@ -608,6 +609,7 @@ internal struct TextFormatDecoder: Decoder {
var keyField: KeyType.BaseType?
var valueField: ValueType?
let terminator = try scanner.skipObjectStart()
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
Expand All @@ -617,14 +619,20 @@ internal struct TextFormatDecoder: Decoder {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try decodeSingularEnumField(value: &valueField)
default:
throw TextFormatDecodingError.unknownField
if ignoreExtensionFields && key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else {
throw TextFormatDecodingError.unknownField
}
}
scanner.skipOptionalSeparator()
} else {
Expand Down Expand Up @@ -657,6 +665,7 @@ internal struct TextFormatDecoder: Decoder {
var keyField: KeyType.BaseType?
var valueField: ValueType?
let terminator = try scanner.skipObjectStart()
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
Expand All @@ -666,14 +675,20 @@ internal struct TextFormatDecoder: Decoder {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try decodeSingularMessageField(value: &valueField)
default:
throw TextFormatDecodingError.unknownField
if ignoreExtensionFields && key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else {
throw TextFormatDecodingError.unknownField
}
}
scanner.skipOptionalSeparator()
} else {
Expand Down
14 changes: 14 additions & 0 deletions Sources/SwiftProtobuf/TextFormatDecodingOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,19 @@ public struct TextFormatDecodingOptions: Sendable {
/// while parsing.
public var messageDepthLimit: Int = 100

/// If unknown fields in the TextFormat should be ignored. If they aren't
/// ignored, an error will be raised if one is encountered.
///
/// Note: This is a lossy option, enabling it means part of the TextFormat
/// is silently skipped.
public var ignoreUnknownFields: Bool = false

/// If unknown extension fields in the TextFormat should be ignored. If they
/// aren't ignored, an error will be raised if one is encountered.
///
/// Note: This is a lossy option, enabling it means part of the TextFormat
/// is silently skipped.
public var ignoreUnknownExtensionFields: Bool = false

public init() {}
}
Loading

0 comments on commit a7b6632

Please sign in to comment.