diff --git a/Sources/SwiftProtobuf/CMakeLists.txt b/Sources/SwiftProtobuf/CMakeLists.txt index 74816c320..fd7fb867e 100644 --- a/Sources/SwiftProtobuf/CMakeLists.txt +++ b/Sources/SwiftProtobuf/CMakeLists.txt @@ -50,12 +50,15 @@ add_library(SwiftProtobuf MathUtils.swift Message+AnyAdditions.swift Message+BinaryAdditions.swift + Message+FieldMask.swift Message+JSONAdditions.swift Message+JSONArrayAdditions.swift Message+TextFormatAdditions.swift Message.swift MessageExtension.swift NameMap.swift + PathDecoder.swift + PathVisitor.swift ProtobufAPIVersionCheck.swift ProtobufMap.swift ProtoNameProviding.swift diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 1d1e7f73c..87cbdd65f 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -13,11 +13,6 @@ /// // ----------------------------------------------------------------------------- -// TODO: We should have utilities to apply a fieldmask to an arbitrary -// message, intersect two fieldmasks, etc. -// Google's C++ implementation does this by having utilities -// to build a tree of field paths that can be easily intersected, -// unioned, traversed to apply to submessages, etc. // True if the string only contains printable (non-control) // ASCII characters. Note: This follows the ASCII standard; @@ -184,3 +179,190 @@ extension Google_Protobuf_FieldMask: _CustomJSONCodable { return "\"" + jsonPaths.joined(separator: ",") + "\"" } } + +extension Google_Protobuf_FieldMask { + + /// Initiates a field mask with all fields of the message type. + /// + /// - Parameter messageType: Message type to get all paths from. + public init( + allFieldsOf messageType: M.Type + ) { + self = .with { mask in + mask.paths = M.allProtoNames + } + } + + /// Initiates a field mask from some particular field numbers of a message + /// + /// - Parameters: + /// - messageType: Message type to get all paths from. + /// - fieldNumbers: Field numbers of paths to be included. + /// - Returns: Field mask that include paths of corresponding field numbers. + /// - Throws: `FieldMaskError.invalidFieldNumber` if the field number + /// is not on the message + public init( + fieldNumbers: [Int], + of messageType: M.Type + ) throws { + var paths: [String] = [] + for number in fieldNumbers { + guard let name = M.protoName(for: number) else { + throw FieldMaskError.invalidFieldNumber + } + paths.append(name) + } + self = .with { mask in + mask.paths = paths + } + } +} + +extension Google_Protobuf_FieldMask { + + /// Adds a path to FieldMask after checking whether the given path is valid. + /// This method check-fails if the path is not a valid path for Message type. + /// + /// - Parameters: + /// - path: Path to be added to FieldMask. + /// - messageType: Message type to check validity. + public mutating func addPath( + _ path: String, + of messageType: M.Type + ) throws { + guard M.isPathValid(path) else { + throw FieldMaskError.invalidPath + } + paths.append(path) + } + + /// Converts a FieldMask to the canonical form. It will: + /// 1. Remove paths that are covered by another path. For example, + /// "foo.bar" is covered by "foo" and will be removed if "foo" + /// is also in the FieldMask. + /// 2. Sort all paths in alphabetical order. + public var canonical: Google_Protobuf_FieldMask { + var mask = Google_Protobuf_FieldMask() + let sortedPaths = self.paths.sorted() + for path in sortedPaths { + if let lastPath = mask.paths.last { + if path != lastPath, !path.hasPrefix("\(lastPath).") { + mask.paths.append(path) + } + } else { + mask.paths.append(path) + } + } + return mask + } + + /// Creates an union of two FieldMasks. + /// + /// - Parameter mask: FieldMask to union with. + /// - Returns: FieldMask with union of two path sets. + public func union( + _ mask: Google_Protobuf_FieldMask + ) -> Google_Protobuf_FieldMask { + var buffer: Set = .init() + var paths: [String] = [] + let allPaths = self.paths + mask.paths + for path in allPaths where !buffer.contains(path) { + buffer.insert(path) + paths.append(path) + } + return .with { mask in + mask.paths = paths + } + } + + /// Creates an intersection of two FieldMasks. + /// + /// - Parameter mask: FieldMask to intersect with. + /// - Returns: FieldMask with intersection of two path sets. + public func intersect( + _ mask: Google_Protobuf_FieldMask + ) -> Google_Protobuf_FieldMask { + let set = Set(mask.paths) + var paths: [String] = [] + var buffer = Set() + for path in self.paths where set.contains(path) && !buffer.contains(path) { + buffer.insert(path) + paths.append(path) + } + return .with { mask in + mask.paths = paths + } + } + + /// Creates a FieldMasks with paths of the original FieldMask + /// that does not included in mask. + /// + /// - Parameter mask: FieldMask with paths should be substracted. + /// - Returns: FieldMask with all paths does not included in mask. + public func subtract( + _ mask: Google_Protobuf_FieldMask + ) -> Google_Protobuf_FieldMask { + let set = Set(mask.paths) + var paths: [String] = [] + var buffer = Set() + for path in self.paths where !set.contains(path) && !buffer.contains(path) { + buffer.insert(path) + paths.append(path) + } + return .with { mask in + mask.paths = paths + } + } + + /// Returns true if path is covered by the given FieldMask. Note that path + /// "foo.bar" covers all paths like "foo.bar.baz", "foo.bar.quz.x", etc. + /// Also note that parent paths are not covered by explicit child path, i.e. + /// "foo.bar" does NOT cover "foo", even if "bar" is the only child. + /// + /// - Parameter path: Path to be checked. + /// - Returns: Boolean determines is path covered. + public func contains(_ path: String) -> Bool { + for fieldMaskPath in paths { + if path.hasPrefix("\(fieldMaskPath).") || fieldMaskPath == path { + return true + } + } + return false + } +} + +extension Google_Protobuf_FieldMask { + + /// Checks whether the given FieldMask is valid for type M. + /// + /// - Parameter messageType: Message type to paths check with. + /// - Returns: Boolean determines FieldMask is valid. + public func isValid( + for messageType: M.Type + ) -> Bool { + var message = M() + return paths.allSatisfy { path in + message.isPathValid(path) + } + } +} + +/// Describes errors could happen during FieldMask utilities. +public enum FieldMaskError: Error { + + /// Describes a path is invalid for a Message type. + case invalidPath + + /// Describes a fieldNumber is invalid for a Message type. + case invalidFieldNumber +} + +private extension Message where Self: _ProtoNameProviding { + static func protoName(for number: Int) -> String? { + Self._protobuf_nameMap.names(for: number)?.proto.description + } + + static var allProtoNames: [String] { + Self._protobuf_nameMap.names.map(\.description) + } +} diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift new file mode 100644 index 000000000..2622d7214 --- /dev/null +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -0,0 +1,132 @@ +// Sources/SwiftProtobuf/Message+FieldMask.swift - Message field mask extensions +// +// Copyright (c) 2014 - 2023 Apple Inc. and the project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See LICENSE.txt for license information: +// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt +// +// ----------------------------------------------------------------------------- +/// +/// Extend the Message types with FieldMask utilities. +/// +// ----------------------------------------------------------------------------- + +import Foundation + +extension Message { + + /// Checks whether the given path is valid for Message type. + /// + /// - Parameter path: Path to be checked + /// - Returns: Boolean determines path is valid. + public static func isPathValid( + _ path: String + ) -> Bool { + var message = Self() + return message.hasPath(path: path) + } + + internal mutating func hasPath(path: String) -> Bool { + do { + try set(path: path, value: nil, mergeOption: .init()) + return true + } catch let error as PathDecodingError { + return error != .pathNotFound + } catch { + return false + } + } + + internal mutating func isPathValid( + _ path: String + ) -> Bool { + hasPath(path: path) + } +} + +extension Google_Protobuf_FieldMask { + + /// Defines available options for merging two messages. + public struct MergeOptions { + + public init() {} + + /// The default merging behavior will append entries from the source + /// repeated field to the destination repeated field. If you only want + /// to keep the entries from the source repeated field, set this flag + /// to true. + public var replaceRepeatedFields = false + } +} + +extension Message { + + /// Merges fields specified in a FieldMask into another message. + /// + /// - Parameters: + /// - source: Message that should be merged to the original one. + /// - fieldMask: FieldMask specifies which fields should be merged. + public mutating func merge( + from source: Self, + fieldMask: Google_Protobuf_FieldMask, + mergeOption: Google_Protobuf_FieldMask.MergeOptions = .init() + ) throws { + var visitor = PathVisitor() + try source.traverse(visitor: &visitor) + let values = visitor.values + // TODO: setting all values with only one decoding + for path in fieldMask.paths { + try? set( + path: path, + value: values[path], + mergeOption: mergeOption + ) + } + } +} + +extension Message where Self: Equatable, Self: _ProtoNameProviding { + + // TODO: Re-implement using clear fields instead of copying message + + /// Removes from 'message' any field that is not represented in the given + /// FieldMask. If the FieldMask is empty, does nothing. + /// + /// - Parameter fieldMask: FieldMask specifies which fields should be kept. + /// - Returns: Boolean determines if the message is modified + @discardableResult + public mutating func trim( + keeping fieldMask: Google_Protobuf_FieldMask + ) -> Bool { + if !fieldMask.isValid(for: Self.self) { + return false + } + if fieldMask.paths.isEmpty { + return false + } + var tmp = Self(removingAllFieldsOf: self) + do { + try tmp.merge(from: self, fieldMask: fieldMask) + let changed = tmp != self + self = tmp + return changed + } catch { + return false + } + } +} + +private extension Message { + init(removingAllFieldsOf message: Self) { + let newMessage: Self = .init() + if var newExtensible = newMessage as? any ExtensibleMessage, + let extensible = message as? any ExtensibleMessage { + newExtensible._protobuf_extensionFieldValues = extensible._protobuf_extensionFieldValues + self = newExtensible as? Self ?? newMessage + } else { + self = newMessage + } + self.unknownFields = message.unknownFields + } +} diff --git a/Sources/SwiftProtobuf/NameMap.swift b/Sources/SwiftProtobuf/NameMap.swift index e835c9899..ab22a3797 100644 --- a/Sources/SwiftProtobuf/NameMap.swift +++ b/Sources/SwiftProtobuf/NameMap.swift @@ -278,4 +278,9 @@ public struct _NameMap: ExpressibleByDictionaryLiteral { let n = Name(transientUtf8Buffer: raw) return jsonToNumberMap[n] } + + /// Returns all proto names + internal var names: [Name] { + numberToNameMap.map(\.value.proto) + } } diff --git a/Sources/SwiftProtobuf/PathDecoder.swift b/Sources/SwiftProtobuf/PathDecoder.swift new file mode 100644 index 000000000..7b7e67a9c --- /dev/null +++ b/Sources/SwiftProtobuf/PathDecoder.swift @@ -0,0 +1,440 @@ +// Sources/SwiftProtobuf/PathDecoder.swift - Path decoder +// +// Copyright (c) 2014 - 2023 Apple Inc. and the project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See LICENSE.txt for license information: +// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt +// +// ----------------------------------------------------------------------------- +/// +/// Decoder which sets value of a field by its path. +/// +// ----------------------------------------------------------------------------- + +import Foundation + +/// Describes errors can occure during decoding a proto by path. +public enum PathDecodingError: Error { + + /// Describes a mismatch in type of the fields. + /// + /// If a value of type A is applied to a path with type B. + /// this error will be thrown. + case typeMismatch + + /// Describes path is not found in message type. + /// + /// If a message has no field with the given path this + /// error will be thrown. + case pathNotFound +} + +extension Message { + static func number(for field: String) -> Int? { + guard let type = Self.self as? any _ProtoNameProviding.Type else { + return nil + } + guard let number = Array(field.utf8).withUnsafeBytes({ bytes in + type._protobuf_nameMap.number(forProtoName: bytes) + }) else { + return nil + } + if type._protobuf_nameMap.names(for: number)?.proto.description != field { + return nil + } + return number + } + + static func name(for field: Int) -> String? { + guard let type = Self.self as? any _ProtoNameProviding.Type else { + return nil + } + return type._protobuf_nameMap.names(for: field)?.proto.description + } +} + +// Decoder that set value of a message field by the given path +struct PathDecoder: Decoder { + + // The value should be set to the path + private let value: Any? + + // Field number should be overriden by decoder + private var number: Int? + + // The path only including sub-paths + private let nextPath: [String] + + // Merge options to be concidered while setting value + private let mergeOption: Google_Protobuf_FieldMask.MergeOptions + + private var replaceRepeatedFields: Bool { + mergeOption.replaceRepeatedFields + } + + init( + path: [String], + value: Any?, + mergeOption: Google_Protobuf_FieldMask.MergeOptions + ) throws { + if let firstComponent = path.first, + let number = T.number(for: firstComponent) { + self.number = number + self.nextPath = .init(path.dropFirst()) + } else { + throw PathDecodingError.pathNotFound + } + self.value = value + self.mergeOption = mergeOption + } + + private func setValue(_ value: inout V, defaultValue: V) throws { + if !nextPath.isEmpty { + throw PathDecodingError.pathNotFound + } + if self.value == nil { + value = defaultValue + return + } + guard let castedValue = self.value as? V else { + throw PathDecodingError.typeMismatch + } + value = castedValue + } + + private func setRepeatedValue(_ value: inout [V]) throws { + if !nextPath.isEmpty { + throw PathDecodingError.pathNotFound + } + var castedValue: [V] = [] + if self.value != nil { + guard let v = self.value as? [V] else { + throw PathDecodingError.typeMismatch + } + castedValue = v + } + if replaceRepeatedFields { + value = castedValue + } else { + value.append(contentsOf: castedValue) + } + } + + private func setMapValue( + _ value: inout Dictionary + ) throws { + if !nextPath.isEmpty { + throw PathDecodingError.pathNotFound + } + var castedValue: [K: V] = [:] + if self.value != nil { + guard let v = self.value as? Dictionary else { + throw PathDecodingError.typeMismatch + } + castedValue = v + } + if replaceRepeatedFields { + value = castedValue + } else { + value.merge(castedValue) { _, new in + new + } + } + } + + private func setMessageValue( + _ value: inout M? + ) throws { + if nextPath.isEmpty { + try setValue(&value, defaultValue: nil) + return + } + var decoder = try PathDecoder( + path: nextPath, + value: self.value, + mergeOption: mergeOption + ) + if value == nil { + value = .init() + } + try value?.decodeMessage(decoder: &decoder) + } + + mutating func handleConflictingOneOf() throws {} + + mutating func nextFieldNumber() throws -> Int? { + defer { number = nil } + return number + } + + mutating func decodeSingularFloatField(value: inout Float) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularFloatField(value: inout Float?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedFloatField(value: inout [Float]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularDoubleField(value: inout Double) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularDoubleField(value: inout Double?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularInt32Field(value: inout Int32) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularInt32Field(value: inout Int32?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularInt64Field(value: inout Int64) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularInt64Field(value: inout Int64?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularUInt32Field(value: inout UInt32) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularUInt64Field(value: inout UInt64) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularSInt32Field(value: inout Int32) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularSInt32Field(value: inout Int32?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularSInt64Field(value: inout Int64) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularSInt64Field(value: inout Int64?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularFixed32Field(value: inout UInt32) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularFixed64Field(value: inout UInt64) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularSFixed32Field(value: inout Int32) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularSFixed64Field(value: inout Int64) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularBoolField(value: inout Bool) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularBoolField(value: inout Bool?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularStringField(value: inout String) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularStringField(value: inout String?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedStringField(value: inout [String]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularBytesField(value: inout Data) throws { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularBytesField(value: inout Data?) throws { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedBytesField(value: inout [Data]) throws { + try setRepeatedValue(&value) + } + + mutating func decodeSingularEnumField( + value: inout E + ) throws where E : Enum, E.RawValue == Int { + try setValue(&value, defaultValue: .init()) + } + + mutating func decodeSingularEnumField( + value: inout E? + ) throws where E : Enum, E.RawValue == Int { + try setValue(&value, defaultValue: nil) + } + + mutating func decodeRepeatedEnumField( + value: inout [E] + ) throws where E : Enum, E.RawValue == Int { + try setRepeatedValue(&value) + } + + mutating func decodeSingularMessageField( + value: inout M? + ) throws where M : Message { + try setMessageValue(&value) + } + + mutating func decodeRepeatedMessageField( + value: inout [M] + ) throws where M : Message { + try setRepeatedValue(&value) + } + + mutating func decodeSingularGroupField( + value: inout G? + ) throws where G : Message { + try setMessageValue(&value) + } + + mutating func decodeRepeatedGroupField( + value: inout [G] + ) throws where G : Message { + try setRepeatedValue(&value) + } + + mutating func decodeMapField( + fieldType: _ProtobufMap.Type, + value: inout _ProtobufMap.BaseType + ) throws where KeyType : MapKeyType, ValueType : MapValueType { + try setMapValue(&value) + } + + mutating func decodeMapField( + fieldType: _ProtobufEnumMap.Type, + value: inout _ProtobufEnumMap.BaseType + ) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int { + try setMapValue(&value) + } + + mutating func decodeMapField( + fieldType: _ProtobufMessageMap.Type, + value: inout _ProtobufMessageMap.BaseType + ) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : Message { + try setMapValue(&value) + } + + mutating func decodeExtensionField( + values: inout ExtensionFieldValueSet, + messageType: any Message.Type, + fieldNumber: Int + ) throws { + preconditionFailure( + "Internal Error: Path decoder should never decode an extension field" + ) + } + +} + +extension Message { + mutating func `set`( + path: String, + value: Any?, + mergeOption: Google_Protobuf_FieldMask.MergeOptions + ) throws { + let _path = path.components(separatedBy: ".") + var decoder = try PathDecoder( + path: _path, + value: value, + mergeOption: mergeOption + ) + try decodeMessage(decoder: &decoder) + } +} diff --git a/Sources/SwiftProtobuf/PathVisitor.swift b/Sources/SwiftProtobuf/PathVisitor.swift new file mode 100644 index 000000000..868fed574 --- /dev/null +++ b/Sources/SwiftProtobuf/PathVisitor.swift @@ -0,0 +1,284 @@ +// Sources/SwiftProtobuf/PathVisitor.swift - Path visitor +// +// Copyright (c) 2014 - 2023 Apple Inc. and the project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See LICENSE.txt for license information: +// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt +// +// ----------------------------------------------------------------------------- +/// +/// Visitor which captures a pair of paths and their values. +/// +// ----------------------------------------------------------------------------- + +import Foundation + +// Visitor captures all values of message with their paths +struct PathVisitor: Visitor { + + // The path contains parent components + private let prevPath: String? + + // Captured values after visiting will be stored in this property + private(set) var values: [String: Any] = [:] + + internal init(prevPath: String? = nil) { + self.prevPath = prevPath + } + + mutating private func visit(_ value: Any, fieldNumber: Int) { + guard let name = T.name(for: fieldNumber) else { + return + } + if let prevPath { + values["\(prevPath).\(name)"] = value + } else { + values[name] = value + } + } + + mutating private func visitMessageField( + _ value: M, + fieldNumber: Int + ) { + guard var path = T.name(for: fieldNumber) else { + return + } + if let prevPath { + path = "\(prevPath).\(path)" + } + values[path] = value + var visitor = PathVisitor(prevPath: path) + try? value.traverse(visitor: &visitor) + values.merge(visitor.values) { _, new in + new + } + } + + mutating func visitUnknown(bytes: Data) throws {} + + mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularInt32Field(value: Int32, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularUInt32Field(value: UInt32, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularSInt32Field(value: Int32, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularSInt64Field(value: Int64, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularFixed32Field(value: UInt32, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularFixed64Field(value: UInt64, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularSFixed32Field(value: Int32, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularSFixed64Field(value: Int64, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularStringField(value: String, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularEnumField(value: E, fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularMessageField(value: M, fieldNumber: Int) throws { + visitMessageField(value, fieldNumber: fieldNumber) + } + + mutating func visitSingularGroupField(value: G, fieldNumber: Int) throws { + visitMessageField(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedEnumField(value: [E], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedMessageField(value: [M], fieldNumber: Int) throws where M : Message { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitRepeatedGroupField(value: [G], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedFloatField(value: [Float], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedDoubleField(value: [Double], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedInt32Field(value: [Int32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedInt64Field(value: [Int64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedUInt32Field(value: [UInt32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedUInt64Field(value: [UInt64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedSInt32Field(value: [Int32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedSInt64Field(value: [Int64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedFixed32Field(value: [UInt32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedFixed64Field(value: [UInt64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedSFixed32Field(value: [Int32], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedSFixed64Field(value: [Int64], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedBoolField(value: [Bool], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitPackedEnumField(value: [E], fieldNumber: Int) throws { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitMapField( + fieldType: _ProtobufMap.Type, + value: _ProtobufMap.BaseType, + fieldNumber: Int + ) throws where KeyType : MapKeyType, ValueType : MapValueType { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitMapField( + fieldType: _ProtobufEnumMap.Type, + value: _ProtobufEnumMap.BaseType, + fieldNumber: Int + ) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int { + visit(value, fieldNumber: fieldNumber) + } + + mutating func visitMapField( + fieldType: _ProtobufMessageMap.Type, + value: _ProtobufMessageMap.BaseType, + fieldNumber: Int + ) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : Message { + visit(value, fieldNumber: fieldNumber) + } +} diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index d550be8d9..be0a8b9b4 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -13,9 +13,6 @@ /// // ----------------------------------------------------------------------------- -// TODO: We should have utility functions for applying a mask to an arbitrary -// message, intersecting two masks, etc. - import Foundation import XCTest import SwiftProtobuf @@ -32,7 +29,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } // assertJSONEncode doesn't want an empty object, hand roll it. let msg = MessageTestType.with { (o: inout MessageTestType) in - o.paths = [] + o.paths = [] } XCTAssertEqual(try msg.jsonString(), "\"\"") assertJSONDecodeSucceeds("\"foo\"") { $0.paths == ["foo"] } @@ -109,4 +106,711 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertThrowsError(try m.jsonString()) } } + + // Checks merge functionality for field masks. + func testMergeFieldsOfMessage() throws { + var message = SwiftProtoTesting_TestAllTypes.with { model in + model.optionalInt32 = 1 + model.optionalNestedMessage = .with { nested in + nested.bb = 2 + } + } + + let secondMessage = SwiftProtoTesting_TestAllTypes.with { model in + model.optionalInt32 = 2 + model.optionalNestedMessage = .with { nested in + nested.bb = 3 + } + } + + // Checks nested message merge + try message.merge(from: secondMessage, fieldMask: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertEqual(message.optionalInt32, 1) + XCTAssertEqual(message.optionalNestedMessage.bb, 3) + + // Checks primitive type merge + try message.merge(from: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) + XCTAssertEqual(message.optionalInt32, 2) + XCTAssertEqual(message.optionalNestedMessage.bb, 3) + } + + // Checks merge functionality for repeated field masks. + func testMergeRepeatedFieldsOfMessage() throws { + var message = SwiftProtoTesting_TestAllTypes.with { model in + model.repeatedInt32 = [1, 2] + } + + let secondMessage = SwiftProtoTesting_TestAllTypes.with { model in + model.repeatedInt32 = [3, 4] + } + + let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["repeated_int32"]) + + // Checks without replacing repeated fields + try message.merge(from: secondMessage, fieldMask: fieldMask) + XCTAssertEqual(message.repeatedInt32, [1, 2, 3, 4]) + + // Checks with replacing repeated fields + var options = Google_Protobuf_FieldMask.MergeOptions() + options.replaceRepeatedFields = true + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: options) + XCTAssertEqual(message.repeatedInt32, [3, 4]) + } + + // Checks merge functionality for map field masks. + func testMergeMapFieldsOfMessage() throws { + var message = SwiftProtoTesting_Fuzz_Message.with { model in + model.mapInt32String = [1: "a", 2: "c"] + } + + let secondMessage = SwiftProtoTesting_Fuzz_Message.with { model in + model.mapInt32String = [2: "b"] + } + + let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["map_int32_string"]) + + // Checks without replacing repeated fields + try message.merge(from: secondMessage, fieldMask: fieldMask) + XCTAssertEqual(message.mapInt32String, [1: "a", 2: "b"]) + + // Checks with replacing repeated fields + var options = Google_Protobuf_FieldMask.MergeOptions() + options.replaceRepeatedFields = true + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: options) + XCTAssertEqual(message.mapInt32String, [2: "b"]) + } + + // Checks trim functionality for field masks. + func testTrimFieldsOfMessage() throws { + var message = SwiftProtoTesting_TestAllTypes.with { model in + model.optionalInt32 = 1 + model.optionalNestedMessage = .with { nested in + nested.bb = 2 + } + } + + // Checks trim to be successful. + let r1 = message.trim(keeping: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertTrue(r1) + XCTAssertEqual(message.optionalInt32, 0) + XCTAssertEqual(message.optionalNestedMessage.bb, 2) + + // Checks trim should do nothing with an empty fieldMask. + let r2 = message.trim(keeping: .init()) + XCTAssertFalse(r2) + + // Checks trim should return false if nothing has been changed. + let r3 = message.trim(keeping: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertFalse(r3) + + // Checks trim to be unsuccessful with an invalid fieldMask. + let r4 = message.trim(keeping: .init(protoPaths: "invalid_path")) + XCTAssertFalse(r4) + } + + // Checks trim functionality for field masks when applies on a extensible message. + func testTrimFieldsOfMessageWithExtension() throws { + var message = SwiftProtoTesting_Fuzz_Message() + message.singularInt32 = 1 + message.SwiftProtoTesting_Fuzz_singularInt32Ext = 1 + let mask = Google_Protobuf_FieldMask(protoPaths: ["singular_string"]) + + // Checks trim should retain extensions while removes other fields. + let r1 = message.trim(keeping: mask) + XCTAssertTrue(r1) + XCTAssertEqual(message.SwiftProtoTesting_Fuzz_singularInt32Ext, .init(1)) + XCTAssertEqual(message.singularInt32, .init(0)) + + // Checks trim should do nothing (fields are already removed) and still retain extension fields. + let r2 = message.trim(keeping: mask) + XCTAssertFalse(r2) + XCTAssertEqual(message.SwiftProtoTesting_Fuzz_singularInt32Ext, .init(1)) + } + + // Checks `isPathValid` func + // 1. Valid primitive path. + // 2, 3. Valid nested path. (for message and group) + // 4. Invalid primitive path. + // 5, 6. Invalid nested path. + // 7, 8. Invalid path after map and repeated. + // 9. Invalid path after group. + func testIsPathValid() { + XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int32")) + XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bb")) + XCTAssertTrue(SwiftProtoTesting_Fuzz_Message.isPathValid("SingularGroup.group_field")) + XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int")) + XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bc")) + XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bb.a")) + XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("repeatedInt32.a")) + XCTAssertFalse(SwiftProtoTesting_Fuzz_Message.isPathValid("map_bool_int32.a")) + XCTAssertFalse(SwiftProtoTesting_Fuzz_Message.isPathValid("SingularGroup.a")) + } + + // Checks `isValid` func of FieldMask. + // 1. Empty field mask is always valid. + // 2, 3. Valid field masks. + // 4, 5. Invalid field masks. + func testIsFieldMaskValid() { + let m1 = Google_Protobuf_FieldMask() + let m2 = Google_Protobuf_FieldMask(protoPaths: [ + "optional_int32", + "optional_nested_message.bb" + ]) + let m3 = Google_Protobuf_FieldMask(protoPaths: [ + "optional_int32", + "optional_nested_message" + ]) + let m4 = Google_Protobuf_FieldMask(protoPaths: [ + "optional_int32", + "optional_nested_message.bc" + ]) + let m5 = Google_Protobuf_FieldMask(protoPaths: [ + "optional_int", + "optional_nested_message.bb" + ]) + XCTAssertTrue(m1.isValid(for: SwiftProtoTesting_TestAllTypes.self)) + XCTAssertTrue(m2.isValid(for: SwiftProtoTesting_TestAllTypes.self)) + XCTAssertTrue(m3.isValid(for: SwiftProtoTesting_TestAllTypes.self)) + XCTAssertFalse(m4.isValid(for: SwiftProtoTesting_TestAllTypes.self)) + XCTAssertFalse(m5.isValid(for: SwiftProtoTesting_TestAllTypes.self)) + } + + // Checks canonincal form of field mask. + // 1. Sub-message with parent in the paths should be excluded. + // 2. Canonincal form should be sorted. + // 3. More nested levels of paths with duplicates. + // 4. Two siblings with their parent should be excluded. + func testCanonicalFieldMask() { + let m1 = Google_Protobuf_FieldMask(protoPaths: ["a.b", "a", "b"]) + XCTAssertEqual(m1.canonical.paths, ["a", "b"]) + let m2 = Google_Protobuf_FieldMask(protoPaths: ["b", "a"]) + XCTAssertEqual(m2.canonical.paths, ["a", "b"]) + let m3 = Google_Protobuf_FieldMask(protoPaths: ["c", "a.b.c", "a.b", "a.b", "a.b.c.d"]) + XCTAssertEqual(m3.canonical.paths, ["a.b", "c"]) + let m4 = Google_Protobuf_FieldMask(protoPaths: ["a.c", "a", "a.b"]) + XCTAssertEqual(m4.canonical.paths, ["a"]) + } + + // Checks `addPath` func of fieldMask with: + // - Valid primitive path should be added. + // - Valid nested path should be added. + // - Invalid path should throw error. + func testAddPathToFieldMask() throws { + var mask = Google_Protobuf_FieldMask() + XCTAssertNoThrow(try mask.addPath("optional_int32", of: SwiftProtoTesting_TestAllTypes.self)) + XCTAssertEqual(mask.paths, ["optional_int32"]) + XCTAssertNoThrow(try mask.addPath("optional_nested_message.bb", of: SwiftProtoTesting_TestAllTypes.self)) + XCTAssertEqual(mask.paths, ["optional_int32", "optional_nested_message.bb"]) + XCTAssertThrowsError(try mask.addPath("optional_int", of: SwiftProtoTesting_TestAllTypes.self)) + } + + // Check `contains` func of fieldMask. + // 1. Parent contains sub-message. + // 2. Path contains itself. + // 3. Sub-message does not contain its parent. + // 4. Two different paths does not contain each other. + // 5. Two different sub-paths does not contain each other. + func testPathContainsInFieldMask() { + let m1 = Google_Protobuf_FieldMask(protoPaths: ["a"]) + XCTAssertTrue(m1.contains("a.b")) + let m2 = Google_Protobuf_FieldMask(protoPaths: ["a"]) + XCTAssertTrue(m2.contains("a")) + let m3 = Google_Protobuf_FieldMask(protoPaths: ["a.b"]) + XCTAssertFalse(m3.contains("a")) + let m4 = Google_Protobuf_FieldMask(protoPaths: ["a"]) + XCTAssertFalse(m4.contains("b")) + let m5 = Google_Protobuf_FieldMask(protoPaths: ["a.b"]) + XCTAssertFalse(m5.contains("a.c")) + } + + // Checks inits of fieldMask with: + // - All fields of a message type. + // - Particular field numbers of a message type. + func testFieldPathMessageInits() throws { + let m1 = Google_Protobuf_FieldMask(allFieldsOf: SwiftProtoTesting_TestAny.self) + XCTAssertEqual(m1.paths.sorted(), ["any_value", "int32_value", "repeated_any_value", "text"]) + let m2 = try Google_Protobuf_FieldMask(fieldNumbers: [1, 2], of: SwiftProtoTesting_TestAny.self) + XCTAssertEqual(m2.paths.sorted(), ["any_value", "int32_value"]) + XCTAssertThrowsError(try Google_Protobuf_FieldMask(fieldNumbers: [10], of: SwiftProtoTesting_TestAny.self)) + } + + // Checks that json names of paths should not be contained in mask field with allFieldsOf init. + func testFieldMaskAllPathsWithUniqueName() { + let mask = Google_Protobuf_FieldMask(allFieldsOf: SwiftProtoTesting_Fuzz_Message.self) + // proto name is included + XCTAssertTrue(mask.paths.contains("SingularGroup")) + // json name is not included + XCTAssertFalse(mask.paths.contains("singulargroup")) + } + + // Checks `union` func of fieldMask. + func testUnionFieldMasks() throws { + let m1 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m2 = Google_Protobuf_FieldMask(protoPaths: ["b", "c"]) + XCTAssertEqual(m1.union(m2).paths, ["a", "b", "c"]) + + let m3 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m4 = Google_Protobuf_FieldMask(protoPaths: ["c", "d"]) + XCTAssertEqual(m3.union(m4).paths, ["a", "b", "c", "d"]) + + let m5 = Google_Protobuf_FieldMask() + let m6 = Google_Protobuf_FieldMask(protoPaths: ["c", "d"]) + XCTAssertEqual(m5.union(m6).paths, ["c", "d"]) + + let m7 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m8 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + XCTAssertEqual(m7.union(m8).paths, ["a", "b"]) + + let m9 = Google_Protobuf_FieldMask(protoPaths: ["a", "a"]) + let m10 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + XCTAssertEqual(m9.union(m10).paths, ["a", "b"]) + } + + // Checks `intersect` func of fieldMask. + func testIntersectFieldMasks() throws { + let m1 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m2 = Google_Protobuf_FieldMask(protoPaths: ["b", "c"]) + XCTAssertEqual(m1.intersect(m2).paths, ["b"]) + + let m3 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m4 = Google_Protobuf_FieldMask(protoPaths: ["c", "d"]) + XCTAssertEqual(m3.intersect(m4).paths, []) + + let m5 = Google_Protobuf_FieldMask() + let m6 = Google_Protobuf_FieldMask(protoPaths: ["c", "d"]) + XCTAssertEqual(m5.intersect(m6).paths, []) + + let m7 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m8 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + XCTAssertEqual(m7.intersect(m8).paths, ["a", "b"]) + + let m9 = Google_Protobuf_FieldMask(protoPaths: ["a", "a"]) + let m10 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + XCTAssertEqual(m9.intersect(m10).paths, ["a"]) + } + + // Checks `substract` func of fieldMask. + func testSubtractFieldMasks() throws { + let m1 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m2 = Google_Protobuf_FieldMask(protoPaths: ["b", "c"]) + XCTAssertEqual(m1.subtract(m2).paths, ["a"]) + + let m3 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m4 = Google_Protobuf_FieldMask(protoPaths: ["c", "d"]) + XCTAssertEqual(m3.subtract(m4).paths, ["a", "b"]) + + let m5 = Google_Protobuf_FieldMask() + let m6 = Google_Protobuf_FieldMask(protoPaths: ["c", "d"]) + XCTAssertEqual(m5.subtract(m6).paths, []) + + let m7 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m8 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + XCTAssertEqual(m7.subtract(m8).paths, []) + + let m9 = Google_Protobuf_FieldMask(protoPaths: ["a", "a"]) + let m10 = Google_Protobuf_FieldMask(protoPaths: ["b"]) + XCTAssertEqual(m9.subtract(m10).paths, ["a"]) + } + + // Checks whether all field types could be merged. + func testMergeAllFields() throws { + var m1 = SwiftProtoTesting_Fuzz_Message() + let m2 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularInt32 = 1 + m.singularInt64 = 1 + m.singularUint32 = 1 + m.singularUint64 = 1 + m.singularSint32 = 1 + m.singularSint64 = 1 + m.singularFixed32 = 1 + m.singularFixed64 = 1 + m.singularSfixed32 = 1 + m.singularSfixed64 = 1 + m.singularFloat = 1 + m.singularDouble = 1 + m.singularBool = true + m.singularString = "str" + m.singularBytes = "str".data(using: .utf8) ?? .init() + m.singularEnum = .two + m.singularGroup = .with { $0.groupField = 1 } + m.singularMessage = .with { $0.singularInt32 = 1 } + m.repeatedInt32 = [1] + m.repeatedInt64 = [1] + m.repeatedUint32 = [1] + m.repeatedUint64 = [1] + m.repeatedSint32 = [1] + m.repeatedSint64 = [1] + m.repeatedFixed32 = [1] + m.repeatedFixed64 = [1] + m.repeatedSfixed32 = [1] + m.repeatedSfixed64 = [1] + m.repeatedFloat = [1] + m.repeatedDouble = [1] + m.repeatedBool = [true] + m.repeatedString = ["str"] + m.repeatedBytes = ["str".data(using: .utf8) ?? .init()] + m.repeatedEnum = [.two] + m.repeatedGroup = [.with { $0.groupField = 1 }] + m.repeatedMessage = [.with { $0.singularInt32 = 1 }] + m.o = .oneofInt32(1) + m.repeatedPackedInt32 = [1] + m.repeatedPackedInt64 = [1] + m.repeatedPackedUint32 = [1] + m.repeatedPackedUint64 = [1] + m.repeatedPackedSint32 = [1] + m.repeatedPackedSint64 = [1] + m.repeatedPackedFixed32 = [1] + m.repeatedPackedFixed64 = [1] + m.repeatedPackedSfixed32 = [1] + m.repeatedPackedSfixed64 = [1] + m.repeatedPackedFloat = [1] + m.repeatedPackedDouble = [1] + m.repeatedPackedBool = [true] + m.repeatedPackedEnum = [.two] + m.mapInt32Int32 = [1: 1] + m.mapInt32Int64 = [1: 1] + m.mapInt32Uint32 = [1: 1] + m.mapInt32Uint64 = [1: 1] + m.mapInt32Sint32 = [1: 1] + m.mapInt32Sint64 = [1: 1] + m.mapInt32Fixed32 = [1: 1] + m.mapInt32Fixed64 = [1: 1] + m.mapInt32AnEnum = [1: .one] + m.mapInt32Message = [1: .init()] + } + try m1.merge(from: m2, fieldMask: .init(allFieldsOf: SwiftProtoTesting_Fuzz_Message.self)) + XCTAssertEqual(m1.singularInt32, m2.singularInt32) + XCTAssertEqual(m1.singularInt64, m2.singularInt64) + XCTAssertEqual(m1.singularUint32, m2.singularUint32) + XCTAssertEqual(m1.singularUint64, m2.singularUint64) + XCTAssertEqual(m1.singularSint32, m2.singularSint32) + XCTAssertEqual(m1.singularSint64, m2.singularSint64) + XCTAssertEqual(m1.singularFixed32, m2.singularFixed32) + XCTAssertEqual(m1.singularFixed64, m2.singularFixed64) + XCTAssertEqual(m1.singularSfixed32, m2.singularSfixed32) + XCTAssertEqual(m1.singularSfixed64, m2.singularSfixed64) + XCTAssertEqual(m1.singularFloat, m2.singularFloat) + XCTAssertEqual(m1.singularDouble, m2.singularDouble) + XCTAssertEqual(m1.singularBool, m2.singularBool) + XCTAssertEqual(m1.singularString, m2.singularString) + XCTAssertEqual(m1.singularBytes, m2.singularBytes) + XCTAssertEqual(m1.singularEnum, m2.singularEnum) + XCTAssertEqual(m1.singularGroup, m2.singularGroup) + XCTAssertEqual(m1.singularMessage, m2.singularMessage) + XCTAssertEqual(m1.repeatedInt32, m2.repeatedInt32) + XCTAssertEqual(m1.repeatedInt64, m2.repeatedInt64) + XCTAssertEqual(m1.repeatedUint32, m2.repeatedUint32) + XCTAssertEqual(m1.repeatedUint64, m2.repeatedUint64) + XCTAssertEqual(m1.repeatedSint32, m2.repeatedSint32) + XCTAssertEqual(m1.repeatedSint64, m2.repeatedSint64) + XCTAssertEqual(m1.repeatedFixed32, m2.repeatedFixed32) + XCTAssertEqual(m1.repeatedFixed64, m2.repeatedFixed64) + XCTAssertEqual(m1.repeatedSfixed32, m2.repeatedSfixed32) + XCTAssertEqual(m1.repeatedSfixed64, m2.repeatedSfixed64) + XCTAssertEqual(m1.repeatedFloat, m2.repeatedFloat) + XCTAssertEqual(m1.repeatedDouble, m2.repeatedDouble) + XCTAssertEqual(m1.repeatedBool, m2.repeatedBool) + XCTAssertEqual(m1.repeatedString, m2.repeatedString) + XCTAssertEqual(m1.repeatedBytes, m2.repeatedBytes) + XCTAssertEqual(m1.repeatedEnum, m2.repeatedEnum) + XCTAssertEqual(m1.repeatedGroup, m2.repeatedGroup) + XCTAssertEqual(m1.repeatedMessage, m2.repeatedMessage) + XCTAssertEqual(m1.o, m2.o) + XCTAssertEqual(m1.repeatedPackedInt32, m2.repeatedPackedInt32) + XCTAssertEqual(m1.repeatedPackedInt64, m2.repeatedPackedInt64) + XCTAssertEqual(m1.repeatedPackedUint32, m2.repeatedPackedUint32) + XCTAssertEqual(m1.repeatedPackedUint64, m2.repeatedPackedUint64) + XCTAssertEqual(m1.repeatedPackedSint32, m2.repeatedPackedSint32) + XCTAssertEqual(m1.repeatedPackedSint64, m2.repeatedPackedSint64) + XCTAssertEqual(m1.repeatedPackedFixed32, m2.repeatedPackedFixed32) + XCTAssertEqual(m1.repeatedPackedFixed64, m2.repeatedPackedFixed64) + XCTAssertEqual(m1.repeatedPackedSfixed32, m2.repeatedPackedSfixed32) + XCTAssertEqual(m1.repeatedPackedSfixed64, m2.repeatedPackedSfixed64) + XCTAssertEqual(m1.repeatedPackedFloat, m2.repeatedPackedFloat) + XCTAssertEqual(m1.repeatedPackedDouble, m2.repeatedPackedDouble) + XCTAssertEqual(m1.repeatedPackedBool, m2.repeatedPackedBool) + XCTAssertEqual(m1.repeatedPackedEnum, m2.repeatedPackedEnum) + XCTAssertEqual(m1.mapInt32Int32, m2.mapInt32Int32) + XCTAssertEqual(m1.mapInt32Int64, m2.mapInt32Int64) + XCTAssertEqual(m1.mapInt32Uint32, m2.mapInt32Uint32) + XCTAssertEqual(m1.mapInt32Uint64, m2.mapInt32Uint64) + XCTAssertEqual(m1.mapInt32Sint32, m2.mapInt32Sint32) + XCTAssertEqual(m1.mapInt32Sint64, m2.mapInt32Sint64) + XCTAssertEqual(m1.mapInt32Fixed32, m2.mapInt32Fixed32) + XCTAssertEqual(m1.mapInt32Fixed64, m2.mapInt32Fixed64) + XCTAssertEqual(m1.mapInt32AnEnum, m2.mapInt32AnEnum) + XCTAssertEqual(m1.mapInt32Message, m2.mapInt32Message) + } + + // Checks whether a group of fields could be merged without merging the others. + func testMergeFieldsPartially() throws { + var m1 = SwiftProtoTesting_Fuzz_Message() + let m2 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularInt32 = 1 + m.singularInt64 = 1 + m.singularUint32 = 1 + m.singularUint64 = 1 + m.singularSint32 = 1 + m.singularSint64 = 1 + m.singularFixed32 = 1 + m.singularFixed64 = 1 + m.singularSfixed32 = 1 + m.singularSfixed64 = 1 + m.singularFloat = 1 + m.singularDouble = 1 + m.singularBool = true + m.singularString = "str" + m.singularBytes = "str".data(using: .utf8) ?? .init() + m.singularEnum = .two + m.singularGroup = .with { $0.groupField = 1 } + m.singularMessage = .with { $0.singularInt32 = 1 } + m.repeatedInt32 = [1] + m.repeatedInt64 = [1] + m.repeatedUint32 = [1] + m.repeatedUint64 = [1] + m.repeatedSint32 = [1] + m.repeatedSint64 = [1] + m.repeatedFixed32 = [1] + m.repeatedFixed64 = [1] + m.repeatedSfixed32 = [1] + m.repeatedSfixed64 = [1] + m.repeatedFloat = [1] + m.repeatedDouble = [1] + m.repeatedBool = [true] + m.repeatedString = ["str"] + m.repeatedBytes = ["str".data(using: .utf8) ?? .init()] + m.repeatedEnum = [.two] + m.repeatedGroup = [.with { $0.groupField = 1 }] + m.repeatedMessage = [.with { $0.singularInt32 = 1 }] + m.o = .oneofInt32(1) + m.repeatedPackedInt32 = [1] + m.repeatedPackedInt64 = [1] + m.repeatedPackedUint32 = [1] + m.repeatedPackedUint64 = [1] + m.repeatedPackedSint32 = [1] + m.repeatedPackedSint64 = [1] + m.repeatedPackedFixed32 = [1] + m.repeatedPackedFixed64 = [1] + m.repeatedPackedSfixed32 = [1] + m.repeatedPackedSfixed64 = [1] + m.repeatedPackedFloat = [1] + m.repeatedPackedDouble = [1] + m.repeatedPackedBool = [true] + m.repeatedPackedEnum = [.two] + m.mapInt32Int32 = [1: 1] + m.mapInt32Int64 = [1: 1] + m.mapInt32Uint32 = [1: 1] + m.mapInt32Uint64 = [1: 1] + m.mapInt32Sint32 = [1: 1] + m.mapInt32Sint64 = [1: 1] + m.mapInt32Fixed32 = [1: 1] + m.mapInt32Fixed64 = [1: 1] + m.mapInt32AnEnum = [1: .one] + m.mapInt32Message = [1: .init()] + } + let mask = Google_Protobuf_FieldMask(protoPaths: [ + "singular_int32", + "singular_int64", + "singular_uint32", + "singular_uint64", + "singular_sint32", + "singular_sint64", + "singular_fixed32", + "singular_fixed64", + "singular_sfixed32", + "singular_sfixed64" + ]) + try m1.merge(from: m2, fieldMask: mask) + XCTAssertEqual(m1.singularInt32, m2.singularInt32) + XCTAssertEqual(m1.singularInt64, m2.singularInt64) + XCTAssertEqual(m1.singularUint32, m2.singularUint32) + XCTAssertEqual(m1.singularUint64, m2.singularUint64) + XCTAssertEqual(m1.singularSint32, m2.singularSint32) + XCTAssertEqual(m1.singularSint64, m2.singularSint64) + XCTAssertEqual(m1.singularFixed32, m2.singularFixed32) + XCTAssertEqual(m1.singularFixed64, m2.singularFixed64) + XCTAssertEqual(m1.singularSfixed32, m2.singularSfixed32) + XCTAssertEqual(m1.singularSfixed64, m2.singularSfixed64) + XCTAssertNotEqual(m1.singularFloat, m2.singularFloat) + XCTAssertNotEqual(m1.singularDouble, m2.singularDouble) + XCTAssertNotEqual(m1.singularBool, m2.singularBool) + XCTAssertNotEqual(m1.singularString, m2.singularString) + XCTAssertNotEqual(m1.singularBytes, m2.singularBytes) + XCTAssertNotEqual(m1.singularEnum, m2.singularEnum) + XCTAssertNotEqual(m1.singularGroup, m2.singularGroup) + XCTAssertNotEqual(m1.singularMessage, m2.singularMessage) + XCTAssertNotEqual(m1.repeatedInt32, m2.repeatedInt32) + XCTAssertNotEqual(m1.repeatedInt64, m2.repeatedInt64) + XCTAssertNotEqual(m1.repeatedUint32, m2.repeatedUint32) + XCTAssertNotEqual(m1.repeatedUint64, m2.repeatedUint64) + XCTAssertNotEqual(m1.repeatedSint32, m2.repeatedSint32) + XCTAssertNotEqual(m1.repeatedSint64, m2.repeatedSint64) + XCTAssertNotEqual(m1.repeatedFixed32, m2.repeatedFixed32) + XCTAssertNotEqual(m1.repeatedFixed64, m2.repeatedFixed64) + XCTAssertNotEqual(m1.repeatedSfixed32, m2.repeatedSfixed32) + XCTAssertNotEqual(m1.repeatedSfixed64, m2.repeatedSfixed64) + XCTAssertNotEqual(m1.repeatedFloat, m2.repeatedFloat) + XCTAssertNotEqual(m1.repeatedDouble, m2.repeatedDouble) + XCTAssertNotEqual(m1.repeatedBool, m2.repeatedBool) + XCTAssertNotEqual(m1.repeatedString, m2.repeatedString) + XCTAssertNotEqual(m1.repeatedBytes, m2.repeatedBytes) + XCTAssertNotEqual(m1.repeatedEnum, m2.repeatedEnum) + XCTAssertNotEqual(m1.repeatedGroup, m2.repeatedGroup) + XCTAssertNotEqual(m1.repeatedMessage, m2.repeatedMessage) + XCTAssertNotEqual(m1.o, m2.o) + XCTAssertNotEqual(m1.repeatedPackedInt32, m2.repeatedPackedInt32) + XCTAssertNotEqual(m1.repeatedPackedInt64, m2.repeatedPackedInt64) + XCTAssertNotEqual(m1.repeatedPackedUint32, m2.repeatedPackedUint32) + XCTAssertNotEqual(m1.repeatedPackedUint64, m2.repeatedPackedUint64) + XCTAssertNotEqual(m1.repeatedPackedSint32, m2.repeatedPackedSint32) + XCTAssertNotEqual(m1.repeatedPackedSint64, m2.repeatedPackedSint64) + XCTAssertNotEqual(m1.repeatedPackedFixed32, m2.repeatedPackedFixed32) + XCTAssertNotEqual(m1.repeatedPackedFixed64, m2.repeatedPackedFixed64) + XCTAssertNotEqual(m1.repeatedPackedSfixed32, m2.repeatedPackedSfixed32) + XCTAssertNotEqual(m1.repeatedPackedSfixed64, m2.repeatedPackedSfixed64) + XCTAssertNotEqual(m1.repeatedPackedFloat, m2.repeatedPackedFloat) + XCTAssertNotEqual(m1.repeatedPackedDouble, m2.repeatedPackedDouble) + XCTAssertNotEqual(m1.repeatedPackedBool, m2.repeatedPackedBool) + XCTAssertNotEqual(m1.repeatedPackedEnum, m2.repeatedPackedEnum) + XCTAssertNotEqual(m1.mapInt32Int32, m2.mapInt32Int32) + XCTAssertNotEqual(m1.mapInt32Int64, m2.mapInt32Int64) + XCTAssertNotEqual(m1.mapInt32Uint32, m2.mapInt32Uint32) + XCTAssertNotEqual(m1.mapInt32Uint64, m2.mapInt32Uint64) + XCTAssertNotEqual(m1.mapInt32Sint32, m2.mapInt32Sint32) + XCTAssertNotEqual(m1.mapInt32Sint64, m2.mapInt32Sint64) + XCTAssertNotEqual(m1.mapInt32Fixed32, m2.mapInt32Fixed32) + XCTAssertNotEqual(m1.mapInt32Fixed64, m2.mapInt32Fixed64) + XCTAssertNotEqual(m1.mapInt32AnEnum, m2.mapInt32AnEnum) + XCTAssertNotEqual(m1.mapInt32Message, m2.mapInt32Message) + } + + // Checks merge could be done for an optional path with nil value. + func testMergeOptionalValue() throws { + var m1 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularInt32 = 1 + } + let m2 = SwiftProtoTesting_Fuzz_Message() + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["singular_int32"])) + XCTAssertEqual(m1.singularInt32, m2.singularInt32) + } + + // Checks merge could be done for an optional path with default value. + func testMergeDefaultValue() throws { + var m1 = SwiftProtoTesting_TestAllTypes.with { m in + m.defaultInt32 = 1 + } + let m2 = SwiftProtoTesting_TestAllTypes() + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["default_int32"])) + XCTAssertEqual(m1.defaultInt32, m2.defaultInt32) + } + + // Checks merge could be done for non-optional paths. + func testMergeNonOptionalValues() throws { + var m1 = try SwiftProtoTesting_Proto3_TestAllTypes.with { m in + m.optionalInt32 = 1 + m.optionalInt64 = 1 + m.optionalDouble = 1 + m.optionalFloat = 1 + m.optionalString = "str" + m.optionalBool = true + m.optionalBytes = try XCTUnwrap("str".data(using: .utf8)) + m.optionalUint32 = 1 + m.optionalUint64 = 1 + m.optionalSint32 = 1 + m.optionalSint64 = 1 + m.optionalFixed32 = 1 + m.optionalFixed64 = 1 + m.optionalSfixed32 = 1 + m.optionalSfixed64 = 1 + m.optionalNestedEnum = .bar + } + let m2 = SwiftProtoTesting_Proto3_TestAllTypes() + try m1.merge(from: m2, fieldMask: .init(protoPaths: [ + "optional_int32", + "optional_int64", + "optional_double", + "optional_float", + "optional_string", + "optional_bool", + "optional_bytes", + "optional_uint32", + "optional_uint64", + "optional_sint32", + "optional_sint64", + "optional_fixed32", + "optional_fixed64", + "optional_sfixed32", + "optional_sfixed64", + "optional_nested_enum" + ])) + XCTAssertEqual(m1.optionalInt32, m2.optionalInt32) + XCTAssertEqual(m1.optionalInt64, m2.optionalInt64) + XCTAssertEqual(m1.optionalDouble, m2.optionalDouble) + XCTAssertEqual(m1.optionalFloat, m2.optionalFloat) + XCTAssertEqual(m1.optionalString, m2.optionalString) + XCTAssertEqual(m1.optionalBool, m2.optionalBool) + XCTAssertEqual(m1.optionalBytes, m2.optionalBytes) + XCTAssertEqual(m1.optionalUint32, m2.optionalUint32) + XCTAssertEqual(m1.optionalUint64, m2.optionalUint64) + XCTAssertEqual(m1.optionalSint32, m2.optionalSint32) + XCTAssertEqual(m1.optionalSint64, m2.optionalSint64) + XCTAssertEqual(m1.optionalFixed32, m2.optionalFixed32) + XCTAssertEqual(m1.optionalFixed64, m2.optionalFixed64) + XCTAssertEqual(m1.optionalSfixed32, m2.optionalSfixed32) + XCTAssertEqual(m1.optionalSfixed64, m2.optionalSfixed64) + XCTAssertEqual(m1.optionalNestedEnum, m2.optionalNestedEnum) + XCTAssertEqual(m1.optionalSint32, m2.optionalSint32) + } + + // Checks if merge works with nested proto messages + func testMergeNestedMessages() throws { + var m1 = SwiftProtoTesting_Fuzz_Message() + let m2 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularMessage = .with { _m in + _m.singularMessage = .with { __m in + __m.singularInt32 = 1 + } + } + } + let m3 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularMessage = .with { _m in + _m.singularMessage = .with { __m in + __m.singularInt32 = 2 + } + } + } + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["singular_message.singular_message"])) + XCTAssertEqual(m1.singularMessage.singularMessage.singularInt32, Int32(1)) + try m1.merge(from: m3, fieldMask: .init(protoPaths: ["singular_message.singular_message.singular_int32"])) + XCTAssertEqual(m1.singularMessage.singularMessage.singularInt32, Int32(2)) + } + + // Checks merging nested path inside groups + func testMergeNestedGroups() throws { + var m1 = SwiftProtoTesting_Fuzz_Message() + let m2 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularGroup = .with { _m in + _m.groupField = 1 + } + } + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["SingularGroup.group_field"])) + XCTAssertEqual(m1.singularGroup.groupField, m2.singularGroup.groupField) + } + + // Checks that merging with json path should do nothing. Path should only be merged using proto names. + func testMergeFieldWithJSONName() throws { + var m1 = SwiftProtoTesting_Fuzz_Message() + let m2 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularGroup = .with { $0.groupField = 1 } + } + // should do nothing with json path (should not merge) + try m1.merge(from: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) + XCTAssertNotEqual(m1.singularGroup, m2.singularGroup) + // should merge with proto path + try m1.merge(from: m2, fieldMask: .with({ $0.paths = ["SingularGroup"] })) + XCTAssertEqual(m1.singularGroup, m2.singularGroup) + // should do nothing with json path (do not clear field) + try m1.merge(from: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) + XCTAssertEqual(m1.singularGroup, m2.singularGroup) + } }