From c889293dc0f323166631db343ee06334f690e4e1 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Sat, 25 Nov 2023 22:11:49 +0330 Subject: [PATCH 01/33] Add applying and tests --- Sources/SwiftProtobuf/Applying.swift | 307 +++++++++++++++++++ Tests/SwiftProtobufTests/Test_Applying.swift | 78 +++++ 2 files changed, 385 insertions(+) create mode 100644 Sources/SwiftProtobuf/Applying.swift create mode 100644 Tests/SwiftProtobufTests/Test_Applying.swift diff --git a/Sources/SwiftProtobuf/Applying.swift b/Sources/SwiftProtobuf/Applying.swift new file mode 100644 index 000000000..4348c0dad --- /dev/null +++ b/Sources/SwiftProtobuf/Applying.swift @@ -0,0 +1,307 @@ +// +// Sources/SwiftProtobuf/Applying.swift - Applying protocol and errors +// +// Copyright (c) 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 +// +// ----------------------------------------------------------------------------- +/// +/// Applying feature (including decoder and errors) +/// +// ----------------------------------------------------------------------------- + +import Foundation + +/// Describes errors can occure during applying a value to proto +public enum ProtoApplyingError: Error { + + /// Describes a mismatch in type of the field + /// + /// If a value of type A is applied to a fieldNumber with type B + /// this error will be thrown by the applying() method. + case typeMismatch +} + +internal struct ApplyingDecoder: Decoder { + + private var _fieldNumber: Int? + private var _value: Any + + init(fieldNumber: Int, value: Any) { + self._fieldNumber = fieldNumber + self._value = value + } + + mutating func handleConflictingOneOf() throws {} + + mutating func nextFieldNumber() throws -> Int? { + if let fieldNumber = _fieldNumber { + _fieldNumber = nil + return fieldNumber + } + return nil + } + + private func _value(as type: T.Type) throws -> T { + guard let __value = _value as? T else { + throw ProtoApplyingError.typeMismatch + } + return __value + } + + mutating func decodeSingularFloatField(value: inout Float) throws { + value = try _value(as: Float.self) + } + + mutating func decodeSingularFloatField(value: inout Float?) throws { + value = try _value(as: Float?.self) + } + + mutating func decodeRepeatedFloatField(value: inout [Float]) throws { + value = try _value(as: [Float].self) + } + + mutating func decodeSingularDoubleField(value: inout Double) throws { + value = try _value(as: Double.self) + } + + mutating func decodeSingularDoubleField(value: inout Double?) throws { + value = try _value(as: Double?.self) + } + + mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { + value = try _value(as: [Double].self) + } + + mutating func decodeSingularInt32Field(value: inout Int32) throws { + value = try _value(as: Int32.self) + } + + mutating func decodeSingularInt32Field(value: inout Int32?) throws { + value = try _value(as: Int32?.self) + } + + mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { + value = try _value(as: [Int32].self) + } + + mutating func decodeSingularInt64Field(value: inout Int64) throws { + value = try _value(as: Int64.self) + } + + mutating func decodeSingularInt64Field(value: inout Int64?) throws { + value = try _value(as: Int64?.self) + } + + mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { + value = try _value(as: [Int64].self) + } + + mutating func decodeSingularUInt32Field(value: inout UInt32) throws { + value = try _value(as: UInt32.self) + } + + mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { + value = try _value(as: UInt32?.self) + } + + mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { + value = try _value(as: [UInt32].self) + } + + mutating func decodeSingularUInt64Field(value: inout UInt64) throws { + value = try _value(as: UInt64.self) + } + + mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { + value = try _value(as: UInt64?.self) + } + + mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { + value = try _value(as: [UInt64].self) + } + + mutating func decodeSingularSInt32Field(value: inout Int32) throws { + value = try _value(as: Int32.self) + } + + mutating func decodeSingularSInt32Field(value: inout Int32?) throws { + value = try _value(as: Int32?.self) + } + + mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { + value = try _value(as: [Int32].self) + } + + mutating func decodeSingularSInt64Field(value: inout Int64) throws { + value = try _value(as: Int64.self) + } + + mutating func decodeSingularSInt64Field(value: inout Int64?) throws { + value = try _value(as: Int64?.self) + } + + mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { + value = try _value(as: [Int64].self) + } + + mutating func decodeSingularFixed32Field(value: inout UInt32) throws { + value = try _value(as: UInt32.self) + } + + mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { + value = try _value(as: UInt32?.self) + } + + mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { + value = try _value(as: [UInt32].self) + } + + mutating func decodeSingularFixed64Field(value: inout UInt64) throws { + value = try _value(as: UInt64.self) + } + + mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { + value = try _value(as: UInt64?.self) + } + + mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { + value = try _value(as: [UInt64].self) + } + + mutating func decodeSingularSFixed32Field(value: inout Int32) throws { + value = try _value(as: Int32.self) + } + + mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { + value = try _value(as: Int32?.self) + } + + mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { + value = try _value(as: [Int32].self) + } + + mutating func decodeSingularSFixed64Field(value: inout Int64) throws { + value = try _value(as: Int64.self) + } + + mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { + value = try _value(as: Int64?.self) + } + + mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { + value = try _value(as: [Int64].self) + } + + mutating func decodeSingularBoolField(value: inout Bool) throws { + value = try _value(as: Bool.self) + } + + mutating func decodeSingularBoolField(value: inout Bool?) throws { + value = try _value(as: Bool?.self) + } + + mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { + value = try _value(as: [Bool].self) + } + + mutating func decodeSingularStringField(value: inout String) throws { + value = try _value(as: String.self) + } + + mutating func decodeSingularStringField(value: inout String?) throws { + value = try _value(as: String?.self) + } + + mutating func decodeRepeatedStringField(value: inout [String]) throws { + value = try _value(as: [String].self) + } + + mutating func decodeSingularBytesField(value: inout Data) throws { + value = try _value(as: Data.self) + } + + mutating func decodeSingularBytesField(value: inout Data?) throws { + value = try _value(as: Data?.self) + } + + mutating func decodeRepeatedBytesField(value: inout [Data]) throws { + value = try _value(as: [Data].self) + } + + mutating func decodeSingularEnumField(value: inout E) throws { + value = try _value(as: E.self) + } + + mutating func decodeSingularEnumField(value: inout E?) throws { + value = try _value(as: E?.self) + } + + mutating func decodeRepeatedEnumField(value: inout [E]) throws { + value = try _value(as: [E].self) + } + + mutating func decodeSingularMessageField(value: inout M?) throws { + value = try _value(as: M?.self) + } + + mutating func decodeRepeatedMessageField(value: inout [M]) throws { + value = try _value(as: [M].self) + } + + mutating func decodeSingularGroupField(value: inout G?) throws { + value = try _value(as: G?.self) + } + + mutating func decodeRepeatedGroupField(value: inout [G]) throws { + value = try _value(as: [G].self) + } + + mutating func decodeMapField( + fieldType: _ProtobufMap.Type, + value: inout _ProtobufMap.BaseType + ) throws { + value = try _value(as: _ProtobufMap.BaseType.self) + } + + mutating func decodeMapField( + fieldType: _ProtobufEnumMap.Type, + value: inout _ProtobufEnumMap.BaseType + ) throws { + value = try _value(as: _ProtobufEnumMap.BaseType.self) + } + + mutating func decodeMapField( + fieldType: _ProtobufMessageMap.Type, + value: inout _ProtobufMessageMap.BaseType + ) throws { + value = try _value(as: _ProtobufMessageMap.BaseType.self) + } + + mutating func decodeExtensionField( + values: inout ExtensionFieldValueSet, + messageType: Message.Type, + fieldNumber: Int + ) throws { + try values.modify(index: fieldNumber) { ext in + try ext?.decodeExtensionField(decoder: &self) + } + } + +} + +public extension Message { + func applying(_ value: Any, for fieldNumber: Int) throws -> Self { + var copy = self + try copy.apply(value, for: fieldNumber) + return copy + } + + mutating func apply(_ value: Any, for fieldNumber: Int) throws { + var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) + try decodeMessage(decoder: &decoder) + } +} diff --git a/Tests/SwiftProtobufTests/Test_Applying.swift b/Tests/SwiftProtobufTests/Test_Applying.swift new file mode 100644 index 000000000..b961af1f8 --- /dev/null +++ b/Tests/SwiftProtobufTests/Test_Applying.swift @@ -0,0 +1,78 @@ +// Tests/SwiftProtobufTests/Test_Applying.swift - Applying method +// +// Copyright (c) 2014 - 2016 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 +// +// ----------------------------------------------------------------------------- + +import Foundation +import XCTest +import SwiftProtobuf + +final class Test_Applying: XCTestCase { + + func testApplying() throws { + let message = SwiftProtoTesting_TestAny() + let newMessage = try message.applying(Int32(2), for: 1) + XCTAssertEqual(newMessage.int32Value, 2) + } + + func testApply() throws { + var message = SwiftProtoTesting_TestAny() + try message.apply(Int32(2), for: 1) + XCTAssertEqual(message.int32Value, 2) + } + + func testApplyingSequentially() throws { + let message = SwiftProtoTesting_TestAny() + let newMessage = try message + .applying(Int32(2), for: 1) + .applying("test", for: 4) + XCTAssertEqual(newMessage.int32Value, 2) + XCTAssertEqual(newMessage.text, "test") + } + + func testApplyingMismatchType() throws { + let message = SwiftProtoTesting_TestAny() + XCTAssertThrowsError(try message.applying("", for: 1)) + } + + func testApplyMismatchType() throws { + var message = SwiftProtoTesting_TestAny() + XCTAssertThrowsError(try message.apply("", for: 1)) + } + + func testApplyingOneof() throws { + let message = SwiftProtoTesting_TestOneof.with { oneof in + oneof.foo = .fooInt(1) + } + let newMessage = try message.applying("oneof", for: 2) + XCTAssertEqual(newMessage.foo, .fooString("oneof")) + } + + func testApplyOneof() throws { + var message = SwiftProtoTesting_TestOneof.with { oneof in + oneof.foo = .fooInt(1) + } + try message.apply("oneof", for: 2) + XCTAssertEqual(message.foo, .fooString("oneof")) + } + + func testApplyingExtension() throws { + var message = SwiftProtoTesting_Extend_Msg1() + message.SwiftProtoTesting_Extend_aB = 0 + let newMessage = try message.applying(Int32(2), for: 1) + XCTAssertEqual(newMessage.SwiftProtoTesting_Extend_aB, 2) + } + + func testApplyExtension() throws { + var message = SwiftProtoTesting_Extend_Msg1() + message.SwiftProtoTesting_Extend_aB = 0 + try message.apply(Int32(2), for: 1) + XCTAssertEqual(message.SwiftProtoTesting_Extend_aB, 2) + } + +} From f01f29c38fe6233690f6c81fe6ade5bd2aba1a5d Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Mon, 27 Nov 2023 22:42:04 +0330 Subject: [PATCH 02/33] Move extension to another file --- Sources/SwiftProtobuf/Applying.swift | 13 ------- Sources/SwiftProtobuf/Message+Applying.swift | 40 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 Sources/SwiftProtobuf/Message+Applying.swift diff --git a/Sources/SwiftProtobuf/Applying.swift b/Sources/SwiftProtobuf/Applying.swift index 4348c0dad..4aa56393b 100644 --- a/Sources/SwiftProtobuf/Applying.swift +++ b/Sources/SwiftProtobuf/Applying.swift @@ -292,16 +292,3 @@ internal struct ApplyingDecoder: Decoder { } } - -public extension Message { - func applying(_ value: Any, for fieldNumber: Int) throws -> Self { - var copy = self - try copy.apply(value, for: fieldNumber) - return copy - } - - mutating func apply(_ value: Any, for fieldNumber: Int) throws { - var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) - try decodeMessage(decoder: &decoder) - } -} diff --git a/Sources/SwiftProtobuf/Message+Applying.swift b/Sources/SwiftProtobuf/Message+Applying.swift new file mode 100644 index 000000000..dead02a95 --- /dev/null +++ b/Sources/SwiftProtobuf/Message+Applying.swift @@ -0,0 +1,40 @@ +// Sources/SwiftProtobuf/Message+Applying.swift - Applying feature +// +// Copyright (c) 2014 - 2017 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 +// +// ----------------------------------------------------------------------------- +/// +/// Extends the `Message` type with applying behavior. +/// +// ----------------------------------------------------------------------------- + +public extension Message { + + /// Applies a value for a specific `fieldNumber` of the message + /// and returns a copy of it. + /// + /// - Parameters: + /// - value: The value to be applied. + /// - fieldNumber: Protobuf index of the field that the value should be applied for. + /// - Returns: A copy of the message with applied value. + func applying(_ value: Any, for fieldNumber: Int) throws -> Self { + var copy = self + try copy.apply(value, for: fieldNumber) + return copy + } + + /// Applies a value for a specific `fieldNumber` of the message without + /// making a copy. This method mutates the original message. + /// + /// - Parameters: + /// - value: The value to be applied. + /// - fieldNumber: Protobuf index of the field that the value should be applied for. + mutating func apply(_ value: Any, for fieldNumber: Int) throws { + var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) + try decodeMessage(decoder: &decoder) + } +} From eaa06664d65232527178eb56c6959b037b0bfa99 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Mon, 27 Nov 2023 22:54:06 +0330 Subject: [PATCH 03/33] Some minor changes in comments --- Sources/SwiftProtobuf/CMakeLists.txt | 2 ++ Sources/SwiftProtobuf/Message+Applying.swift | 26 ++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftProtobuf/CMakeLists.txt b/Sources/SwiftProtobuf/CMakeLists.txt index 74816c320..245bf3146 100644 --- a/Sources/SwiftProtobuf/CMakeLists.txt +++ b/Sources/SwiftProtobuf/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(SwiftProtobuf AnyMessageStorage.swift AnyUnpackError.swift api.pb.swift + Applying.swift BinaryDecoder.swift BinaryDecodingError.swift BinaryDecodingOptions.swift @@ -49,6 +50,7 @@ add_library(SwiftProtobuf JSONScanner.swift MathUtils.swift Message+AnyAdditions.swift + Message+Applying.swift Message+BinaryAdditions.swift Message+JSONAdditions.swift Message+JSONArrayAdditions.swift diff --git a/Sources/SwiftProtobuf/Message+Applying.swift b/Sources/SwiftProtobuf/Message+Applying.swift index dead02a95..18aeb21f6 100644 --- a/Sources/SwiftProtobuf/Message+Applying.swift +++ b/Sources/SwiftProtobuf/Message+Applying.swift @@ -12,29 +12,39 @@ /// // ----------------------------------------------------------------------------- -public extension Message { +/// Applying a value to a field of the message by its field number +extension Message { - /// Applies a value for a specific `fieldNumber` of the message - /// and returns a copy of it. + /// Applies a value for a specific `fieldNumber` of the message and returns a copy + /// of the original message. /// /// - Parameters: /// - value: The value to be applied. /// - fieldNumber: Protobuf index of the field that the value should be applied for. /// - Returns: A copy of the message with applied value. - func applying(_ value: Any, for fieldNumber: Int) throws -> Self { + public func applying( + _ value: Any, + for fieldNumber: Int + ) throws -> Self { var copy = self try copy.apply(value, for: fieldNumber) return copy } - /// Applies a value for a specific `fieldNumber` of the message without - /// making a copy. This method mutates the original message. + /// Applies a value for a specific `fieldNumber` of the message without making a + /// copy. This method mutates the original message. /// /// - Parameters: /// - value: The value to be applied. /// - fieldNumber: Protobuf index of the field that the value should be applied for. - mutating func apply(_ value: Any, for fieldNumber: Int) throws { - var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) + public mutating func apply( + _ value: Any, + for fieldNumber: Int + ) throws { + var decoder = ApplyingDecoder( + fieldNumber: fieldNumber, + value: value + ) try decodeMessage(decoder: &decoder) } } From 9e33a9bc21a9f228927f392db3b43298eaf5bfbd Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 1 Dec 2023 14:51:10 +0330 Subject: [PATCH 04/33] Revert "Some minor changes in comments" This reverts commit eaa06664d65232527178eb56c6959b037b0bfa99. --- Sources/SwiftProtobuf/CMakeLists.txt | 2 -- Sources/SwiftProtobuf/Message+Applying.swift | 26 ++++++-------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Sources/SwiftProtobuf/CMakeLists.txt b/Sources/SwiftProtobuf/CMakeLists.txt index 245bf3146..74816c320 100644 --- a/Sources/SwiftProtobuf/CMakeLists.txt +++ b/Sources/SwiftProtobuf/CMakeLists.txt @@ -3,7 +3,6 @@ add_library(SwiftProtobuf AnyMessageStorage.swift AnyUnpackError.swift api.pb.swift - Applying.swift BinaryDecoder.swift BinaryDecodingError.swift BinaryDecodingOptions.swift @@ -50,7 +49,6 @@ add_library(SwiftProtobuf JSONScanner.swift MathUtils.swift Message+AnyAdditions.swift - Message+Applying.swift Message+BinaryAdditions.swift Message+JSONAdditions.swift Message+JSONArrayAdditions.swift diff --git a/Sources/SwiftProtobuf/Message+Applying.swift b/Sources/SwiftProtobuf/Message+Applying.swift index 18aeb21f6..dead02a95 100644 --- a/Sources/SwiftProtobuf/Message+Applying.swift +++ b/Sources/SwiftProtobuf/Message+Applying.swift @@ -12,39 +12,29 @@ /// // ----------------------------------------------------------------------------- -/// Applying a value to a field of the message by its field number -extension Message { +public extension Message { - /// Applies a value for a specific `fieldNumber` of the message and returns a copy - /// of the original message. + /// Applies a value for a specific `fieldNumber` of the message + /// and returns a copy of it. /// /// - Parameters: /// - value: The value to be applied. /// - fieldNumber: Protobuf index of the field that the value should be applied for. /// - Returns: A copy of the message with applied value. - public func applying( - _ value: Any, - for fieldNumber: Int - ) throws -> Self { + func applying(_ value: Any, for fieldNumber: Int) throws -> Self { var copy = self try copy.apply(value, for: fieldNumber) return copy } - /// Applies a value for a specific `fieldNumber` of the message without making a - /// copy. This method mutates the original message. + /// Applies a value for a specific `fieldNumber` of the message without + /// making a copy. This method mutates the original message. /// /// - Parameters: /// - value: The value to be applied. /// - fieldNumber: Protobuf index of the field that the value should be applied for. - public mutating func apply( - _ value: Any, - for fieldNumber: Int - ) throws { - var decoder = ApplyingDecoder( - fieldNumber: fieldNumber, - value: value - ) + mutating func apply(_ value: Any, for fieldNumber: Int) throws { + var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) try decodeMessage(decoder: &decoder) } } From b728a338234c349b6be2e3a73f7fe65cd7721dc7 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 1 Dec 2023 14:51:15 +0330 Subject: [PATCH 05/33] Revert "Move extension to another file" This reverts commit f01f29c38fe6233690f6c81fe6ade5bd2aba1a5d. --- Sources/SwiftProtobuf/Applying.swift | 13 +++++++ Sources/SwiftProtobuf/Message+Applying.swift | 40 -------------------- 2 files changed, 13 insertions(+), 40 deletions(-) delete mode 100644 Sources/SwiftProtobuf/Message+Applying.swift diff --git a/Sources/SwiftProtobuf/Applying.swift b/Sources/SwiftProtobuf/Applying.swift index 4aa56393b..4348c0dad 100644 --- a/Sources/SwiftProtobuf/Applying.swift +++ b/Sources/SwiftProtobuf/Applying.swift @@ -292,3 +292,16 @@ internal struct ApplyingDecoder: Decoder { } } + +public extension Message { + func applying(_ value: Any, for fieldNumber: Int) throws -> Self { + var copy = self + try copy.apply(value, for: fieldNumber) + return copy + } + + mutating func apply(_ value: Any, for fieldNumber: Int) throws { + var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) + try decodeMessage(decoder: &decoder) + } +} diff --git a/Sources/SwiftProtobuf/Message+Applying.swift b/Sources/SwiftProtobuf/Message+Applying.swift deleted file mode 100644 index dead02a95..000000000 --- a/Sources/SwiftProtobuf/Message+Applying.swift +++ /dev/null @@ -1,40 +0,0 @@ -// Sources/SwiftProtobuf/Message+Applying.swift - Applying feature -// -// Copyright (c) 2014 - 2017 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 -// -// ----------------------------------------------------------------------------- -/// -/// Extends the `Message` type with applying behavior. -/// -// ----------------------------------------------------------------------------- - -public extension Message { - - /// Applies a value for a specific `fieldNumber` of the message - /// and returns a copy of it. - /// - /// - Parameters: - /// - value: The value to be applied. - /// - fieldNumber: Protobuf index of the field that the value should be applied for. - /// - Returns: A copy of the message with applied value. - func applying(_ value: Any, for fieldNumber: Int) throws -> Self { - var copy = self - try copy.apply(value, for: fieldNumber) - return copy - } - - /// Applies a value for a specific `fieldNumber` of the message without - /// making a copy. This method mutates the original message. - /// - /// - Parameters: - /// - value: The value to be applied. - /// - fieldNumber: Protobuf index of the field that the value should be applied for. - mutating func apply(_ value: Any, for fieldNumber: Int) throws { - var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) - try decodeMessage(decoder: &decoder) - } -} From 634ca09821efe9dd4cd46eb4639ea3a862d0a031 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 1 Dec 2023 14:51:16 +0330 Subject: [PATCH 06/33] Revert "Add applying and tests" This reverts commit c889293dc0f323166631db343ee06334f690e4e1. --- Sources/SwiftProtobuf/Applying.swift | 307 ------------------- Tests/SwiftProtobufTests/Test_Applying.swift | 78 ----- 2 files changed, 385 deletions(-) delete mode 100644 Sources/SwiftProtobuf/Applying.swift delete mode 100644 Tests/SwiftProtobufTests/Test_Applying.swift diff --git a/Sources/SwiftProtobuf/Applying.swift b/Sources/SwiftProtobuf/Applying.swift deleted file mode 100644 index 4348c0dad..000000000 --- a/Sources/SwiftProtobuf/Applying.swift +++ /dev/null @@ -1,307 +0,0 @@ -// -// Sources/SwiftProtobuf/Applying.swift - Applying protocol and errors -// -// Copyright (c) 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 -// -// ----------------------------------------------------------------------------- -/// -/// Applying feature (including decoder and errors) -/// -// ----------------------------------------------------------------------------- - -import Foundation - -/// Describes errors can occure during applying a value to proto -public enum ProtoApplyingError: Error { - - /// Describes a mismatch in type of the field - /// - /// If a value of type A is applied to a fieldNumber with type B - /// this error will be thrown by the applying() method. - case typeMismatch -} - -internal struct ApplyingDecoder: Decoder { - - private var _fieldNumber: Int? - private var _value: Any - - init(fieldNumber: Int, value: Any) { - self._fieldNumber = fieldNumber - self._value = value - } - - mutating func handleConflictingOneOf() throws {} - - mutating func nextFieldNumber() throws -> Int? { - if let fieldNumber = _fieldNumber { - _fieldNumber = nil - return fieldNumber - } - return nil - } - - private func _value(as type: T.Type) throws -> T { - guard let __value = _value as? T else { - throw ProtoApplyingError.typeMismatch - } - return __value - } - - mutating func decodeSingularFloatField(value: inout Float) throws { - value = try _value(as: Float.self) - } - - mutating func decodeSingularFloatField(value: inout Float?) throws { - value = try _value(as: Float?.self) - } - - mutating func decodeRepeatedFloatField(value: inout [Float]) throws { - value = try _value(as: [Float].self) - } - - mutating func decodeSingularDoubleField(value: inout Double) throws { - value = try _value(as: Double.self) - } - - mutating func decodeSingularDoubleField(value: inout Double?) throws { - value = try _value(as: Double?.self) - } - - mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { - value = try _value(as: [Double].self) - } - - mutating func decodeSingularInt32Field(value: inout Int32) throws { - value = try _value(as: Int32.self) - } - - mutating func decodeSingularInt32Field(value: inout Int32?) throws { - value = try _value(as: Int32?.self) - } - - mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { - value = try _value(as: [Int32].self) - } - - mutating func decodeSingularInt64Field(value: inout Int64) throws { - value = try _value(as: Int64.self) - } - - mutating func decodeSingularInt64Field(value: inout Int64?) throws { - value = try _value(as: Int64?.self) - } - - mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { - value = try _value(as: [Int64].self) - } - - mutating func decodeSingularUInt32Field(value: inout UInt32) throws { - value = try _value(as: UInt32.self) - } - - mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { - value = try _value(as: UInt32?.self) - } - - mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { - value = try _value(as: [UInt32].self) - } - - mutating func decodeSingularUInt64Field(value: inout UInt64) throws { - value = try _value(as: UInt64.self) - } - - mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { - value = try _value(as: UInt64?.self) - } - - mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { - value = try _value(as: [UInt64].self) - } - - mutating func decodeSingularSInt32Field(value: inout Int32) throws { - value = try _value(as: Int32.self) - } - - mutating func decodeSingularSInt32Field(value: inout Int32?) throws { - value = try _value(as: Int32?.self) - } - - mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { - value = try _value(as: [Int32].self) - } - - mutating func decodeSingularSInt64Field(value: inout Int64) throws { - value = try _value(as: Int64.self) - } - - mutating func decodeSingularSInt64Field(value: inout Int64?) throws { - value = try _value(as: Int64?.self) - } - - mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { - value = try _value(as: [Int64].self) - } - - mutating func decodeSingularFixed32Field(value: inout UInt32) throws { - value = try _value(as: UInt32.self) - } - - mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { - value = try _value(as: UInt32?.self) - } - - mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { - value = try _value(as: [UInt32].self) - } - - mutating func decodeSingularFixed64Field(value: inout UInt64) throws { - value = try _value(as: UInt64.self) - } - - mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { - value = try _value(as: UInt64?.self) - } - - mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { - value = try _value(as: [UInt64].self) - } - - mutating func decodeSingularSFixed32Field(value: inout Int32) throws { - value = try _value(as: Int32.self) - } - - mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { - value = try _value(as: Int32?.self) - } - - mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { - value = try _value(as: [Int32].self) - } - - mutating func decodeSingularSFixed64Field(value: inout Int64) throws { - value = try _value(as: Int64.self) - } - - mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { - value = try _value(as: Int64?.self) - } - - mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { - value = try _value(as: [Int64].self) - } - - mutating func decodeSingularBoolField(value: inout Bool) throws { - value = try _value(as: Bool.self) - } - - mutating func decodeSingularBoolField(value: inout Bool?) throws { - value = try _value(as: Bool?.self) - } - - mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { - value = try _value(as: [Bool].self) - } - - mutating func decodeSingularStringField(value: inout String) throws { - value = try _value(as: String.self) - } - - mutating func decodeSingularStringField(value: inout String?) throws { - value = try _value(as: String?.self) - } - - mutating func decodeRepeatedStringField(value: inout [String]) throws { - value = try _value(as: [String].self) - } - - mutating func decodeSingularBytesField(value: inout Data) throws { - value = try _value(as: Data.self) - } - - mutating func decodeSingularBytesField(value: inout Data?) throws { - value = try _value(as: Data?.self) - } - - mutating func decodeRepeatedBytesField(value: inout [Data]) throws { - value = try _value(as: [Data].self) - } - - mutating func decodeSingularEnumField(value: inout E) throws { - value = try _value(as: E.self) - } - - mutating func decodeSingularEnumField(value: inout E?) throws { - value = try _value(as: E?.self) - } - - mutating func decodeRepeatedEnumField(value: inout [E]) throws { - value = try _value(as: [E].self) - } - - mutating func decodeSingularMessageField(value: inout M?) throws { - value = try _value(as: M?.self) - } - - mutating func decodeRepeatedMessageField(value: inout [M]) throws { - value = try _value(as: [M].self) - } - - mutating func decodeSingularGroupField(value: inout G?) throws { - value = try _value(as: G?.self) - } - - mutating func decodeRepeatedGroupField(value: inout [G]) throws { - value = try _value(as: [G].self) - } - - mutating func decodeMapField( - fieldType: _ProtobufMap.Type, - value: inout _ProtobufMap.BaseType - ) throws { - value = try _value(as: _ProtobufMap.BaseType.self) - } - - mutating func decodeMapField( - fieldType: _ProtobufEnumMap.Type, - value: inout _ProtobufEnumMap.BaseType - ) throws { - value = try _value(as: _ProtobufEnumMap.BaseType.self) - } - - mutating func decodeMapField( - fieldType: _ProtobufMessageMap.Type, - value: inout _ProtobufMessageMap.BaseType - ) throws { - value = try _value(as: _ProtobufMessageMap.BaseType.self) - } - - mutating func decodeExtensionField( - values: inout ExtensionFieldValueSet, - messageType: Message.Type, - fieldNumber: Int - ) throws { - try values.modify(index: fieldNumber) { ext in - try ext?.decodeExtensionField(decoder: &self) - } - } - -} - -public extension Message { - func applying(_ value: Any, for fieldNumber: Int) throws -> Self { - var copy = self - try copy.apply(value, for: fieldNumber) - return copy - } - - mutating func apply(_ value: Any, for fieldNumber: Int) throws { - var decoder = ApplyingDecoder(fieldNumber: fieldNumber, value: value) - try decodeMessage(decoder: &decoder) - } -} diff --git a/Tests/SwiftProtobufTests/Test_Applying.swift b/Tests/SwiftProtobufTests/Test_Applying.swift deleted file mode 100644 index b961af1f8..000000000 --- a/Tests/SwiftProtobufTests/Test_Applying.swift +++ /dev/null @@ -1,78 +0,0 @@ -// Tests/SwiftProtobufTests/Test_Applying.swift - Applying method -// -// Copyright (c) 2014 - 2016 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 -// -// ----------------------------------------------------------------------------- - -import Foundation -import XCTest -import SwiftProtobuf - -final class Test_Applying: XCTestCase { - - func testApplying() throws { - let message = SwiftProtoTesting_TestAny() - let newMessage = try message.applying(Int32(2), for: 1) - XCTAssertEqual(newMessage.int32Value, 2) - } - - func testApply() throws { - var message = SwiftProtoTesting_TestAny() - try message.apply(Int32(2), for: 1) - XCTAssertEqual(message.int32Value, 2) - } - - func testApplyingSequentially() throws { - let message = SwiftProtoTesting_TestAny() - let newMessage = try message - .applying(Int32(2), for: 1) - .applying("test", for: 4) - XCTAssertEqual(newMessage.int32Value, 2) - XCTAssertEqual(newMessage.text, "test") - } - - func testApplyingMismatchType() throws { - let message = SwiftProtoTesting_TestAny() - XCTAssertThrowsError(try message.applying("", for: 1)) - } - - func testApplyMismatchType() throws { - var message = SwiftProtoTesting_TestAny() - XCTAssertThrowsError(try message.apply("", for: 1)) - } - - func testApplyingOneof() throws { - let message = SwiftProtoTesting_TestOneof.with { oneof in - oneof.foo = .fooInt(1) - } - let newMessage = try message.applying("oneof", for: 2) - XCTAssertEqual(newMessage.foo, .fooString("oneof")) - } - - func testApplyOneof() throws { - var message = SwiftProtoTesting_TestOneof.with { oneof in - oneof.foo = .fooInt(1) - } - try message.apply("oneof", for: 2) - XCTAssertEqual(message.foo, .fooString("oneof")) - } - - func testApplyingExtension() throws { - var message = SwiftProtoTesting_Extend_Msg1() - message.SwiftProtoTesting_Extend_aB = 0 - let newMessage = try message.applying(Int32(2), for: 1) - XCTAssertEqual(newMessage.SwiftProtoTesting_Extend_aB, 2) - } - - func testApplyExtension() throws { - var message = SwiftProtoTesting_Extend_Msg1() - message.SwiftProtoTesting_Extend_aB = 0 - try message.apply(Int32(2), for: 1) - XCTAssertEqual(message.SwiftProtoTesting_Extend_aB, 2) - } - -} From ad59804de916b789062e5d2c912f37e22dbd9b15 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Sun, 3 Dec 2023 17:30:22 +0330 Subject: [PATCH 07/33] Implement field mask utils --- Sources/SwiftProtobuf/GetPathDecoder.swift | 283 +++++++++++++++ ...Google_Protobuf_FieldMask+Extensions.swift | 57 +++ Sources/SwiftProtobuf/Message+FieldMask.swift | 77 ++++ Sources/SwiftProtobuf/NameMap.swift | 7 + Sources/SwiftProtobuf/SetPathDecoder.swift | 330 ++++++++++++++++++ Tests/SwiftProtobufTests/Test_FieldMask.swift | 102 +++++- 6 files changed, 854 insertions(+), 2 deletions(-) create mode 100644 Sources/SwiftProtobuf/GetPathDecoder.swift create mode 100644 Sources/SwiftProtobuf/Message+FieldMask.swift create mode 100644 Sources/SwiftProtobuf/SetPathDecoder.swift diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift new file mode 100644 index 000000000..1ff7104cb --- /dev/null +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -0,0 +1,283 @@ +// Sources/SwiftProtobuf/GetPathDecoder.swift - Path decoder (Getter) +// +// Copyright (c) 2014 - 2016 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 captures value of a field by its path. +/// +// ----------------------------------------------------------------------------- + +import Foundation + +struct GetPathDecoder: Decoder { + + private let path: String + private(set) var value: Any? + private var number: Int? + + init(path: String) { + self.path = path + if let firstPathComponent { + self.number = T.number(for: firstPathComponent) + } + } + + var firstPathComponent: String? { + path.components(separatedBy: ".").first + } + + var nextPath: String { + path.components(separatedBy: ".").dropFirst().joined(separator: ".") + } + + mutating func handleConflictingOneOf() throws {} + + mutating func nextFieldNumber() throws -> Int? { + defer { number = nil } + return number + } + + mutating func decodeSingularFloatField(value: inout Float) throws { + self.value = value + } + + mutating func decodeSingularFloatField(value: inout Float?) throws { + self.value = value + } + + mutating func decodeRepeatedFloatField(value: inout [Float]) throws { + self.value = value + } + + mutating func decodeSingularDoubleField(value: inout Double) throws { + self.value = value + } + + mutating func decodeSingularDoubleField(value: inout Double?) throws { + self.value = value + } + + mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { + self.value = value + } + + mutating func decodeSingularInt32Field(value: inout Int32) throws { + self.value = value + } + + mutating func decodeSingularInt32Field(value: inout Int32?) throws { + self.value = value + } + + mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { + self.value = value + } + + mutating func decodeSingularInt64Field(value: inout Int64) throws { + self.value = value + } + + mutating func decodeSingularInt64Field(value: inout Int64?) throws { + self.value = value + } + + mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { + self.value = value + } + + mutating func decodeSingularUInt32Field(value: inout UInt32) throws { + self.value = value + } + + mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { + self.value = value + } + + mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { + self.value = value + } + + mutating func decodeSingularUInt64Field(value: inout UInt64) throws { + self.value = value + } + + mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { + self.value = value + } + + mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { + self.value = value + } + + mutating func decodeSingularSInt32Field(value: inout Int32) throws { + self.value = value + } + + mutating func decodeSingularSInt32Field(value: inout Int32?) throws { + self.value = value + } + + mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { + self.value = value + } + + mutating func decodeSingularSInt64Field(value: inout Int64) throws { + self.value = value + } + + mutating func decodeSingularSInt64Field(value: inout Int64?) throws { + self.value = value + } + + mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { + self.value = value + } + + mutating func decodeSingularFixed32Field(value: inout UInt32) throws { + self.value = value + } + + mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { + self.value = value + } + + mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { + self.value = value + } + + mutating func decodeSingularFixed64Field(value: inout UInt64) throws { + self.value = value + } + + mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { + self.value = value + } + + mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { + self.value = value + } + + mutating func decodeSingularSFixed32Field(value: inout Int32) throws { + self.value = value + } + + mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { + self.value = value + } + + mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { + self.value = value + } + + mutating func decodeSingularSFixed64Field(value: inout Int64) throws { + self.value = value + } + + mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { + self.value = value + } + + mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { + self.value = value + } + + mutating func decodeSingularBoolField(value: inout Bool) throws { + self.value = value + } + + mutating func decodeSingularBoolField(value: inout Bool?) throws { + self.value = value + } + + mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { + self.value = value + } + + mutating func decodeSingularStringField(value: inout String) throws { + self.value = value + } + + mutating func decodeSingularStringField(value: inout String?) throws { + self.value = value + } + + mutating func decodeRepeatedStringField(value: inout [String]) throws { + self.value = value + } + + mutating func decodeSingularBytesField(value: inout Data) throws { + self.value = value + } + + mutating func decodeSingularBytesField(value: inout Data?) throws { + self.value = value + } + + mutating func decodeRepeatedBytesField(value: inout [Data]) throws { + self.value = value + } + + mutating func decodeSingularEnumField(value: inout E) throws where E : Enum, E.RawValue == Int { + self.value = value + } + + mutating func decodeSingularEnumField(value: inout E?) throws where E : Enum, E.RawValue == Int { + self.value = value + } + + mutating func decodeRepeatedEnumField(value: inout [E]) throws where E : Enum, E.RawValue == Int { + self.value = value + } + + mutating func decodeSingularMessageField(value: inout M?) throws where M : Message { + if nextPath.isEmpty { + self.value = value + return + } + var decoder = GetPathDecoder(path: nextPath) + try value?.decodeMessage(decoder: &decoder) + self.value = decoder.value + } + + mutating func decodeRepeatedMessageField(value: inout [M]) throws where M : Message { + self.value = value + } + + mutating func decodeSingularGroupField(value: inout G?) throws where G : Message { + self.value = value + } + + mutating func decodeRepeatedGroupField(value: inout [G]) throws where G : Message { + self.value = value + } + + mutating func decodeMapField(fieldType: _ProtobufMap.Type, value: inout _ProtobufMap.BaseType) throws where KeyType : MapKeyType, ValueType : MapValueType { + self.value = value + } + + mutating func decodeMapField(fieldType: _ProtobufEnumMap.Type, value: inout _ProtobufEnumMap.BaseType) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int { + self.value = value + } + + mutating func decodeMapField(fieldType: _ProtobufMessageMap.Type, value: inout _ProtobufMessageMap.BaseType) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : Message { + self.value = value + } + + mutating func decodeExtensionField(values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int) throws {} + +} + +extension Message { + func `get`(path: String) throws -> Any? { + var copy = self + var decoder = GetPathDecoder(path: path) + try copy.decodeMessage(decoder: &decoder) + return decoder.value + } +} + diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 1d1e7f73c..764234b52 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -184,3 +184,60 @@ 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( + from messageType: M.Type + ) { + let paths = M._protobuf_nameMap.names.map(\.description) + self = .with { mask in + mask.paths = paths + } + } + + /// 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. + public init( + from messageType: M.Type, + fieldNumbers: [Int] + ) { + let paths = fieldNumbers.compactMap { number in + M._protobuf_nameMap.names(for: number)?.proto.description + } + self = .with { mask in + mask.paths = paths + } + } + + /// Initiates a field mask by excluding some paths + /// - Parameters: + /// - messageType: Message type to get all paths from. + /// - paths: Paths to be excluded. + /// - Returns: Field mask that does not include the paths. + public init( + from messageType: M.Type, + excludedPaths paths: [String] + ) { + let allPaths = M._protobuf_nameMap.names.map(\.description) + let _paths = Set(allPaths).subtracting(.init(paths)) + self = .with { mask in + mask.paths = Array(_paths) + } + } + + /// Returns a field mask by reversing all fields. So all excluded paths from + /// the original field mask will be included, and vise versa. + /// - Parameter messageType: Message type to get all paths from. + /// - Returns: Field mask which is completly reverse of the cuurent one. + public func reverse( + _ messageType: M.Type + ) -> Google_Protobuf_FieldMask { + .init(from: M.self, excludedPaths: self.paths) + } +} diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift new file mode 100644 index 000000000..e8ae00451 --- /dev/null +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -0,0 +1,77 @@ +// Sources/SwiftProtobuf/Message+FieldMask.swift - Message field mask extensions +// +// Copyright (c) 2014 - 2016 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. (e.g. masking) +/// +// ----------------------------------------------------------------------------- + +extension Message where Self: _ProtoNameProviding { + + /// Clears masked fields and keep the other fields unchanged. + /// Notice that masking will be done in order of field mask paths. + /// + /// - Parameter mask: Field mask which determines what fields + /// shoud be cleared. + public mutating func mask( + by mask: Google_Protobuf_FieldMask + ) throws { + try override(with: .init(), by: mask) + } + + /// Overrides value of masked fields in original message with the input message. + /// Notice that overriding will be done in order of field mask paths. + /// + /// - Parameters: + /// - message: Message which overrides some fields of the original message. + /// - mask: Field mask which determines what fields should be overriden. + public mutating func override( + with message: Self, + by mask: Google_Protobuf_FieldMask + ) throws { + var copy = self + var pathToValueMap: [String: Any?] = [:] + for path in mask.paths { + pathToValueMap[path] = try message.get(path: path) + } + for (path, value) in pathToValueMap { + try copy.set(path: path, value: value) + } + self = copy + } + + /// Returns a new message with cleared masked fields. + /// Notice that masking will be done in order of field mask paths. + /// + /// - Parameter mask: Field mask which determines what fields + /// should be cleared. + public func masked( + by mask: Google_Protobuf_FieldMask + ) throws -> Self { + var copy = self + try copy.mask(by: mask) + return copy + } + + /// Returns a new message which some of its value are overriden with the + /// input message. Notice that masking will be done in order of field + /// mask paths. + /// + /// - Parameters: + /// - message: Message which overrides some fields of the original message. + /// - mask: Field mask which determines what fields should be overriden. + public func overriden( + with message: Self, + by mask: Google_Protobuf_FieldMask + ) throws -> Self { + var copy = self + try copy.override(with: message, by: mask) + return copy + } +} diff --git a/Sources/SwiftProtobuf/NameMap.swift b/Sources/SwiftProtobuf/NameMap.swift index e835c9899..2d9f2cee6 100644 --- a/Sources/SwiftProtobuf/NameMap.swift +++ b/Sources/SwiftProtobuf/NameMap.swift @@ -278,4 +278,11 @@ public struct _NameMap: ExpressibleByDictionaryLiteral { let n = Name(transientUtf8Buffer: raw) return jsonToNumberMap[n] } + + /// Returns all proto names + internal var names: [Name] { + protoToNumberMap.map { + $0.key + } + } } diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift new file mode 100644 index 000000000..fcaa20449 --- /dev/null +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -0,0 +1,330 @@ +// Sources/SwiftProtobuf/SetPathDecoder.swift - Path decoder (Setter) +// +// Copyright (c) 2014 - 2016 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 + +extension Message { + static func number(for field: String) -> Int? { + guard let type = Self.self as? _ProtoNameProviding.Type else { + return nil + } + return type._protobuf_nameMap.number(forJSONName: field) + } +} + +enum PathDecodingError: Error { + case typeMismatch +} + +struct SetPathDecoder: Decoder { + + private let path: String + private let value: Any? + private var number: Int? + + init(path: String, value: Any?) { + self.path = path + self.value = value + if let firstPathComponent { + self.number = T.number(for: firstPathComponent) + } + } + + var firstPathComponent: String? { + return path + .components(separatedBy: ".") + .first + } + + var nextPath: String { + return path + .components(separatedBy: ".") + .dropFirst() + .joined(separator: ".") + } + + func _value(as: V.Type) throws -> V { + guard let __value = self.value as? V else { + throw PathDecodingError.typeMismatch + } + return __value + } + + mutating func handleConflictingOneOf() throws {} + + mutating func nextFieldNumber() throws -> Int? { + defer { number = nil } + return number + } + + mutating func decodeSingularFloatField(value: inout Float) throws { + value = try _value(as: Float.self) + } + + mutating func decodeSingularFloatField(value: inout Float?) throws { + value = try _value(as: Float?.self) + } + + mutating func decodeRepeatedFloatField(value: inout [Float]) throws { + value = try _value(as: [Float].self) + } + + mutating func decodeSingularDoubleField(value: inout Double) throws { + value = try _value(as: Double.self) + } + + mutating func decodeSingularDoubleField(value: inout Double?) throws { + value = try _value(as: Double?.self) + } + + mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { + value = try _value(as: [Double].self) + } + + mutating func decodeSingularInt32Field(value: inout Int32) throws { + value = try _value(as: Int32.self) + } + + mutating func decodeSingularInt32Field(value: inout Int32?) throws { + value = try _value(as: Int32?.self) + } + + mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { + value = try _value(as: [Int32].self) + } + + mutating func decodeSingularInt64Field(value: inout Int64) throws { + value = try _value(as: Int64.self) + } + + mutating func decodeSingularInt64Field(value: inout Int64?) throws { + value = try _value(as: Int64?.self) + } + + mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { + value = try _value(as: [Int64].self) + } + + mutating func decodeSingularUInt32Field(value: inout UInt32) throws { + value = try _value(as: UInt32.self) + } + + mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { + value = try _value(as: UInt32?.self) + } + + mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { + value = try _value(as: [UInt32].self) + } + + mutating func decodeSingularUInt64Field(value: inout UInt64) throws { + value = try _value(as: UInt64.self) + } + + mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { + value = try _value(as: UInt64?.self) + } + + mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { + value = try _value(as: [UInt64].self) + } + + mutating func decodeSingularSInt32Field(value: inout Int32) throws { + value = try _value(as: Int32.self) + } + + mutating func decodeSingularSInt32Field(value: inout Int32?) throws { + value = try _value(as: Int32?.self) + } + + mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { + value = try _value(as: [Int32].self) + } + + mutating func decodeSingularSInt64Field(value: inout Int64) throws { + value = try _value(as: Int64.self) + } + + mutating func decodeSingularSInt64Field(value: inout Int64?) throws { + value = try _value(as: Int64?.self) + } + + mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { + value = try _value(as: [Int64].self) + } + + mutating func decodeSingularFixed32Field(value: inout UInt32) throws { + value = try _value(as: UInt32.self) + } + + mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { + value = try _value(as: UInt32?.self) + } + + mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { + value = try _value(as: [UInt32].self) + } + + mutating func decodeSingularFixed64Field(value: inout UInt64) throws { + value = try _value(as: UInt64.self) + } + + mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { + value = try _value(as: UInt64?.self) + } + + mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { + value = try _value(as: [UInt64].self) + } + + mutating func decodeSingularSFixed32Field(value: inout Int32) throws { + value = try _value(as: Int32.self) + } + + mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { + value = try _value(as: Int32?.self) + } + + mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { + value = try _value(as: [Int32].self) + } + + mutating func decodeSingularSFixed64Field(value: inout Int64) throws { + value = try _value(as: Int64.self) + } + + mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { + value = try _value(as: Int64?.self) + } + + mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { + value = try _value(as: [Int64].self) + } + + mutating func decodeSingularBoolField(value: inout Bool) throws { + value = try _value(as: Bool.self) + } + + mutating func decodeSingularBoolField(value: inout Bool?) throws { + value = try _value(as: Bool?.self) + } + + mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { + value = try _value(as: [Bool].self) + } + + mutating func decodeSingularStringField(value: inout String) throws { + value = try _value(as: String.self) + } + + mutating func decodeSingularStringField(value: inout String?) throws { + value = try _value(as: String?.self) + } + + mutating func decodeRepeatedStringField(value: inout [String]) throws { + value = try _value(as: [String].self) + } + + mutating func decodeSingularBytesField(value: inout Data) throws { + value = try _value(as: Data.self) + } + + mutating func decodeSingularBytesField(value: inout Data?) throws { + value = try _value(as: Data?.self) + } + + mutating func decodeRepeatedBytesField(value: inout [Data]) throws { + value = try _value(as: [Data].self) + } + + mutating func decodeSingularEnumField( + value: inout E + ) throws where E : Enum, E.RawValue == Int { + value = try _value(as: E.self) + } + + mutating func decodeSingularEnumField( + value: inout E? + ) throws where E : Enum, E.RawValue == Int { + value = try _value(as: E?.self) + } + + mutating func decodeRepeatedEnumField( + value: inout [E] + ) throws where E : Enum, E.RawValue == Int { + value = try _value(as: [E].self) + } + + mutating func decodeSingularMessageField( + value: inout M? + ) throws where M : Message { + if nextPath.isEmpty { + value = try _value(as: M?.self) + return + } + var decoder = SetPathDecoder( + path: nextPath, value: self.value + ) + try value?.decodeMessage(decoder: &decoder) + } + + mutating func decodeRepeatedMessageField( + value: inout [M] + ) throws where M : Message { + value = try _value(as: [M].self) + } + + mutating func decodeSingularGroupField( + value: inout G? + ) throws where G : Message { + value = try _value(as: G?.self) + } + + mutating func decodeRepeatedGroupField( + value: inout [G] + ) throws where G : Message { + value = try _value(as: [G].self) + } + + mutating func decodeMapField( + fieldType: _ProtobufMap.Type, + value: inout _ProtobufMap.BaseType + ) throws where KeyType : MapKeyType, ValueType : MapValueType { + value = try _value(as: _ProtobufMap.BaseType.self) + } + + mutating func decodeMapField( + fieldType: _ProtobufEnumMap.Type, + value: inout _ProtobufEnumMap.BaseType + ) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int { + value = try _value(as: _ProtobufEnumMap.BaseType.self) + } + + mutating func decodeMapField( + fieldType: _ProtobufMessageMap.Type, + value: inout _ProtobufMessageMap.BaseType + ) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : Message { + value = try _value(as: _ProtobufMessageMap.BaseType.self) + } + + mutating func decodeExtensionField(values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int) throws {} + +} + +extension Message { + mutating func `set`(path: String, value: Any?) throws { + var decoder = SetPathDecoder(path: path, value: value) + try decodeMessage(decoder: &decoder) + } +} diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index d550be8d9..3dc5ef398 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -13,8 +13,7 @@ /// // ----------------------------------------------------------------------------- -// TODO: We should have utility functions for applying a mask to an arbitrary -// message, intersecting two masks, etc. +// TODO: We should have utility functions for intersecting two masks, etc. import Foundation import XCTest @@ -109,4 +108,103 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertThrowsError(try m.jsonString()) } } + + func testMaskFieldsOfAMessage() throws { + var message = SwiftProtoTesting_TestAllTypes.with { model in + model.optionalInt32 = 1 + model.optionalNestedMessage = .with { nested in + nested.bb = 2 + } + } + + try message.mask(by: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertEqual(message.optionalInt32, 1) + XCTAssertEqual(message.optionalNestedMessage.bb, 0) + + try message.mask(by: .init(protoPaths: "optional_int32")) + XCTAssertEqual(message.optionalInt32, 0) + XCTAssertEqual(message.optionalNestedMessage.bb, 0) + } + + func testMaskedFieldsOfAMessage() throws { + let message = SwiftProtoTesting_TestAllTypes.with { model in + model.optionalInt32 = 1 + model.optionalNestedMessage = .with { nested in + nested.bb = 2 + } + } + + let newMessage1 = try message.masked(by: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertEqual(newMessage1.optionalInt32, 1) + XCTAssertEqual(newMessage1.optionalNestedMessage.bb, 0) + + let newMessage2 = try message.masked(by: .init(protoPaths: "optional_int32")) + XCTAssertEqual(newMessage2.optionalInt32, 0) + XCTAssertEqual(newMessage2.optionalNestedMessage.bb, 2) + } + + func testOverrideFieldsOfAMessage() 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 + } + } + + try message.override(with: secondMessage, by: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertEqual(message.optionalInt32, 1) + XCTAssertEqual(message.optionalNestedMessage.bb, 3) + + try message.override(with: secondMessage, by: .init(protoPaths: "optional_int32")) + XCTAssertEqual(message.optionalInt32, 2) + XCTAssertEqual(message.optionalNestedMessage.bb, 3) + } + + func testOverridenFieldsOfAMessage() throws { + let 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 + } + } + + let newMessage1 = try message.overriden(with: secondMessage, by: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertEqual(newMessage1.optionalInt32, 1) + XCTAssertEqual(newMessage1.optionalNestedMessage.bb, 3) + + let newMessage2 = try message.overriden(with: secondMessage, by: .init(protoPaths: "optional_int32")) + XCTAssertEqual(newMessage2.optionalInt32, 2) + XCTAssertEqual(newMessage2.optionalNestedMessage.bb, 2) + } + + func testFieldMaskMessageInits() { + let m1 = Google_Protobuf_FieldMask(from: SwiftProtoTesting_TestAny.self) + XCTAssertEqual(m1.paths.sorted(), ["any_value", "int32_value", "repeated_any_value", "text"]) + + let m2 = Google_Protobuf_FieldMask(from: SwiftProtoTesting_TestAny.self, fieldNumbers: [1, 2]) + XCTAssertEqual(m2.paths.sorted(), ["any_value", "int32_value"]) + + let m3 = Google_Protobuf_FieldMask(from: SwiftProtoTesting_TestAny.self, excludedPaths: ["int32_value"]) + XCTAssertEqual(m3.paths.sorted(), ["any_value", "repeated_any_value", "text"]) + } + + func testReverseFieldMask() { + let m1 = Google_Protobuf_FieldMask(protoPaths: ["any_value"]) + let m2 = m1.reverse(SwiftProtoTesting_TestAny.self) + XCTAssertEqual(m2.paths.sorted(), ["int32_value", "repeated_any_value", "text"]) + } } From 1b5fbe90aff3b73fbecb013f6904a8386333daa3 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Sun, 3 Dec 2023 17:32:48 +0330 Subject: [PATCH 08/33] Update cmakelist --- Sources/SwiftProtobuf/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/SwiftProtobuf/CMakeLists.txt b/Sources/SwiftProtobuf/CMakeLists.txt index 74816c320..158055778 100644 --- a/Sources/SwiftProtobuf/CMakeLists.txt +++ b/Sources/SwiftProtobuf/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(SwiftProtobuf FieldTag.swift FieldTypes.swift field_mask.pb.swift + GetPathDecoder.swift Google_Protobuf_Any+Extensions.swift Google_Protobuf_Any+Registry.swift Google_Protobuf_Duration+Extensions.swift @@ -50,6 +51,7 @@ add_library(SwiftProtobuf MathUtils.swift Message+AnyAdditions.swift Message+BinaryAdditions.swift + Message+FieldMask.swift Message+JSONAdditions.swift Message+JSONArrayAdditions.swift Message+TextFormatAdditions.swift @@ -60,6 +62,7 @@ add_library(SwiftProtobuf ProtobufMap.swift ProtoNameProviding.swift SelectiveVisitor.swift + SetPathDecoder.swift SimpleExtensionMap.swift source_context.pb.swift StringUtils.swift From c1e42b2dce49f6542b1e782b1181dcbeb69ad23f Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Sun, 3 Dec 2023 17:50:13 +0330 Subject: [PATCH 09/33] Add comments for path decoding error --- Sources/SwiftProtobuf/SetPathDecoder.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index fcaa20449..264cff8fb 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -14,6 +14,16 @@ 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 +} + extension Message { static func number(for field: String) -> Int? { guard let type = Self.self as? _ProtoNameProviding.Type else { @@ -23,10 +33,6 @@ extension Message { } } -enum PathDecodingError: Error { - case typeMismatch -} - struct SetPathDecoder: Decoder { private let path: String From da24d1910313b6ff802b0451d327fdce88862e35 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 8 Dec 2023 23:40:28 +0330 Subject: [PATCH 10/33] Change APIs --- Sources/SwiftProtobuf/GetPathDecoder.swift | 188 +++++++++------- ...Google_Protobuf_FieldMask+Extensions.swift | 152 ++++++++++--- Sources/SwiftProtobuf/Message+FieldMask.swift | 90 ++++---- Sources/SwiftProtobuf/SetPathDecoder.swift | 9 +- Tests/SwiftProtobufTests/Test_FieldMask.swift | 200 ++++++++++++------ 5 files changed, 424 insertions(+), 215 deletions(-) diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift index 1ff7104cb..c78933ad3 100644 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -17,21 +17,31 @@ import Foundation struct GetPathDecoder: Decoder { private let path: String - private(set) var value: Any? private var number: Int? - init(path: String) { + private var _value: Any? + private var _hasPath: Bool = false + + internal var value: Any? { + _value + } + + internal var hasPath: Bool { + _hasPath + } + + internal init(path: String) { self.path = path if let firstPathComponent { self.number = T.number(for: firstPathComponent) } } - var firstPathComponent: String? { + private var firstPathComponent: String? { path.components(separatedBy: ".").first } - var nextPath: String { + private var nextPath: String { path.components(separatedBy: ".").dropFirst().joined(separator: ".") } @@ -42,233 +52,259 @@ struct GetPathDecoder: Decoder { return number } + private mutating func captureValue(_ value: Any?) { + self._value = value + self._hasPath = true + } + mutating func decodeSingularFloatField(value: inout Float) throws { - self.value = value + captureValue(value) } mutating func decodeSingularFloatField(value: inout Float?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedFloatField(value: inout [Float]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularDoubleField(value: inout Double) throws { - self.value = value + captureValue(value) } mutating func decodeSingularDoubleField(value: inout Double?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularInt32Field(value: inout Int32) throws { - self.value = value + captureValue(value) } mutating func decodeSingularInt32Field(value: inout Int32?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularInt64Field(value: inout Int64) throws { - self.value = value + captureValue(value) } mutating func decodeSingularInt64Field(value: inout Int64?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularUInt32Field(value: inout UInt32) throws { - self.value = value + captureValue(value) } mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularUInt64Field(value: inout UInt64) throws { - self.value = value + captureValue(value) } mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSInt32Field(value: inout Int32) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSInt32Field(value: inout Int32?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSInt64Field(value: inout Int64) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSInt64Field(value: inout Int64?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularFixed32Field(value: inout UInt32) throws { - self.value = value + captureValue(value) } mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularFixed64Field(value: inout UInt64) throws { - self.value = value + captureValue(value) } mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSFixed32Field(value: inout Int32) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSFixed64Field(value: inout Int64) throws { - self.value = value + captureValue(value) } mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularBoolField(value: inout Bool) throws { - self.value = value + captureValue(value) } mutating func decodeSingularBoolField(value: inout Bool?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularStringField(value: inout String) throws { - self.value = value + captureValue(value) } mutating func decodeSingularStringField(value: inout String?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedStringField(value: inout [String]) throws { - self.value = value + captureValue(value) } mutating func decodeSingularBytesField(value: inout Data) throws { - self.value = value + captureValue(value) } mutating func decodeSingularBytesField(value: inout Data?) throws { - self.value = value + captureValue(value) } mutating func decodeRepeatedBytesField(value: inout [Data]) throws { - self.value = value + captureValue(value) } - mutating func decodeSingularEnumField(value: inout E) throws where E : Enum, E.RawValue == Int { - self.value = value + mutating func decodeSingularEnumField(value: inout E) throws { + captureValue(value) } - mutating func decodeSingularEnumField(value: inout E?) throws where E : Enum, E.RawValue == Int { - self.value = value + mutating func decodeSingularEnumField(value: inout E?) throws { + captureValue(value) } - mutating func decodeRepeatedEnumField(value: inout [E]) throws where E : Enum, E.RawValue == Int { - self.value = value + mutating func decodeRepeatedEnumField(value: inout [E]) throws { + captureValue(value) } - mutating func decodeSingularMessageField(value: inout M?) throws where M : Message { + mutating func decodeSingularMessageField( + value: inout M? + ) throws where M : Message { if nextPath.isEmpty { - self.value = value + captureValue(value) return } var decoder = GetPathDecoder(path: nextPath) - try value?.decodeMessage(decoder: &decoder) - self.value = decoder.value + if value != nil { + try value?.decodeMessage(decoder: &decoder) + } else { + var tmp = M() + try tmp.decodeMessage(decoder: &decoder) + } + self._value = decoder.value + self._hasPath = decoder.hasPath } - mutating func decodeRepeatedMessageField(value: inout [M]) throws where M : Message { - self.value = value + mutating func decodeRepeatedMessageField(value: inout [M]) throws { + captureValue(value) } - mutating func decodeSingularGroupField(value: inout G?) throws where G : Message { - self.value = value + mutating func decodeSingularGroupField(value: inout G?) throws { + captureValue(value) } - mutating func decodeRepeatedGroupField(value: inout [G]) throws where G : Message { - self.value = value + mutating func decodeRepeatedGroupField(value: inout [G]) throws { + captureValue(value) } - mutating func decodeMapField(fieldType: _ProtobufMap.Type, value: inout _ProtobufMap.BaseType) throws where KeyType : MapKeyType, ValueType : MapValueType { - self.value = value + mutating func decodeMapField( + fieldType: _ProtobufMap.Type, + value: inout _ProtobufMap.BaseType + ) throws { + captureValue(value) } - mutating func decodeMapField(fieldType: _ProtobufEnumMap.Type, value: inout _ProtobufEnumMap.BaseType) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int { - self.value = value + mutating func decodeMapField( + fieldType: _ProtobufEnumMap.Type, + value: inout _ProtobufEnumMap.BaseType + ) throws { + captureValue(value) } - mutating func decodeMapField(fieldType: _ProtobufMessageMap.Type, value: inout _ProtobufMessageMap.BaseType) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : Message { - self.value = value + mutating func decodeMapField( + fieldType: _ProtobufMessageMap.Type, + value: inout _ProtobufMessageMap.BaseType + ) throws { + captureValue(value) } - mutating func decodeExtensionField(values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int) throws {} + mutating func decodeExtensionField( + values: inout ExtensionFieldValueSet, + messageType: Message.Type, + fieldNumber: Int + ) throws {} } @@ -279,5 +315,11 @@ extension Message { try copy.decodeMessage(decoder: &decoder) return decoder.value } -} + func hasPath(path: String) -> Bool { + var copy = self + var decoder = GetPathDecoder(path: path) + try? copy.decodeMessage(decoder: &decoder) + return decoder.hasPath + } +} diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 764234b52..507739a19 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -190,11 +190,10 @@ 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( - from messageType: M.Type + allFieldsOf messageType: M.Type ) { - let paths = M._protobuf_nameMap.names.map(\.description) self = .with { mask in - mask.paths = paths + mask.paths = M.allProtoNames } } @@ -204,40 +203,137 @@ extension Google_Protobuf_FieldMask { /// - fieldNumbers: Field numbers of paths to be included. /// - Returns: Field mask that include paths of corresponding field numbers. public init( - from messageType: M.Type, - fieldNumbers: [Int] - ) { - let paths = fieldNumbers.compactMap { number in - M._protobuf_nameMap.names(for: number)?.proto.description + fieldNumbers: [Int], + of messageType: M.Type + ) throws { + var paths: [String] = [] + for number in fieldNumbers { + guard let name = M.protoName(for: number) else { + throw FieldMaskUtilsError.invalidFieldNumber + } + paths.append(name) } self = .with { mask in mask.paths = paths } } +} - /// Initiates a field mask by excluding some paths - /// - Parameters: - /// - messageType: Message type to get all paths from. - /// - paths: Paths to be excluded. - /// - Returns: Field mask that does not include the paths. - public init( - from messageType: M.Type, - excludedPaths paths: [String] - ) { - let allPaths = M._protobuf_nameMap.names.map(\.description) - let _paths = Set(allPaths).subtracting(.init(paths)) - self = .with { mask in - mask.paths = Array(_paths) +public enum FieldMaskUtilsError: Error { + case invalidPath + case invalidFieldNumber +} + +extension Google_Protobuf_FieldMask { + public mutating func addPath( + _ path: String, + of messageType: M.Type + ) throws { + guard M.isPathValid(path) else { + throw FieldMaskUtilsError.invalidPath } + paths.append(path) } - /// Returns a field mask by reversing all fields. So all excluded paths from - /// the original field mask will be included, and vise versa. - /// - Parameter messageType: Message type to get all paths from. - /// - Returns: Field mask which is completly reverse of the cuurent one. - public func reverse( - _ messageType: M.Type + public var canonical: Google_Protobuf_FieldMask { + var mask = Google_Protobuf_FieldMask() + let sortedPaths = self.paths.sorted() + var set = Set() + for path in sortedPaths { + if !contains(path, in: set) { + set.insert(path) + mask.paths.append(path) + } + } + return mask + } + + public func union( + _ mask: Google_Protobuf_FieldMask + ) -> Google_Protobuf_FieldMask { + var buffer: Set = .init() + var _paths: [String] = [] + let allPaths = paths + mask.paths + for path in allPaths where !buffer.contains(path) { + buffer.insert(path) + _paths.append(path) + } + return .with { mask in + mask.paths = _paths + } + } + + private var pathsSet: Set { + .init(paths) + } + + public func intersect( + _ mask: Google_Protobuf_FieldMask + ) -> Google_Protobuf_FieldMask { + let set = mask.pathsSet + var _paths: [String] = [] + for path in paths where set.contains(path) { + _paths.append(path) + } + return .with { mask in + mask.paths = _paths + } + } + + public func subtract( + _ mask: Google_Protobuf_FieldMask ) -> Google_Protobuf_FieldMask { - .init(from: M.self, excludedPaths: self.paths) + let set = mask.pathsSet + var _paths: [String] = [] + for path in paths where !set.contains(path) { + _paths.append(path) + } + return .with { mask in + mask.paths = _paths + } + } + + private func levels(path: String) -> [String] { + let comps = path.components(separatedBy: ".") + return (0.. + ) -> Bool { + for level in levels(path: path) { + if set.contains(level) { + return true + } + } + return false + } + + public func contains(_ path: String) -> Bool { + contains(path, in: pathsSet) + } +} + +extension Google_Protobuf_FieldMask { + public func isValid( + for messageType: M.Type + ) -> Bool { + let message = M() + return paths.allSatisfy { path in + message.isPathValid(path) + } + } +} + +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 index e8ae00451..59bff6c69 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -8,70 +8,62 @@ // // ----------------------------------------------------------------------------- /// -/// Extend the Message types with FieldMask utilities. (e.g. masking) +/// Extend the Message types with FieldMask utilities. /// // ----------------------------------------------------------------------------- -extension Message where Self: _ProtoNameProviding { +import Foundation - /// Clears masked fields and keep the other fields unchanged. - /// Notice that masking will be done in order of field mask paths. - /// - /// - Parameter mask: Field mask which determines what fields - /// shoud be cleared. - public mutating func mask( - by mask: Google_Protobuf_FieldMask - ) throws { - try override(with: .init(), by: mask) +extension Message { + public static func isPathValid( + _ path: String + ) -> Bool { + Self().hasPath(path: path) + } + + internal func isPathValid( + _ path: String + ) -> Bool { + hasPath(path: path) } +} - /// Overrides value of masked fields in original message with the input message. - /// Notice that overriding will be done in order of field mask paths. - /// - /// - Parameters: - /// - message: Message which overrides some fields of the original message. - /// - mask: Field mask which determines what fields should be overriden. - public mutating func override( - with message: Self, - by mask: Google_Protobuf_FieldMask +extension Message { + public mutating func merge( + to source: Self, + fieldMask: Google_Protobuf_FieldMask ) throws { var copy = self var pathToValueMap: [String: Any?] = [:] - for path in mask.paths { - pathToValueMap[path] = try message.get(path: path) + for path in fieldMask.paths { + pathToValueMap[path] = try source.get(path: path) } for (path, value) in pathToValueMap { try copy.set(path: path, value: value) } self = copy } +} - /// Returns a new message with cleared masked fields. - /// Notice that masking will be done in order of field mask paths. - /// - /// - Parameter mask: Field mask which determines what fields - /// should be cleared. - public func masked( - by mask: Google_Protobuf_FieldMask - ) throws -> Self { - var copy = self - try copy.mask(by: mask) - return copy - } - - /// Returns a new message which some of its value are overriden with the - /// input message. Notice that masking will be done in order of field - /// mask paths. - /// - /// - Parameters: - /// - message: Message which overrides some fields of the original message. - /// - mask: Field mask which determines what fields should be overriden. - public func overriden( - with message: Self, - by mask: Google_Protobuf_FieldMask - ) throws -> Self { - var copy = self - try copy.override(with: message, by: mask) - return copy +extension Message where Self: Equatable, Self: _ProtoNameProviding { + @discardableResult + public mutating func trim( + fieldMask: Google_Protobuf_FieldMask + ) -> Bool { + if !fieldMask.isValid(for: Self.self) { + return false + } + if fieldMask.paths.isEmpty { + return false + } + var tmp = Self.init() + do { + try tmp.merge(to: self, fieldMask: fieldMask) + let changed = tmp != self + self = tmp + return changed + } catch { + return false + } } } diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 264cff8fb..7cd4ca431 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -282,6 +282,9 @@ struct SetPathDecoder: Decoder { var decoder = SetPathDecoder( path: nextPath, value: self.value ) + if value == nil { + value = .init() + } try value?.decodeMessage(decoder: &decoder) } @@ -324,7 +327,11 @@ struct SetPathDecoder: Decoder { value = try _value(as: _ProtobufMessageMap.BaseType.self) } - mutating func decodeExtensionField(values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int) throws {} + mutating func decodeExtensionField( + values: inout ExtensionFieldValueSet, + messageType: Message.Type, + fieldNumber: Int + ) throws {} } diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 3dc5ef398..fcce72187 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -13,8 +13,6 @@ /// // ----------------------------------------------------------------------------- -// TODO: We should have utility functions for intersecting two masks, etc. - import Foundation import XCTest import SwiftProtobuf @@ -31,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,41 +107,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } } - func testMaskFieldsOfAMessage() throws { - var message = SwiftProtoTesting_TestAllTypes.with { model in - model.optionalInt32 = 1 - model.optionalNestedMessage = .with { nested in - nested.bb = 2 - } - } - - try message.mask(by: .init(protoPaths: "optional_nested_message.bb")) - XCTAssertEqual(message.optionalInt32, 1) - XCTAssertEqual(message.optionalNestedMessage.bb, 0) - - try message.mask(by: .init(protoPaths: "optional_int32")) - XCTAssertEqual(message.optionalInt32, 0) - XCTAssertEqual(message.optionalNestedMessage.bb, 0) - } - - func testMaskedFieldsOfAMessage() throws { - let message = SwiftProtoTesting_TestAllTypes.with { model in - model.optionalInt32 = 1 - model.optionalNestedMessage = .with { nested in - nested.bb = 2 - } - } - - let newMessage1 = try message.masked(by: .init(protoPaths: "optional_nested_message.bb")) - XCTAssertEqual(newMessage1.optionalInt32, 1) - XCTAssertEqual(newMessage1.optionalNestedMessage.bb, 0) - - let newMessage2 = try message.masked(by: .init(protoPaths: "optional_int32")) - XCTAssertEqual(newMessage2.optionalInt32, 0) - XCTAssertEqual(newMessage2.optionalNestedMessage.bb, 2) - } - - func testOverrideFieldsOfAMessage() throws { + func testMergeFieldsOfMessage() throws { var message = SwiftProtoTesting_TestAllTypes.with { model in model.optionalInt32 = 1 model.optionalNestedMessage = .with { nested in @@ -158,53 +122,161 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } } - try message.override(with: secondMessage, by: .init(protoPaths: "optional_nested_message.bb")) + try message.merge(to: secondMessage, fieldMask: .init(protoPaths: "optional_nested_message.bb")) XCTAssertEqual(message.optionalInt32, 1) XCTAssertEqual(message.optionalNestedMessage.bb, 3) - try message.override(with: secondMessage, by: .init(protoPaths: "optional_int32")) + try message.merge(to: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) XCTAssertEqual(message.optionalInt32, 2) XCTAssertEqual(message.optionalNestedMessage.bb, 3) } - func testOverridenFieldsOfAMessage() throws { - let message = SwiftProtoTesting_TestAllTypes.with { model in + func testTrimFieldsOfMessage() 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 - } - } + let r1 = message.trim(fieldMask: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertTrue(r1) + XCTAssertEqual(message.optionalInt32, 0) + XCTAssertEqual(message.optionalNestedMessage.bb, 2) - let newMessage1 = try message.overriden(with: secondMessage, by: .init(protoPaths: "optional_nested_message.bb")) - XCTAssertEqual(newMessage1.optionalInt32, 1) - XCTAssertEqual(newMessage1.optionalNestedMessage.bb, 3) + let r2 = message.trim(fieldMask: .init()) + XCTAssertFalse(r2) - let newMessage2 = try message.overriden(with: secondMessage, by: .init(protoPaths: "optional_int32")) - XCTAssertEqual(newMessage2.optionalInt32, 2) - XCTAssertEqual(newMessage2.optionalNestedMessage.bb, 2) + let r3 = message.trim(fieldMask: .init(protoPaths: "optional_nested_message.bb")) + XCTAssertFalse(r3) + + let r4 = message.trim(fieldMask: .init(protoPaths: "invalid_path")) + XCTAssertFalse(r4) } - func testFieldMaskMessageInits() { - let m1 = Google_Protobuf_FieldMask(from: SwiftProtoTesting_TestAny.self) - XCTAssertEqual(m1.paths.sorted(), ["any_value", "int32_value", "repeated_any_value", "text"]) + func testIsPathValid() { + XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int32")) + XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bb")) + XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int")) + XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bc")) + } + + 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)) + } - let m2 = Google_Protobuf_FieldMask(from: SwiftProtoTesting_TestAny.self, fieldNumbers: [1, 2]) + func testCanonicalFieldMask() { + let m1 = Google_Protobuf_FieldMask(protoPaths: ["a.b", "b", "a"]) + XCTAssertEqual(m1.canonical.paths, ["a", "b"]) + let m2 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + XCTAssertEqual(m2.canonical.paths, ["a", "b"]) + let m3 = Google_Protobuf_FieldMask(protoPaths: ["c", "a.b.c", "a.b", "a.b.c.d"]) + XCTAssertEqual(m3.canonical.paths, ["a.b", "c"]) + } + + 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)) + } + + 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")) + } + + 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)) + } + + 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 m3 = Google_Protobuf_FieldMask(from: SwiftProtoTesting_TestAny.self, excludedPaths: ["int32_value"]) - XCTAssertEqual(m3.paths.sorted(), ["any_value", "repeated_any_value", "text"]) + let m7 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + let m8 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) + XCTAssertEqual(m7.union(m8).paths, ["a", "b"]) } - func testReverseFieldMask() { - let m1 = Google_Protobuf_FieldMask(protoPaths: ["any_value"]) - let m2 = m1.reverse(SwiftProtoTesting_TestAny.self) - XCTAssertEqual(m2.paths.sorted(), ["int32_value", "repeated_any_value", "text"]) + 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"]) } + + 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, []) + } + } From 1c9406522a8bb32eb220ca4240a845963b79908f Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Sat, 9 Dec 2023 01:03:18 +0330 Subject: [PATCH 11/33] Add comments --- ...Google_Protobuf_FieldMask+Extensions.swift | 76 ++++++++++++++----- Sources/SwiftProtobuf/Message+FieldMask.swift | 17 +++++ Tests/SwiftProtobufTests/Test_FieldMask.swift | 43 ++++++++++- 3 files changed, 115 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 507739a19..9b81bb735 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; @@ -188,6 +183,7 @@ extension Google_Protobuf_FieldMask: _CustomJSONCodable { 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 @@ -198,6 +194,7 @@ extension Google_Protobuf_FieldMask { } /// 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. @@ -219,12 +216,14 @@ extension Google_Protobuf_FieldMask { } } -public enum FieldMaskUtilsError: Error { - case invalidPath - case invalidFieldNumber -} - 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 @@ -235,6 +234,11 @@ extension Google_Protobuf_FieldMask { 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() @@ -248,6 +252,10 @@ extension Google_Protobuf_FieldMask { 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 { @@ -263,10 +271,10 @@ extension Google_Protobuf_FieldMask { } } - private var pathsSet: Set { - .init(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 { @@ -280,6 +288,11 @@ extension Google_Protobuf_FieldMask { } } + /// 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 { @@ -293,6 +306,22 @@ extension Google_Protobuf_FieldMask { } } + /// 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 { + contains(path, in: pathsSet) + } + + // Set containing paths of FieldMask + private var pathsSet: Set { + .init(paths) + } + private func levels(path: String) -> [String] { let comps = path.components(separatedBy: ".") return (0.. Bool { - contains(path, in: pathsSet) - } } 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 { @@ -328,6 +358,16 @@ extension Google_Protobuf_FieldMask { } } +/// Describes errors could happen during FieldMask utilities. +public enum FieldMaskUtilsError: 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 diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 59bff6c69..30dc1b98d 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -15,6 +15,11 @@ 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 { @@ -29,6 +34,12 @@ extension Message { } extension Message { + + /// Merges fields specified in a FieldMask into another message. + /// + /// - Parameters: + /// - source: Message should be merged to the original one. + /// - fieldMask: FieldMask specifies which fields should be merged. public mutating func merge( to source: Self, fieldMask: Google_Protobuf_FieldMask @@ -46,7 +57,13 @@ extension Message { } extension Message where Self: Equatable, Self: _ProtoNameProviding { + @discardableResult + /// 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 public mutating func trim( fieldMask: Google_Protobuf_FieldMask ) -> Bool { diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index fcce72187..56d75e824 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -106,7 +106,8 @@ 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 @@ -122,15 +123,18 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } } + // Checks nested message merge try message.merge(to: secondMessage, fieldMask: .init(protoPaths: "optional_nested_message.bb")) XCTAssertEqual(message.optionalInt32, 1) XCTAssertEqual(message.optionalNestedMessage.bb, 3) + // Checks primitive type merge try message.merge(to: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) XCTAssertEqual(message.optionalInt32, 2) XCTAssertEqual(message.optionalNestedMessage.bb, 3) } + // Checks trim functionality for field masks. func testTrimFieldsOfMessage() throws { var message = SwiftProtoTesting_TestAllTypes.with { model in model.optionalInt32 = 1 @@ -139,21 +143,30 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } } + // Checks trim to be successful. let r1 = message.trim(fieldMask: .init(protoPaths: "optional_nested_message.bb")) XCTAssertTrue(r1) XCTAssertEqual(message.optionalInt32, 0) XCTAssertEqual(message.optionalNestedMessage.bb, 2) + // Checks trim should does nothing with an empty fieldMask. let r2 = message.trim(fieldMask: .init()) XCTAssertFalse(r2) + // Checks trim should return false if nothing has been changed. let r3 = message.trim(fieldMask: .init(protoPaths: "optional_nested_message.bb")) XCTAssertFalse(r3) + // Checks trim to be unsuccessful with an invalid fieldMask. let r4 = message.trim(fieldMask: .init(protoPaths: "invalid_path")) XCTAssertFalse(r4) } + // Checks `isPathValid` func + // 1. Valid primitive path should be valid. + // 2. Valid nested path should be valid. + // 3. Invalid primitive path should be valid. + // 4. Invalid nested path should be valid. func testIsPathValid() { XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int32")) XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bb")) @@ -161,6 +174,10 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bc")) } + // 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: [ @@ -186,15 +203,23 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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. func testCanonicalFieldMask() { - let m1 = Google_Protobuf_FieldMask(protoPaths: ["a.b", "b", "a"]) + let m1 = Google_Protobuf_FieldMask(protoPaths: ["a.b", "a", "b"]) XCTAssertEqual(m1.canonical.paths, ["a", "b"]) - let m2 = Google_Protobuf_FieldMask(protoPaths: ["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.c.d"]) XCTAssertEqual(m3.canonical.paths, ["a.b", "c"]) } + // 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)) @@ -204,6 +229,12 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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")) @@ -217,6 +248,9 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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"]) @@ -225,6 +259,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertThrowsError(try Google_Protobuf_FieldMask(fieldNumbers: [10], of: SwiftProtoTesting_TestAny.self)) } + // Checks `union` func of fieldMask. func testUnionFieldMasks() throws { let m1 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) let m2 = Google_Protobuf_FieldMask(protoPaths: ["b", "c"]) @@ -243,6 +278,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertEqual(m7.union(m8).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"]) @@ -261,6 +297,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertEqual(m7.intersect(m8).paths, ["a", "b"]) } + // Checks `substract` func of fieldMask. func testSubtractFieldMasks() throws { let m1 = Google_Protobuf_FieldMask(protoPaths: ["a", "b"]) let m2 = Google_Protobuf_FieldMask(protoPaths: ["b", "c"]) From af441d1afb21c86fd8417a5fa203b27f554a9283 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 14 Dec 2023 00:28:06 +0330 Subject: [PATCH 12/33] Remove copy mechanism in some functions --- Sources/SwiftProtobuf/GetPathDecoder.swift | 36 ++++++++----------- ...Google_Protobuf_FieldMask+Extensions.swift | 2 +- Sources/SwiftProtobuf/Message+FieldMask.swift | 8 +++-- Sources/SwiftProtobuf/SetPathDecoder.swift | 30 +++++----------- 4 files changed, 29 insertions(+), 47 deletions(-) diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift index c78933ad3..b6a01c30d 100644 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -1,6 +1,6 @@ // Sources/SwiftProtobuf/GetPathDecoder.swift - Path decoder (Getter) // -// Copyright (c) 2014 - 2016 Apple Inc. and the project authors +// 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: @@ -16,7 +16,7 @@ import Foundation struct GetPathDecoder: Decoder { - private let path: String + private let nextPath: [String] private var number: Int? private var _value: Any? @@ -30,19 +30,11 @@ struct GetPathDecoder: Decoder { _hasPath } - internal init(path: String) { - self.path = path - if let firstPathComponent { - self.number = T.number(for: firstPathComponent) + internal init(path: [String]) { + if let firstComponent = path.first { + self.number = T.number(for: firstComponent) } - } - - private var firstPathComponent: String? { - path.components(separatedBy: ".").first - } - - private var nextPath: String { - path.components(separatedBy: ".").dropFirst().joined(separator: ".") + self.nextPath = .init(path.dropFirst()) } mutating func handleConflictingOneOf() throws {} @@ -309,17 +301,17 @@ struct GetPathDecoder: Decoder { } extension Message { - func `get`(path: String) throws -> Any? { - var copy = self - var decoder = GetPathDecoder(path: path) - try copy.decodeMessage(decoder: &decoder) + mutating func `get`(path: String) throws -> Any? { + let _path = path.components(separatedBy: ".") + var decoder = GetPathDecoder(path: _path) + try decodeMessage(decoder: &decoder) return decoder.value } - func hasPath(path: String) -> Bool { - var copy = self - var decoder = GetPathDecoder(path: path) - try? copy.decodeMessage(decoder: &decoder) + mutating func hasPath(path: String) -> Bool { + let _path = path.components(separatedBy: ".") + var decoder = GetPathDecoder(path: _path) + try? decodeMessage(decoder: &decoder) return decoder.hasPath } } diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 9b81bb735..3215bc75e 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -351,7 +351,7 @@ extension Google_Protobuf_FieldMask { public func isValid( for messageType: M.Type ) -> Bool { - let message = M() + var message = M() return paths.allSatisfy { path in message.isPathValid(path) } diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 30dc1b98d..5990860ed 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -1,6 +1,6 @@ // Sources/SwiftProtobuf/Message+FieldMask.swift - Message field mask extensions // -// Copyright (c) 2014 - 2016 Apple Inc. and the project authors +// 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: @@ -23,10 +23,11 @@ extension Message { public static func isPathValid( _ path: String ) -> Bool { - Self().hasPath(path: path) + var message = Self() + return message.hasPath(path: path) } - internal func isPathValid( + internal mutating func isPathValid( _ path: String ) -> Bool { hasPath(path: path) @@ -44,6 +45,7 @@ extension Message { to source: Self, fieldMask: Google_Protobuf_FieldMask ) throws { + var source = source var copy = self var pathToValueMap: [String: Any?] = [:] for path in fieldMask.paths { diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 7cd4ca431..6b4094d92 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -1,6 +1,6 @@ // Sources/SwiftProtobuf/SetPathDecoder.swift - Path decoder (Setter) // -// Copyright (c) 2014 - 2016 Apple Inc. and the project authors +// 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: @@ -35,29 +35,16 @@ extension Message { struct SetPathDecoder: Decoder { - private let path: String private let value: Any? private var number: Int? + private let nextPath: [String] - init(path: String, value: Any?) { - self.path = path - self.value = value - if let firstPathComponent { - self.number = T.number(for: firstPathComponent) + init(path: [String], value: Any?) { + if let firstComponent = path.first { + self.number = T.number(for: firstComponent) } - } - - var firstPathComponent: String? { - return path - .components(separatedBy: ".") - .first - } - - var nextPath: String { - return path - .components(separatedBy: ".") - .dropFirst() - .joined(separator: ".") + self.nextPath = .init(path.dropFirst()) + self.value = value } func _value(as: V.Type) throws -> V { @@ -337,7 +324,8 @@ struct SetPathDecoder: Decoder { extension Message { mutating func `set`(path: String, value: Any?) throws { - var decoder = SetPathDecoder(path: path, value: value) + let _path = path.components(separatedBy: ".") + var decoder = SetPathDecoder(path: _path, value: value) try decodeMessage(decoder: &decoder) } } From 249b0665435cd5e7e05e1387ac288f10e601ae0e Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 14 Dec 2023 01:27:20 +0330 Subject: [PATCH 13/33] Change path contain and canonical algorithm --- ...Google_Protobuf_FieldMask+Extensions.swift | 27 ++++++++----------- Tests/SwiftProtobufTests/Test_FieldMask.swift | 7 +++-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 3215bc75e..4538a96f1 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -242,10 +242,12 @@ extension Google_Protobuf_FieldMask { public var canonical: Google_Protobuf_FieldMask { var mask = Google_Protobuf_FieldMask() let sortedPaths = self.paths.sorted() - var set = Set() for path in sortedPaths { - if !contains(path, in: set) { - set.insert(path) + if let lastPath = mask.paths.last { + if path != lastPath, !path.hasPrefix("\(lastPath).") { + mask.paths.append(path) + } + } else { mask.paths.append(path) } } @@ -314,7 +316,12 @@ extension Google_Protobuf_FieldMask { /// - Parameter path: Path to be checked. /// - Returns: Boolean determines is path covered. public func contains(_ path: String) -> Bool { - contains(path, in: pathsSet) + for _path in paths { + if path.hasPrefix("\(_path).") || _path == path { + return true + } + } + return false } // Set containing paths of FieldMask @@ -328,18 +335,6 @@ extension Google_Protobuf_FieldMask { comps[0...$0].joined(separator: ".") } } - - private func contains( - _ path: String, - in set: Set - ) -> Bool { - for level in levels(path: path) { - if set.contains(level) { - return true - } - } - return false - } } extension Google_Protobuf_FieldMask { diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 56d75e824..60f2f880c 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -206,14 +206,17 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { // 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. + // 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.c.d"]) + 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: From 795b22a614cb348264a62e145b464347c1db1e66 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 14 Dec 2023 01:30:23 +0330 Subject: [PATCH 14/33] Rename field mask error --- .../Google_Protobuf_FieldMask+Extensions.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 4538a96f1..3e7f56c3f 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -206,7 +206,7 @@ extension Google_Protobuf_FieldMask { var paths: [String] = [] for number in fieldNumbers { guard let name = M.protoName(for: number) else { - throw FieldMaskUtilsError.invalidFieldNumber + throw FieldMaskError.invalidFieldNumber } paths.append(name) } @@ -229,7 +229,7 @@ extension Google_Protobuf_FieldMask { of messageType: M.Type ) throws { guard M.isPathValid(path) else { - throw FieldMaskUtilsError.invalidPath + throw FieldMaskError.invalidPath } paths.append(path) } @@ -354,7 +354,7 @@ extension Google_Protobuf_FieldMask { } /// Describes errors could happen during FieldMask utilities. -public enum FieldMaskUtilsError: Error { +public enum FieldMaskError: Error { /// Describes a path is invalid for a Message type. case invalidPath From 095dde28115a05e70f4cfe638ba16f8afa5e6c1f Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 14 Dec 2023 01:57:39 +0330 Subject: [PATCH 15/33] Fix intersect and subtract --- .../Google_Protobuf_FieldMask+Extensions.swift | 10 ++++++++-- Tests/SwiftProtobufTests/Test_FieldMask.swift | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 3e7f56c3f..412b9ec9b 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -282,7 +282,10 @@ extension Google_Protobuf_FieldMask { ) -> Google_Protobuf_FieldMask { let set = mask.pathsSet var _paths: [String] = [] - for path in paths where set.contains(path) { + var _buffer = Set() + for path in paths where set.contains(path) + && !_buffer.contains(path) { + _buffer.insert(path) _paths.append(path) } return .with { mask in @@ -300,7 +303,10 @@ extension Google_Protobuf_FieldMask { ) -> Google_Protobuf_FieldMask { let set = mask.pathsSet var _paths: [String] = [] - for path in paths where !set.contains(path) { + var _buffer = Set() + for path in paths where !set.contains(path) + && !_buffer.contains(path) { + _buffer.insert(path) _paths.append(path) } return .with { mask in diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 60f2f880c..f794759dc 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -279,6 +279,10 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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. @@ -298,6 +302,10 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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. @@ -317,6 +325,10 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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"]) } } From 37f1872604a638d7c93246c7ac475b8d4a533019 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 14 Dec 2023 02:49:34 +0330 Subject: [PATCH 16/33] Implement merge option --- Sources/SwiftProtobuf/Message+FieldMask.swift | 15 +- Sources/SwiftProtobuf/SetPathDecoder.swift | 149 +++++++++++------- Tests/SwiftProtobufTests/Test_FieldMask.swift | 21 +++ 3 files changed, 122 insertions(+), 63 deletions(-) diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 5990860ed..76ff832fe 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -34,6 +34,10 @@ extension Message { } } +public enum MergeOption: Equatable { + case replaceRepeatedFields +} + extension Message { /// Merges fields specified in a FieldMask into another message. @@ -43,16 +47,23 @@ extension Message { /// - fieldMask: FieldMask specifies which fields should be merged. public mutating func merge( to source: Self, - fieldMask: Google_Protobuf_FieldMask + fieldMask: Google_Protobuf_FieldMask, + mergeOptions: [MergeOption] = [] ) throws { var source = source var copy = self var pathToValueMap: [String: Any?] = [:] + let replaceRepeatedFields = mergeOptions + .contains(.replaceRepeatedFields) for path in fieldMask.paths { pathToValueMap[path] = try source.get(path: path) } for (path, value) in pathToValueMap { - try copy.set(path: path, value: value) + try copy.set( + path: path, + value: value, + replaceRepeatedFields: replaceRepeatedFields + ) } self = copy } diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 6b4094d92..54352b156 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -38,20 +38,37 @@ struct SetPathDecoder: Decoder { private let value: Any? private var number: Int? private let nextPath: [String] + private let replaceRepeatedFields: Bool - init(path: [String], value: Any?) { + init( + path: [String], + value: Any?, + replaceRepeatedFields: Bool + ) { if let firstComponent = path.first { self.number = T.number(for: firstComponent) } self.nextPath = .init(path.dropFirst()) self.value = value + self.replaceRepeatedFields = replaceRepeatedFields } - func _value(as: V.Type) throws -> V { + private func setValue(_ value: inout V) throws { guard let __value = self.value as? V else { throw PathDecodingError.typeMismatch } - return __value + value = __value + } + + private func setRepeatedValue(_ value: inout [V]) throws { + guard let __value = self.value as? [V] else { + throw PathDecodingError.typeMismatch + } + if replaceRepeatedFields { + value = __value + } else { + value.append(contentsOf: __value) + } } mutating func handleConflictingOneOf() throws {} @@ -62,212 +79,214 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularFloatField(value: inout Float) throws { - value = try _value(as: Float.self) + try setValue(&value) } mutating func decodeSingularFloatField(value: inout Float?) throws { - value = try _value(as: Float?.self) + try setValue(&value) } mutating func decodeRepeatedFloatField(value: inout [Float]) throws { - value = try _value(as: [Float].self) + try setRepeatedValue(&value) } mutating func decodeSingularDoubleField(value: inout Double) throws { - value = try _value(as: Double.self) + try setValue(&value) } mutating func decodeSingularDoubleField(value: inout Double?) throws { - value = try _value(as: Double?.self) + try setValue(&value) } mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { - value = try _value(as: [Double].self) + try setRepeatedValue(&value) } mutating func decodeSingularInt32Field(value: inout Int32) throws { - value = try _value(as: Int32.self) + try setValue(&value) } mutating func decodeSingularInt32Field(value: inout Int32?) throws { - value = try _value(as: Int32?.self) + try setValue(&value) } mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { - value = try _value(as: [Int32].self) + try setRepeatedValue(&value) } mutating func decodeSingularInt64Field(value: inout Int64) throws { - value = try _value(as: Int64.self) + try setValue(&value) } mutating func decodeSingularInt64Field(value: inout Int64?) throws { - value = try _value(as: Int64?.self) + try setValue(&value) } mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { - value = try _value(as: [Int64].self) + try setRepeatedValue(&value) } mutating func decodeSingularUInt32Field(value: inout UInt32) throws { - value = try _value(as: UInt32.self) + try setValue(&value) } mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { - value = try _value(as: UInt32?.self) + try setValue(&value) } mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { - value = try _value(as: [UInt32].self) + try setRepeatedValue(&value) } mutating func decodeSingularUInt64Field(value: inout UInt64) throws { - value = try _value(as: UInt64.self) + try setValue(&value) } mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { - value = try _value(as: UInt64?.self) + try setValue(&value) } mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { - value = try _value(as: [UInt64].self) + try setRepeatedValue(&value) } mutating func decodeSingularSInt32Field(value: inout Int32) throws { - value = try _value(as: Int32.self) + try setValue(&value) } mutating func decodeSingularSInt32Field(value: inout Int32?) throws { - value = try _value(as: Int32?.self) + try setValue(&value) } mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { - value = try _value(as: [Int32].self) + try setRepeatedValue(&value) } mutating func decodeSingularSInt64Field(value: inout Int64) throws { - value = try _value(as: Int64.self) + try setValue(&value) } mutating func decodeSingularSInt64Field(value: inout Int64?) throws { - value = try _value(as: Int64?.self) + try setValue(&value) } mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { - value = try _value(as: [Int64].self) + try setRepeatedValue(&value) } mutating func decodeSingularFixed32Field(value: inout UInt32) throws { - value = try _value(as: UInt32.self) + try setValue(&value) } mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { - value = try _value(as: UInt32?.self) + try setValue(&value) } mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { - value = try _value(as: [UInt32].self) + try setRepeatedValue(&value) } mutating func decodeSingularFixed64Field(value: inout UInt64) throws { - value = try _value(as: UInt64.self) + try setValue(&value) } mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { - value = try _value(as: UInt64?.self) + try setValue(&value) } mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { - value = try _value(as: [UInt64].self) + try setRepeatedValue(&value) } mutating func decodeSingularSFixed32Field(value: inout Int32) throws { - value = try _value(as: Int32.self) + try setValue(&value) } mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { - value = try _value(as: Int32?.self) + try setValue(&value) } mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { - value = try _value(as: [Int32].self) + try setRepeatedValue(&value) } mutating func decodeSingularSFixed64Field(value: inout Int64) throws { - value = try _value(as: Int64.self) + try setValue(&value) } mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { - value = try _value(as: Int64?.self) + try setValue(&value) } mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { - value = try _value(as: [Int64].self) + try setRepeatedValue(&value) } mutating func decodeSingularBoolField(value: inout Bool) throws { - value = try _value(as: Bool.self) + try setValue(&value) } mutating func decodeSingularBoolField(value: inout Bool?) throws { - value = try _value(as: Bool?.self) + try setValue(&value) } mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { - value = try _value(as: [Bool].self) + try setRepeatedValue(&value) } mutating func decodeSingularStringField(value: inout String) throws { - value = try _value(as: String.self) + try setValue(&value) } mutating func decodeSingularStringField(value: inout String?) throws { - value = try _value(as: String?.self) + try setValue(&value) } mutating func decodeRepeatedStringField(value: inout [String]) throws { - value = try _value(as: [String].self) + try setRepeatedValue(&value) } mutating func decodeSingularBytesField(value: inout Data) throws { - value = try _value(as: Data.self) + try setValue(&value) } mutating func decodeSingularBytesField(value: inout Data?) throws { - value = try _value(as: Data?.self) + try setValue(&value) } mutating func decodeRepeatedBytesField(value: inout [Data]) throws { - value = try _value(as: [Data].self) + try setRepeatedValue(&value) } mutating func decodeSingularEnumField( value: inout E ) throws where E : Enum, E.RawValue == Int { - value = try _value(as: E.self) + try setValue(&value) } mutating func decodeSingularEnumField( value: inout E? ) throws where E : Enum, E.RawValue == Int { - value = try _value(as: E?.self) + try setValue(&value) } mutating func decodeRepeatedEnumField( value: inout [E] ) throws where E : Enum, E.RawValue == Int { - value = try _value(as: [E].self) + try setRepeatedValue(&value) } mutating func decodeSingularMessageField( value: inout M? ) throws where M : Message { if nextPath.isEmpty { - value = try _value(as: M?.self) + try setValue(&value) return } var decoder = SetPathDecoder( - path: nextPath, value: self.value + path: nextPath, + value: self.value, + replaceRepeatedFields: replaceRepeatedFields ) if value == nil { value = .init() @@ -278,40 +297,40 @@ struct SetPathDecoder: Decoder { mutating func decodeRepeatedMessageField( value: inout [M] ) throws where M : Message { - value = try _value(as: [M].self) + try setRepeatedValue(&value) } mutating func decodeSingularGroupField( value: inout G? ) throws where G : Message { - value = try _value(as: G?.self) + try setValue(&value) } mutating func decodeRepeatedGroupField( value: inout [G] ) throws where G : Message { - value = try _value(as: [G].self) + try setRepeatedValue(&value) } mutating func decodeMapField( fieldType: _ProtobufMap.Type, value: inout _ProtobufMap.BaseType ) throws where KeyType : MapKeyType, ValueType : MapValueType { - value = try _value(as: _ProtobufMap.BaseType.self) + try setValue(&value) } mutating func decodeMapField( fieldType: _ProtobufEnumMap.Type, value: inout _ProtobufEnumMap.BaseType ) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int { - value = try _value(as: _ProtobufEnumMap.BaseType.self) + try setValue(&value) } mutating func decodeMapField( fieldType: _ProtobufMessageMap.Type, value: inout _ProtobufMessageMap.BaseType ) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : Message { - value = try _value(as: _ProtobufMessageMap.BaseType.self) + try setValue(&value) } mutating func decodeExtensionField( @@ -323,9 +342,17 @@ struct SetPathDecoder: Decoder { } extension Message { - mutating func `set`(path: String, value: Any?) throws { + mutating func `set`( + path: String, + value: Any?, + replaceRepeatedFields: Bool + ) throws { let _path = path.components(separatedBy: ".") - var decoder = SetPathDecoder(path: _path, value: value) + var decoder = SetPathDecoder( + path: _path, + value: value, + replaceRepeatedFields: replaceRepeatedFields + ) try decodeMessage(decoder: &decoder) } } diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index f794759dc..609ce93e7 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -134,6 +134,27 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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(to: secondMessage, fieldMask: fieldMask) + XCTAssertEqual(message.repeatedInt32, [1, 2, 3, 4]) + + // Checks with replacing repeated fields + try message.merge(to: secondMessage, fieldMask: fieldMask, mergeOptions: [.replaceRepeatedFields]) + XCTAssertEqual(message.repeatedInt32, [3, 4]) + } + // Checks trim functionality for field masks. func testTrimFieldsOfMessage() throws { var message = SwiftProtoTesting_TestAllTypes.with { model in From 08270b5ee2c38604b7601a48c6d40df1d045a3e4 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 14 Dec 2023 03:01:54 +0330 Subject: [PATCH 17/33] Add comments --- Sources/SwiftProtobuf/Message+FieldMask.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 76ff832fe..8b664c175 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -34,7 +34,13 @@ extension Message { } } +/// Defines available options for merging two messages. public enum MergeOption: Equatable { + + /// 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. case replaceRepeatedFields } From d657a819d9d59f85c4cb16672d23729f7e6e13d4 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 15 Dec 2023 01:25:33 +0330 Subject: [PATCH 18/33] Fix bug of extra paths --- Sources/SwiftProtobuf/GetPathDecoder.swift | 131 +++++++++--------- Sources/SwiftProtobuf/SetPathDecoder.swift | 6 + Tests/SwiftProtobufTests/Test_FieldMask.swift | 9 +- 3 files changed, 80 insertions(+), 66 deletions(-) diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift index b6a01c30d..37de2ea52 100644 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -30,10 +30,12 @@ struct GetPathDecoder: Decoder { _hasPath } - internal init(path: [String]) { - if let firstComponent = path.first { - self.number = T.number(for: firstComponent) + internal init(path: [String]) throws { + guard let firstComponent = path.first, + let number = T.number(for: firstComponent) else { + throw PathDecodingError.pathNotFound } + self.number = number self.nextPath = .init(path.dropFirst()) } @@ -44,211 +46,214 @@ struct GetPathDecoder: Decoder { return number } - private mutating func captureValue(_ value: Any?) { + private mutating func captureValue(_ value: Any?) throws { + if !nextPath.isEmpty { + throw PathDecodingError.pathNotFound + } self._value = value self._hasPath = true } mutating func decodeSingularFloatField(value: inout Float) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularFloatField(value: inout Float?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedFloatField(value: inout [Float]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularDoubleField(value: inout Double) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularDoubleField(value: inout Double?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularInt32Field(value: inout Int32) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularInt32Field(value: inout Int32?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularInt64Field(value: inout Int64) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularInt64Field(value: inout Int64?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularUInt32Field(value: inout UInt32) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularUInt64Field(value: inout UInt64) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSInt32Field(value: inout Int32) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSInt32Field(value: inout Int32?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSInt64Field(value: inout Int64) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSInt64Field(value: inout Int64?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularFixed32Field(value: inout UInt32) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularFixed64Field(value: inout UInt64) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSFixed32Field(value: inout Int32) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSFixed64Field(value: inout Int64) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularBoolField(value: inout Bool) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularBoolField(value: inout Bool?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularStringField(value: inout String) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularStringField(value: inout String?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedStringField(value: inout [String]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularBytesField(value: inout Data) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularBytesField(value: inout Data?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedBytesField(value: inout [Data]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularEnumField(value: inout E) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularEnumField(value: inout E?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedEnumField(value: inout [E]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularMessageField( value: inout M? ) throws where M : Message { if nextPath.isEmpty { - captureValue(value) + try captureValue(value) return } - var decoder = GetPathDecoder(path: nextPath) + var decoder = try GetPathDecoder(path: nextPath) if value != nil { try value?.decodeMessage(decoder: &decoder) } else { @@ -260,36 +265,36 @@ struct GetPathDecoder: Decoder { } mutating func decodeRepeatedMessageField(value: inout [M]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeSingularGroupField(value: inout G?) throws { - captureValue(value) + try captureValue(value) } mutating func decodeRepeatedGroupField(value: inout [G]) throws { - captureValue(value) + try captureValue(value) } mutating func decodeMapField( fieldType: _ProtobufMap.Type, value: inout _ProtobufMap.BaseType ) throws { - captureValue(value) + try captureValue(value) } mutating func decodeMapField( fieldType: _ProtobufEnumMap.Type, value: inout _ProtobufEnumMap.BaseType ) throws { - captureValue(value) + try captureValue(value) } mutating func decodeMapField( fieldType: _ProtobufMessageMap.Type, value: inout _ProtobufMessageMap.BaseType ) throws { - captureValue(value) + try captureValue(value) } mutating func decodeExtensionField( @@ -303,14 +308,16 @@ struct GetPathDecoder: Decoder { extension Message { mutating func `get`(path: String) throws -> Any? { let _path = path.components(separatedBy: ".") - var decoder = GetPathDecoder(path: _path) + var decoder = try GetPathDecoder(path: _path) try decodeMessage(decoder: &decoder) return decoder.value } mutating func hasPath(path: String) -> Bool { let _path = path.components(separatedBy: ".") - var decoder = GetPathDecoder(path: _path) + guard var decoder = try? GetPathDecoder(path: _path) else { + return false + } try? decodeMessage(decoder: &decoder) return decoder.hasPath } diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 54352b156..49bb71957 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -22,6 +22,12 @@ public enum PathDecodingError: Error { /// 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 property with path this error + /// will be thrown. + case pathNotFound } extension Message { diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 609ce93e7..5df441b99 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -184,15 +184,16 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } // Checks `isPathValid` func - // 1. Valid primitive path should be valid. - // 2. Valid nested path should be valid. - // 3. Invalid primitive path should be valid. - // 4. Invalid nested path should be valid. + // 1. Valid primitive path. + // 2. Valid nested path. + // 3. Invalid primitive path. + // 4, 5. Invalid nested path. func testIsPathValid() { XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int32")) XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bb")) XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int")) XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bc")) + XCTAssertFalse(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bb.a")) } // Checks `isValid` func of FieldMask. From 3269433183db3f0d96f1b6caf8b14aac7157c189 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 15 Dec 2023 02:00:24 +0330 Subject: [PATCH 19/33] Fix comment --- Sources/SwiftProtobuf/SetPathDecoder.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 49bb71957..f7b821740 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -25,8 +25,8 @@ public enum PathDecodingError: Error { /// Describes path is not found in message type. /// - /// If a message has no property with path this error - /// will be thrown. + /// If a message has no field with the given path this + /// error will be thrown. case pathNotFound } From d56489511fd057cc07082987b2c05dd9c9b64e01 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 15 Dec 2023 17:55:28 +0330 Subject: [PATCH 20/33] Change if to guard --- Sources/SwiftProtobuf/GetPathDecoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift index 37de2ea52..ac5fa5a0b 100644 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -47,7 +47,7 @@ struct GetPathDecoder: Decoder { } private mutating func captureValue(_ value: Any?) throws { - if !nextPath.isEmpty { + guard nextPath.isEmpty else { throw PathDecodingError.pathNotFound } self._value = value From fcec7f22337ac3bb3ec1c690728370653e24dc9d Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 22 Dec 2023 19:11:25 +0330 Subject: [PATCH 21/33] Fix comments --- Sources/SwiftProtobuf/GetPathDecoder.swift | 32 ++++++----- ...Google_Protobuf_FieldMask+Extensions.swift | 13 +---- Sources/SwiftProtobuf/Message+FieldMask.swift | 41 ++++++++++---- Sources/SwiftProtobuf/SetPathDecoder.swift | 56 +++++++++++++++---- Tests/SwiftProtobufTests/Test_FieldMask.swift | 23 +++++++- 5 files changed, 117 insertions(+), 48 deletions(-) diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift index ac5fa5a0b..913e69fd2 100644 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -14,21 +14,21 @@ import Foundation +// Decoder that captures value of a message field from the given path struct GetPathDecoder: Decoder { + // The path only including sub-paths private let nextPath: [String] - private var number: Int? - private var _value: Any? - private var _hasPath: Bool = false + // Field number should be captured by decoder + private var number: Int? - internal var value: Any? { - _value - } + // Captured value after decoding will be stored in this property + private(set) var value: Any? - internal var hasPath: Bool { - _hasPath - } + // While decoding this property becomes true if the path exists + // Note that a property value could be nil while its path is found + private(set) var hasPath: Bool = false internal init(path: [String]) throws { guard let firstComponent = path.first, @@ -50,8 +50,8 @@ struct GetPathDecoder: Decoder { guard nextPath.isEmpty else { throw PathDecodingError.pathNotFound } - self._value = value - self._hasPath = true + self.value = value + self.hasPath = true } mutating func decodeSingularFloatField(value: inout Float) throws { @@ -260,8 +260,8 @@ struct GetPathDecoder: Decoder { var tmp = M() try tmp.decodeMessage(decoder: &decoder) } - self._value = decoder.value - self._hasPath = decoder.hasPath + self.value = decoder.value + self.hasPath = decoder.hasPath } mutating func decodeRepeatedMessageField(value: inout [M]) throws { @@ -301,7 +301,11 @@ struct GetPathDecoder: Decoder { values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int - ) throws {} + ) throws { + preconditionFailure( + "Path decoder should never decode an extension field" + ) + } } diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 412b9ec9b..647896f93 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -283,8 +283,7 @@ extension Google_Protobuf_FieldMask { let set = mask.pathsSet var _paths: [String] = [] var _buffer = Set() - for path in paths where set.contains(path) - && !_buffer.contains(path) { + for path in paths where set.contains(path) && !_buffer.contains(path) { _buffer.insert(path) _paths.append(path) } @@ -304,8 +303,7 @@ extension Google_Protobuf_FieldMask { let set = mask.pathsSet var _paths: [String] = [] var _buffer = Set() - for path in paths where !set.contains(path) - && !_buffer.contains(path) { + for path in paths where !set.contains(path) && !_buffer.contains(path) { _buffer.insert(path) _paths.append(path) } @@ -334,13 +332,6 @@ extension Google_Protobuf_FieldMask { private var pathsSet: Set { .init(paths) } - - private func levels(path: String) -> [String] { - let comps = path.components(separatedBy: ".") - return (0..: 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] - private let replaceRepeatedFields: Bool + + // Merge options to be concidered while setting value + private let mergeOption: MergeOption + + private var replaceRepeatedFields: Bool { + mergeOption.replaceRepeatedFields + } init( path: [String], value: Any?, - replaceRepeatedFields: Bool + mergeOption: MergeOption ) { - if let firstComponent = path.first { - self.number = T.number(for: firstComponent) + if let firstComponent = path.first, + let number = T.number(for: firstComponent) { + self.number = number } self.nextPath = .init(path.dropFirst()) self.value = value - self.replaceRepeatedFields = replaceRepeatedFields + self.mergeOption = mergeOption } private func setValue(_ value: inout V) throws { @@ -77,6 +90,21 @@ struct SetPathDecoder: Decoder { } } + private func setMapValue( + _ value: inout Dictionary + ) throws { + guard let __value = self.value as? Dictionary else { + throw PathDecodingError.typeMismatch + } + if replaceRepeatedFields { + value = __value + } else { + value.merge(__value) { _, new in + new + } + } + } + mutating func handleConflictingOneOf() throws {} mutating func nextFieldNumber() throws -> Int? { @@ -292,7 +320,7 @@ struct SetPathDecoder: Decoder { var decoder = SetPathDecoder( path: nextPath, value: self.value, - replaceRepeatedFields: replaceRepeatedFields + mergeOption: mergeOption ) if value == nil { value = .init() @@ -322,28 +350,32 @@ struct SetPathDecoder: Decoder { fieldType: _ProtobufMap.Type, value: inout _ProtobufMap.BaseType ) throws where KeyType : MapKeyType, ValueType : MapValueType { - try setValue(&value) + try setMapValue(&value) } mutating func decodeMapField( fieldType: _ProtobufEnumMap.Type, value: inout _ProtobufEnumMap.BaseType ) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int { - try setValue(&value) + try setMapValue(&value) } mutating func decodeMapField( fieldType: _ProtobufMessageMap.Type, value: inout _ProtobufMessageMap.BaseType ) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : Message { - try setValue(&value) + try setMapValue(&value) } mutating func decodeExtensionField( values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int - ) throws {} + ) throws { + preconditionFailure( + "Path decoder should never decode an extension field" + ) + } } @@ -351,13 +383,13 @@ extension Message { mutating func `set`( path: String, value: Any?, - replaceRepeatedFields: Bool + mergeOption: MergeOption ) throws { let _path = path.components(separatedBy: ".") var decoder = SetPathDecoder( path: _path, value: value, - replaceRepeatedFields: replaceRepeatedFields + mergeOption: mergeOption ) try decodeMessage(decoder: &decoder) } diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 5df441b99..6e3defa28 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -151,10 +151,31 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertEqual(message.repeatedInt32, [1, 2, 3, 4]) // Checks with replacing repeated fields - try message.merge(to: secondMessage, fieldMask: fieldMask, mergeOptions: [.replaceRepeatedFields]) + try message.merge(to: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) 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(to: secondMessage, fieldMask: fieldMask) + XCTAssertEqual(message.mapInt32String, [1: "a", 2: "b"]) + + // Checks with replacing repeated fields + try message.merge(to: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) + XCTAssertEqual(message.mapInt32String, [2: "b"]) + } + // Checks trim functionality for field masks. func testTrimFieldsOfMessage() throws { var message = SwiftProtoTesting_TestAllTypes.with { model in From 787957feac06b51a2613917f096debe8584b8c56 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 22 Dec 2023 21:27:19 +0330 Subject: [PATCH 22/33] Fix variable names with underscore --- Sources/SwiftProtobuf/GetPathDecoder.swift | 8 ++--- ...Google_Protobuf_FieldMask+Extensions.swift | 36 +++++++++---------- Sources/SwiftProtobuf/SetPathDecoder.swift | 16 ++++----- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift index 913e69fd2..3511b0069 100644 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -311,15 +311,15 @@ struct GetPathDecoder: Decoder { extension Message { mutating func `get`(path: String) throws -> Any? { - let _path = path.components(separatedBy: ".") - var decoder = try GetPathDecoder(path: _path) + let components = path.components(separatedBy: ".") + var decoder = try GetPathDecoder(path: components) try decodeMessage(decoder: &decoder) return decoder.value } mutating func hasPath(path: String) -> Bool { - let _path = path.components(separatedBy: ".") - guard var decoder = try? GetPathDecoder(path: _path) else { + let components = path.components(separatedBy: ".") + guard var decoder = try? GetPathDecoder(path: components) else { return false } try? decodeMessage(decoder: &decoder) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 647896f93..e098e5f84 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -262,14 +262,14 @@ extension Google_Protobuf_FieldMask { _ mask: Google_Protobuf_FieldMask ) -> Google_Protobuf_FieldMask { var buffer: Set = .init() - var _paths: [String] = [] - let allPaths = paths + mask.paths + var paths: [String] = [] + let allPaths = self.paths + mask.paths for path in allPaths where !buffer.contains(path) { buffer.insert(path) - _paths.append(path) + paths.append(path) } return .with { mask in - mask.paths = _paths + mask.paths = paths } } @@ -281,14 +281,14 @@ extension Google_Protobuf_FieldMask { _ mask: Google_Protobuf_FieldMask ) -> Google_Protobuf_FieldMask { let set = mask.pathsSet - var _paths: [String] = [] - var _buffer = Set() - for path in paths where set.contains(path) && !_buffer.contains(path) { - _buffer.insert(path) - _paths.append(path) + 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 + mask.paths = paths } } @@ -301,14 +301,14 @@ extension Google_Protobuf_FieldMask { _ mask: Google_Protobuf_FieldMask ) -> Google_Protobuf_FieldMask { let set = mask.pathsSet - var _paths: [String] = [] - var _buffer = Set() - for path in paths where !set.contains(path) && !_buffer.contains(path) { - _buffer.insert(path) - _paths.append(path) + 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 + mask.paths = paths } } @@ -320,8 +320,8 @@ extension Google_Protobuf_FieldMask { /// - Parameter path: Path to be checked. /// - Returns: Boolean determines is path covered. public func contains(_ path: String) -> Bool { - for _path in paths { - if path.hasPrefix("\(_path).") || _path == path { + for fieldMaskPath in paths { + if path.hasPrefix("\(fieldMaskPath).") || fieldMaskPath == path { return true } } diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 3232d9ac4..42f729490 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -73,33 +73,33 @@ struct SetPathDecoder: Decoder { } private func setValue(_ value: inout V) throws { - guard let __value = self.value as? V else { + guard let castedValue = self.value as? V else { throw PathDecodingError.typeMismatch } - value = __value + value = castedValue } private func setRepeatedValue(_ value: inout [V]) throws { - guard let __value = self.value as? [V] else { + guard let castedValue = self.value as? [V] else { throw PathDecodingError.typeMismatch } if replaceRepeatedFields { - value = __value + value = castedValue } else { - value.append(contentsOf: __value) + value.append(contentsOf: castedValue) } } private func setMapValue( _ value: inout Dictionary ) throws { - guard let __value = self.value as? Dictionary else { + guard let castedValue = self.value as? Dictionary else { throw PathDecodingError.typeMismatch } if replaceRepeatedFields { - value = __value + value = castedValue } else { - value.merge(__value) { _, new in + value.merge(castedValue) { _, new in new } } From eedb785f5546d4d8f006862d601f60ac88b4763f Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Wed, 17 Jan 2024 13:50:36 +0330 Subject: [PATCH 23/33] Fix comments --- Sources/SwiftProtobuf/Message+FieldMask.swift | 4 ++-- Sources/SwiftProtobuf/SetPathDecoder.swift | 4 +++- Tests/SwiftProtobufTests/Test_FieldMask.swift | 12 ++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 9d6b05489..04ebda6e8 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -56,7 +56,7 @@ extension Message { /// - source: Message should be merged to the original one. /// - fieldMask: FieldMask specifies which fields should be merged. public mutating func merge( - to source: Self, + from source: Self, fieldMask: Google_Protobuf_FieldMask, mergeOption: MergeOption = .init() ) throws { @@ -96,7 +96,7 @@ extension Message where Self: Equatable, Self: _ProtoNameProviding { } var tmp = Self(removingAllFieldsOf: self) do { - try tmp.merge(to: self, fieldMask: fieldMask) + try tmp.merge(from: self, fieldMask: fieldMask) let changed = tmp != self self = tmp return changed diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 42f729490..d406beb6b 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -66,8 +66,10 @@ struct SetPathDecoder: Decoder { if let firstComponent = path.first, let number = T.number(for: firstComponent) { self.number = number + self.nextPath = .init(path.dropFirst()) + } else { + self.nextPath = [] } - self.nextPath = .init(path.dropFirst()) self.value = value self.mergeOption = mergeOption } diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 6e3defa28..4fddf2598 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -124,12 +124,12 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } // Checks nested message merge - try message.merge(to: secondMessage, fieldMask: .init(protoPaths: "optional_nested_message.bb")) + 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(to: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) + try message.merge(from: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) XCTAssertEqual(message.optionalInt32, 2) XCTAssertEqual(message.optionalNestedMessage.bb, 3) } @@ -147,11 +147,11 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["repeated_int32"]) // Checks without replacing repeated fields - try message.merge(to: secondMessage, fieldMask: fieldMask) + try message.merge(from: secondMessage, fieldMask: fieldMask) XCTAssertEqual(message.repeatedInt32, [1, 2, 3, 4]) // Checks with replacing repeated fields - try message.merge(to: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) XCTAssertEqual(message.repeatedInt32, [3, 4]) } @@ -168,11 +168,11 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["map_int32_string"]) // Checks without replacing repeated fields - try message.merge(to: secondMessage, fieldMask: fieldMask) + try message.merge(from: secondMessage, fieldMask: fieldMask) XCTAssertEqual(message.mapInt32String, [1: "a", 2: "b"]) // Checks with replacing repeated fields - try message.merge(to: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) XCTAssertEqual(message.mapInt32String, [2: "b"]) } From d7ba53258a3f081bbfa1994c9471c1748737dd98 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 2 Feb 2024 13:07:56 +0330 Subject: [PATCH 24/33] Fix comments --- Sources/SwiftProtobuf/Message+FieldMask.swift | 23 ++++++++++--------- Sources/SwiftProtobuf/SetPathDecoder.swift | 6 ++--- Tests/SwiftProtobufTests/Test_FieldMask.swift | 8 +++++-- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 04ebda6e8..c120de12a 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -34,18 +34,19 @@ extension Message { } } -/// Defines available options for merging two messages. -public struct MergeOption { +extension Google_Protobuf_FieldMask { - public init(replaceRepeatedFields: Bool = false) { - self.replaceRepeatedFields = replaceRepeatedFields - } + /// 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 + /// 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 { @@ -58,7 +59,7 @@ extension Message { public mutating func merge( from source: Self, fieldMask: Google_Protobuf_FieldMask, - mergeOption: MergeOption = .init() + mergeOption: Google_Protobuf_FieldMask.MergeOptions = .init() ) throws { var source = source var pathToValueMap: [String: Any?] = [:] diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index d406beb6b..6b18c9668 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -52,7 +52,7 @@ struct SetPathDecoder: Decoder { private let nextPath: [String] // Merge options to be concidered while setting value - private let mergeOption: MergeOption + private let mergeOption: Google_Protobuf_FieldMask.MergeOptions private var replaceRepeatedFields: Bool { mergeOption.replaceRepeatedFields @@ -61,7 +61,7 @@ struct SetPathDecoder: Decoder { init( path: [String], value: Any?, - mergeOption: MergeOption + mergeOption: Google_Protobuf_FieldMask.MergeOptions ) { if let firstComponent = path.first, let number = T.number(for: firstComponent) { @@ -385,7 +385,7 @@ extension Message { mutating func `set`( path: String, value: Any?, - mergeOption: MergeOption + mergeOption: Google_Protobuf_FieldMask.MergeOptions ) throws { let _path = path.components(separatedBy: ".") var decoder = SetPathDecoder( diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 4fddf2598..cb43487c0 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -151,7 +151,9 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertEqual(message.repeatedInt32, [1, 2, 3, 4]) // Checks with replacing repeated fields - try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) + var options = Google_Protobuf_FieldMask.MergeOptions() + options.replaceRepeatedFields = true + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: options) XCTAssertEqual(message.repeatedInt32, [3, 4]) } @@ -172,7 +174,9 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertEqual(message.mapInt32String, [1: "a", 2: "b"]) // Checks with replacing repeated fields - try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: .init(replaceRepeatedFields: true)) + var options = Google_Protobuf_FieldMask.MergeOptions() + options.replaceRepeatedFields = true + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: options) XCTAssertEqual(message.mapInt32String, [2: "b"]) } From 03f5f0d25f18277cdc7f33e21f88a308fc0ddbcb Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 29 Mar 2024 17:28:19 +0330 Subject: [PATCH 25/33] Fix build errors in swift 5.8 and later using any keyword for type-casting to protocols --- Sources/SwiftProtobuf/GetPathDecoder.swift | 2 +- Sources/SwiftProtobuf/Message+FieldMask.swift | 6 +++--- Sources/SwiftProtobuf/SetPathDecoder.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift index 3511b0069..e116cc880 100644 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ b/Sources/SwiftProtobuf/GetPathDecoder.swift @@ -299,7 +299,7 @@ struct GetPathDecoder: Decoder { mutating func decodeExtensionField( values: inout ExtensionFieldValueSet, - messageType: Message.Type, + messageType: any Message.Type, fieldNumber: Int ) throws { preconditionFailure( diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index c120de12a..7560a9da3 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -109,8 +109,8 @@ extension Message where Self: Equatable, Self: _ProtoNameProviding { private extension Message { init(removingAllFieldsOf message: Self) { - if let type = Self.self as? ExtensibleMessage.Type, - let extensible = message as? ExtensibleMessage { + if let type = Self.self as? any ExtensibleMessage.Type, + let extensible = message as? any ExtensibleMessage { self = type.init(extensionsOf: extensible) as? Self ?? .init() } else { self = .init() @@ -120,7 +120,7 @@ private extension Message { } private extension Message where Self: ExtensibleMessage { - init(extensionsOf message: ExtensibleMessage) { + init(extensionsOf message: any ExtensibleMessage) { self.init() _protobuf_extensionFieldValues = message._protobuf_extensionFieldValues } diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/SetPathDecoder.swift index 6b18c9668..692ec8b7d 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/SetPathDecoder.swift @@ -32,7 +32,7 @@ public enum PathDecodingError: Error { extension Message { static func number(for field: String) -> Int? { - guard let type = Self.self as? _ProtoNameProviding.Type else { + guard let type = Self.self as? any _ProtoNameProviding.Type else { return nil } return type._protobuf_nameMap.number(forJSONName: field) @@ -371,7 +371,7 @@ struct SetPathDecoder: Decoder { mutating func decodeExtensionField( values: inout ExtensionFieldValueSet, - messageType: Message.Type, + messageType: any Message.Type, fieldNumber: Int ) throws { preconditionFailure( From 9e15359365f0b8358ecb8ea89a0f7b1a6b21a481 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 29 Mar 2024 18:11:08 +0330 Subject: [PATCH 26/33] Add a test for trim with extensible messages --- Tests/SwiftProtobufTests/Test_FieldMask.swift | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index cb43487c0..ace9c47ad 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -195,7 +195,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { XCTAssertEqual(message.optionalInt32, 0) XCTAssertEqual(message.optionalNestedMessage.bb, 2) - // Checks trim should does nothing with an empty fieldMask. + // Checks trim should do nothing with an empty fieldMask. let r2 = message.trim(fieldMask: .init()) XCTAssertFalse(r2) @@ -208,6 +208,25 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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: ["singularString"]) + + // Checks trim should retain extensions while removes other fields. + let r1 = message.trim(fieldMask: 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(fieldMask: mask) + XCTAssertFalse(r2) + XCTAssertEqual(message.SwiftProtoTesting_Fuzz_singularInt32Ext, .init(1)) + } + // Checks `isPathValid` func // 1. Valid primitive path. // 2. Valid nested path. From dcfa44277952e664fbcd74544af9afd8a77cbd98 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Sat, 13 Apr 2024 11:18:34 +0330 Subject: [PATCH 27/33] Rename merge(from:) to merge(with:) --- Sources/SwiftProtobuf/Message+FieldMask.swift | 4 ++-- Tests/SwiftProtobufTests/Test_FieldMask.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 7560a9da3..f9885b1f5 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -57,7 +57,7 @@ extension Message { /// - source: Message should be merged to the original one. /// - fieldMask: FieldMask specifies which fields should be merged. public mutating func merge( - from source: Self, + with source: Self, fieldMask: Google_Protobuf_FieldMask, mergeOption: Google_Protobuf_FieldMask.MergeOptions = .init() ) throws { @@ -97,7 +97,7 @@ extension Message where Self: Equatable, Self: _ProtoNameProviding { } var tmp = Self(removingAllFieldsOf: self) do { - try tmp.merge(from: self, fieldMask: fieldMask) + try tmp.merge(with: self, fieldMask: fieldMask) let changed = tmp != self self = tmp return changed diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index ace9c47ad..4eaebc97e 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -124,12 +124,12 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } // Checks nested message merge - try message.merge(from: secondMessage, fieldMask: .init(protoPaths: "optional_nested_message.bb")) + try message.merge(with: 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")) + try message.merge(with: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) XCTAssertEqual(message.optionalInt32, 2) XCTAssertEqual(message.optionalNestedMessage.bb, 3) } @@ -147,13 +147,13 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["repeated_int32"]) // Checks without replacing repeated fields - try message.merge(from: secondMessage, fieldMask: fieldMask) + try message.merge(with: 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) + try message.merge(with: secondMessage, fieldMask: fieldMask, mergeOption: options) XCTAssertEqual(message.repeatedInt32, [3, 4]) } @@ -170,13 +170,13 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["map_int32_string"]) // Checks without replacing repeated fields - try message.merge(from: secondMessage, fieldMask: fieldMask) + try message.merge(with: 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) + try message.merge(with: secondMessage, fieldMask: fieldMask, mergeOption: options) XCTAssertEqual(message.mapInt32String, [2: "b"]) } From aae790b481e8d6d5461856c5dc01dfd4439fc7bb Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 3 May 2024 20:36:59 +0330 Subject: [PATCH 28/33] Replace GetPathDecoder with PathVisitor --- Sources/SwiftProtobuf/CMakeLists.txt | 4 +- Sources/SwiftProtobuf/GetPathDecoder.swift | 328 ------------------ Sources/SwiftProtobuf/Message+FieldMask.swift | 23 +- ...SetPathDecoder.swift => PathDecoder.swift} | 117 ++++--- Sources/SwiftProtobuf/PathVisitor.swift | 275 +++++++++++++++ Tests/SwiftProtobufTests/Test_FieldMask.swift | 199 +++++++++++ 6 files changed, 563 insertions(+), 383 deletions(-) delete mode 100644 Sources/SwiftProtobuf/GetPathDecoder.swift rename Sources/SwiftProtobuf/{SetPathDecoder.swift => PathDecoder.swift} (77%) create mode 100644 Sources/SwiftProtobuf/PathVisitor.swift diff --git a/Sources/SwiftProtobuf/CMakeLists.txt b/Sources/SwiftProtobuf/CMakeLists.txt index 158055778..fd7fb867e 100644 --- a/Sources/SwiftProtobuf/CMakeLists.txt +++ b/Sources/SwiftProtobuf/CMakeLists.txt @@ -26,7 +26,6 @@ add_library(SwiftProtobuf FieldTag.swift FieldTypes.swift field_mask.pb.swift - GetPathDecoder.swift Google_Protobuf_Any+Extensions.swift Google_Protobuf_Any+Registry.swift Google_Protobuf_Duration+Extensions.swift @@ -58,11 +57,12 @@ add_library(SwiftProtobuf Message.swift MessageExtension.swift NameMap.swift + PathDecoder.swift + PathVisitor.swift ProtobufAPIVersionCheck.swift ProtobufMap.swift ProtoNameProviding.swift SelectiveVisitor.swift - SetPathDecoder.swift SimpleExtensionMap.swift source_context.pb.swift StringUtils.swift diff --git a/Sources/SwiftProtobuf/GetPathDecoder.swift b/Sources/SwiftProtobuf/GetPathDecoder.swift deleted file mode 100644 index e116cc880..000000000 --- a/Sources/SwiftProtobuf/GetPathDecoder.swift +++ /dev/null @@ -1,328 +0,0 @@ -// Sources/SwiftProtobuf/GetPathDecoder.swift - Path decoder (Getter) -// -// 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 captures value of a field by its path. -/// -// ----------------------------------------------------------------------------- - -import Foundation - -// Decoder that captures value of a message field from the given path -struct GetPathDecoder: Decoder { - - // The path only including sub-paths - private let nextPath: [String] - - // Field number should be captured by decoder - private var number: Int? - - // Captured value after decoding will be stored in this property - private(set) var value: Any? - - // While decoding this property becomes true if the path exists - // Note that a property value could be nil while its path is found - private(set) var hasPath: Bool = false - - internal init(path: [String]) throws { - guard let firstComponent = path.first, - let number = T.number(for: firstComponent) else { - throw PathDecodingError.pathNotFound - } - self.number = number - self.nextPath = .init(path.dropFirst()) - } - - mutating func handleConflictingOneOf() throws {} - - mutating func nextFieldNumber() throws -> Int? { - defer { number = nil } - return number - } - - private mutating func captureValue(_ value: Any?) throws { - guard nextPath.isEmpty else { - throw PathDecodingError.pathNotFound - } - self.value = value - self.hasPath = true - } - - mutating func decodeSingularFloatField(value: inout Float) throws { - try captureValue(value) - } - - mutating func decodeSingularFloatField(value: inout Float?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedFloatField(value: inout [Float]) throws { - try captureValue(value) - } - - mutating func decodeSingularDoubleField(value: inout Double) throws { - try captureValue(value) - } - - mutating func decodeSingularDoubleField(value: inout Double?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { - try captureValue(value) - } - - mutating func decodeSingularInt32Field(value: inout Int32) throws { - try captureValue(value) - } - - mutating func decodeSingularInt32Field(value: inout Int32?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { - try captureValue(value) - } - - mutating func decodeSingularInt64Field(value: inout Int64) throws { - try captureValue(value) - } - - mutating func decodeSingularInt64Field(value: inout Int64?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { - try captureValue(value) - } - - mutating func decodeSingularUInt32Field(value: inout UInt32) throws { - try captureValue(value) - } - - mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { - try captureValue(value) - } - - mutating func decodeSingularUInt64Field(value: inout UInt64) throws { - try captureValue(value) - } - - mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { - try captureValue(value) - } - - mutating func decodeSingularSInt32Field(value: inout Int32) throws { - try captureValue(value) - } - - mutating func decodeSingularSInt32Field(value: inout Int32?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { - try captureValue(value) - } - - mutating func decodeSingularSInt64Field(value: inout Int64) throws { - try captureValue(value) - } - - mutating func decodeSingularSInt64Field(value: inout Int64?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { - try captureValue(value) - } - - mutating func decodeSingularFixed32Field(value: inout UInt32) throws { - try captureValue(value) - } - - mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { - try captureValue(value) - } - - mutating func decodeSingularFixed64Field(value: inout UInt64) throws { - try captureValue(value) - } - - mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { - try captureValue(value) - } - - mutating func decodeSingularSFixed32Field(value: inout Int32) throws { - try captureValue(value) - } - - mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { - try captureValue(value) - } - - mutating func decodeSingularSFixed64Field(value: inout Int64) throws { - try captureValue(value) - } - - mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { - try captureValue(value) - } - - mutating func decodeSingularBoolField(value: inout Bool) throws { - try captureValue(value) - } - - mutating func decodeSingularBoolField(value: inout Bool?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { - try captureValue(value) - } - - mutating func decodeSingularStringField(value: inout String) throws { - try captureValue(value) - } - - mutating func decodeSingularStringField(value: inout String?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedStringField(value: inout [String]) throws { - try captureValue(value) - } - - mutating func decodeSingularBytesField(value: inout Data) throws { - try captureValue(value) - } - - mutating func decodeSingularBytesField(value: inout Data?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedBytesField(value: inout [Data]) throws { - try captureValue(value) - } - - mutating func decodeSingularEnumField(value: inout E) throws { - try captureValue(value) - } - - mutating func decodeSingularEnumField(value: inout E?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedEnumField(value: inout [E]) throws { - try captureValue(value) - } - - mutating func decodeSingularMessageField( - value: inout M? - ) throws where M : Message { - if nextPath.isEmpty { - try captureValue(value) - return - } - var decoder = try GetPathDecoder(path: nextPath) - if value != nil { - try value?.decodeMessage(decoder: &decoder) - } else { - var tmp = M() - try tmp.decodeMessage(decoder: &decoder) - } - self.value = decoder.value - self.hasPath = decoder.hasPath - } - - mutating func decodeRepeatedMessageField(value: inout [M]) throws { - try captureValue(value) - } - - mutating func decodeSingularGroupField(value: inout G?) throws { - try captureValue(value) - } - - mutating func decodeRepeatedGroupField(value: inout [G]) throws { - try captureValue(value) - } - - mutating func decodeMapField( - fieldType: _ProtobufMap.Type, - value: inout _ProtobufMap.BaseType - ) throws { - try captureValue(value) - } - - mutating func decodeMapField( - fieldType: _ProtobufEnumMap.Type, - value: inout _ProtobufEnumMap.BaseType - ) throws { - try captureValue(value) - } - - mutating func decodeMapField( - fieldType: _ProtobufMessageMap.Type, - value: inout _ProtobufMessageMap.BaseType - ) throws { - try captureValue(value) - } - - mutating func decodeExtensionField( - values: inout ExtensionFieldValueSet, - messageType: any Message.Type, - fieldNumber: Int - ) throws { - preconditionFailure( - "Path decoder should never decode an extension field" - ) - } - -} - -extension Message { - mutating func `get`(path: String) throws -> Any? { - let components = path.components(separatedBy: ".") - var decoder = try GetPathDecoder(path: components) - try decodeMessage(decoder: &decoder) - return decoder.value - } - - mutating func hasPath(path: String) -> Bool { - let components = path.components(separatedBy: ".") - guard var decoder = try? GetPathDecoder(path: components) else { - return false - } - try? decodeMessage(decoder: &decoder) - return decoder.hasPath - } -} diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index f9885b1f5..5f68202f3 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -27,6 +27,17 @@ extension Message { 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 { @@ -54,22 +65,20 @@ extension Message { /// Merges fields specified in a FieldMask into another message. /// /// - Parameters: - /// - source: Message should be merged to the original one. + /// - source: Message that should be merged to the original one. /// - fieldMask: FieldMask specifies which fields should be merged. public mutating func merge( with source: Self, fieldMask: Google_Protobuf_FieldMask, mergeOption: Google_Protobuf_FieldMask.MergeOptions = .init() ) throws { - var source = source - var pathToValueMap: [String: Any?] = [:] + var visitor = PathVisitor() + try source.traverse(visitor: &visitor) + let values = visitor.values for path in fieldMask.paths { - pathToValueMap[path] = try source.get(path: path) - } - for (path, value) in pathToValueMap { try? set( path: path, - value: value, + value: values[path], mergeOption: mergeOption ) } diff --git a/Sources/SwiftProtobuf/SetPathDecoder.swift b/Sources/SwiftProtobuf/PathDecoder.swift similarity index 77% rename from Sources/SwiftProtobuf/SetPathDecoder.swift rename to Sources/SwiftProtobuf/PathDecoder.swift index 692ec8b7d..0c664e81a 100644 --- a/Sources/SwiftProtobuf/SetPathDecoder.swift +++ b/Sources/SwiftProtobuf/PathDecoder.swift @@ -1,4 +1,4 @@ -// Sources/SwiftProtobuf/SetPathDecoder.swift - Path decoder (Setter) +// 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 @@ -37,10 +37,17 @@ extension Message { } return type._protobuf_nameMap.number(forJSONName: field) } + + 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 SetPathDecoder: Decoder { +struct PathDecoder: Decoder { // The value should be set to the path private let value: Any? @@ -62,19 +69,26 @@ struct SetPathDecoder: Decoder { 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 { - self.nextPath = [] + throw PathDecodingError.pathNotFound } self.value = value self.mergeOption = mergeOption } - private func setValue(_ value: inout V) throws { + 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 } @@ -82,8 +96,15 @@ struct SetPathDecoder: Decoder { } private func setRepeatedValue(_ value: inout [V]) throws { - guard let castedValue = self.value as? [V] else { - throw PathDecodingError.typeMismatch + 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 @@ -95,8 +116,12 @@ struct SetPathDecoder: Decoder { private func setMapValue( _ value: inout Dictionary ) throws { - guard let castedValue = self.value as? Dictionary else { - throw PathDecodingError.typeMismatch + 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 @@ -115,11 +140,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularFloatField(value: inout Float) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularFloatField(value: inout Float?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedFloatField(value: inout [Float]) throws { @@ -127,11 +152,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularDoubleField(value: inout Double) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularDoubleField(value: inout Double?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedDoubleField(value: inout [Double]) throws { @@ -139,11 +164,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularInt32Field(value: inout Int32) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularInt32Field(value: inout Int32?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws { @@ -151,11 +176,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularInt64Field(value: inout Int64) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularInt64Field(value: inout Int64?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws { @@ -163,11 +188,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularUInt32Field(value: inout UInt32) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularUInt32Field(value: inout UInt32?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws { @@ -175,11 +200,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularUInt64Field(value: inout UInt64) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularUInt64Field(value: inout UInt64?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws { @@ -187,11 +212,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularSInt32Field(value: inout Int32) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularSInt32Field(value: inout Int32?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws { @@ -199,11 +224,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularSInt64Field(value: inout Int64) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularSInt64Field(value: inout Int64?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws { @@ -211,11 +236,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularFixed32Field(value: inout UInt32) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularFixed32Field(value: inout UInt32?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws { @@ -223,11 +248,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularFixed64Field(value: inout UInt64) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularFixed64Field(value: inout UInt64?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws { @@ -235,11 +260,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularSFixed32Field(value: inout Int32) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularSFixed32Field(value: inout Int32?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws { @@ -247,11 +272,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularSFixed64Field(value: inout Int64) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularSFixed64Field(value: inout Int64?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws { @@ -259,11 +284,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularBoolField(value: inout Bool) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularBoolField(value: inout Bool?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedBoolField(value: inout [Bool]) throws { @@ -271,11 +296,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularStringField(value: inout String) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularStringField(value: inout String?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedStringField(value: inout [String]) throws { @@ -283,11 +308,11 @@ struct SetPathDecoder: Decoder { } mutating func decodeSingularBytesField(value: inout Data) throws { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularBytesField(value: inout Data?) throws { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedBytesField(value: inout [Data]) throws { @@ -297,13 +322,13 @@ struct SetPathDecoder: Decoder { mutating func decodeSingularEnumField( value: inout E ) throws where E : Enum, E.RawValue == Int { - try setValue(&value) + try setValue(&value, defaultValue: .init()) } mutating func decodeSingularEnumField( value: inout E? ) throws where E : Enum, E.RawValue == Int { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedEnumField( @@ -316,11 +341,11 @@ struct SetPathDecoder: Decoder { value: inout M? ) throws where M : Message { if nextPath.isEmpty { - try setValue(&value) + try setValue(&value, defaultValue: nil) return } - var decoder = SetPathDecoder( - path: nextPath, + var decoder = try PathDecoder( + path: nextPath, value: self.value, mergeOption: mergeOption ) @@ -339,7 +364,7 @@ struct SetPathDecoder: Decoder { mutating func decodeSingularGroupField( value: inout G? ) throws where G : Message { - try setValue(&value) + try setValue(&value, defaultValue: nil) } mutating func decodeRepeatedGroupField( @@ -388,7 +413,7 @@ extension Message { mergeOption: Google_Protobuf_FieldMask.MergeOptions ) throws { let _path = path.components(separatedBy: ".") - var decoder = SetPathDecoder( + var decoder = try PathDecoder( path: _path, value: value, mergeOption: mergeOption diff --git a/Sources/SwiftProtobuf/PathVisitor.swift b/Sources/SwiftProtobuf/PathVisitor.swift new file mode 100644 index 000000000..c86422337 --- /dev/null +++ b/Sources/SwiftProtobuf/PathVisitor.swift @@ -0,0 +1,275 @@ +// 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 value after decoding 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 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 { + guard var path = T.name(for: fieldNumber) else { + return + } + values[path] = value + if let prevPath { + path = "\(prevPath).\(path)" + } + var visitor = PathVisitor(prevPath: path) + try value.traverse(visitor: &visitor) + values.merge(visitor.values, uniquingKeysWith: { _, new in new }) + } + + mutating func visitSingularGroupField(value: G, fieldNumber: Int) throws { + visit(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 4eaebc97e..bab55f7a1 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -397,4 +397,203 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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(with: 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 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(with: 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(with: m2, fieldMask: .init(protoPaths: ["default_int32"])) + XCTAssertEqual(m1.defaultInt32, m2.defaultInt32) + } + + // Checks merge could be done for non-optional paths. + func testMergeNonOptionalValues() throws { + let mask = Google_Protobuf_FieldMask(protoPaths: ["value"]) + + var m1 = Google_Protobuf_DoubleValue(1) + let m2 = Google_Protobuf_DoubleValue() + try m1.merge(with: m2, fieldMask: mask) + XCTAssertEqual(m1.value, m2.value) + + var m3 = Google_Protobuf_FloatValue(1) + let m4 = Google_Protobuf_FloatValue() + try m3.merge(with: m4, fieldMask: mask) + XCTAssertEqual(m3.value, m4.value) + + var m5 = Google_Protobuf_Int64Value(1) + let m6 = Google_Protobuf_Int64Value() + try m5.merge(with: m6, fieldMask: mask) + XCTAssertEqual(m5.value, m6.value) + + var m7 = Google_Protobuf_Int32Value(1) + let m8 = Google_Protobuf_Int32Value() + try m7.merge(with: m8, fieldMask: mask) + XCTAssertEqual(m7.value, m8.value) + + var m9 = Google_Protobuf_UInt64Value(1) + let m10 = Google_Protobuf_UInt64Value() + try m9.merge(with: m10, fieldMask: mask) + XCTAssertEqual(m9.value, m10.value) + + var m11 = Google_Protobuf_UInt32Value(1) + let m12 = Google_Protobuf_UInt32Value() + try m11.merge(with: m12, fieldMask: mask) + XCTAssertEqual(m11.value, m12.value) + + var m13 = Google_Protobuf_BoolValue(true) + let m14 = Google_Protobuf_BoolValue() + try m13.merge(with: m14, fieldMask: mask) + XCTAssertEqual(m13.value, m14.value) + + var m15 = Google_Protobuf_StringValue("str") + let m16 = Google_Protobuf_StringValue() + try m15.merge(with: m16, fieldMask: mask) + XCTAssertEqual(m15.value, m16.value) + + var m17 = Google_Protobuf_BytesValue("str".data(using: .utf8) ?? .init()) + let m18 = Google_Protobuf_BytesValue() + try m17.merge(with: m18, fieldMask: mask) + XCTAssertEqual(m17.value, m18.value) + } } From db6be11e285ae61b5e3bddb30ee392f6919462c0 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Sat, 4 May 2024 12:50:55 +0330 Subject: [PATCH 29/33] Improve tests --- Sources/SwiftProtobuf/PathDecoder.swift | 3 + Sources/SwiftProtobuf/PathVisitor.swift | 6 +- Tests/SwiftProtobufTests/Test_FieldMask.swift | 117 +++++++++++------- 3 files changed, 78 insertions(+), 48 deletions(-) diff --git a/Sources/SwiftProtobuf/PathDecoder.swift b/Sources/SwiftProtobuf/PathDecoder.swift index 0c664e81a..333b7ff0a 100644 --- a/Sources/SwiftProtobuf/PathDecoder.swift +++ b/Sources/SwiftProtobuf/PathDecoder.swift @@ -116,6 +116,9 @@ struct PathDecoder: Decoder { 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 { diff --git a/Sources/SwiftProtobuf/PathVisitor.swift b/Sources/SwiftProtobuf/PathVisitor.swift index c86422337..0cbec63f2 100644 --- a/Sources/SwiftProtobuf/PathVisitor.swift +++ b/Sources/SwiftProtobuf/PathVisitor.swift @@ -20,7 +20,7 @@ struct PathVisitor: Visitor { // The path contains parent components private let prevPath: String? - // Captured value after decoding will be stored in this property + // Captured values after visiting will be stored in this property private(set) var values: [String: Any] = [:] internal init(prevPath: String? = nil) { @@ -114,7 +114,9 @@ struct PathVisitor: Visitor { } var visitor = PathVisitor(prevPath: path) try value.traverse(visitor: &visitor) - values.merge(visitor.values, uniquingKeysWith: { _, new in new }) + values.merge(visitor.values, uniquingKeysWith: { _, new in + new + }) } mutating func visitSingularGroupField(value: G, fieldNumber: Int) throws { diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index bab55f7a1..0381e7801 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -232,12 +232,15 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { // 2. Valid nested path. // 3. Invalid primitive path. // 4, 5. Invalid nested path. + // 6, 7. Invalid path after map and repeated. func testIsPathValid() { XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_int32")) XCTAssertTrue(SwiftProtoTesting_TestAllTypes.isPathValid("optional_nested_message.bb")) 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")) } // Checks `isValid` func of FieldMask. @@ -549,51 +552,73 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { // Checks merge could be done for non-optional paths. func testMergeNonOptionalValues() throws { - let mask = Google_Protobuf_FieldMask(protoPaths: ["value"]) - - var m1 = Google_Protobuf_DoubleValue(1) - let m2 = Google_Protobuf_DoubleValue() - try m1.merge(with: m2, fieldMask: mask) - XCTAssertEqual(m1.value, m2.value) - - var m3 = Google_Protobuf_FloatValue(1) - let m4 = Google_Protobuf_FloatValue() - try m3.merge(with: m4, fieldMask: mask) - XCTAssertEqual(m3.value, m4.value) - - var m5 = Google_Protobuf_Int64Value(1) - let m6 = Google_Protobuf_Int64Value() - try m5.merge(with: m6, fieldMask: mask) - XCTAssertEqual(m5.value, m6.value) - - var m7 = Google_Protobuf_Int32Value(1) - let m8 = Google_Protobuf_Int32Value() - try m7.merge(with: m8, fieldMask: mask) - XCTAssertEqual(m7.value, m8.value) - - var m9 = Google_Protobuf_UInt64Value(1) - let m10 = Google_Protobuf_UInt64Value() - try m9.merge(with: m10, fieldMask: mask) - XCTAssertEqual(m9.value, m10.value) - - var m11 = Google_Protobuf_UInt32Value(1) - let m12 = Google_Protobuf_UInt32Value() - try m11.merge(with: m12, fieldMask: mask) - XCTAssertEqual(m11.value, m12.value) - - var m13 = Google_Protobuf_BoolValue(true) - let m14 = Google_Protobuf_BoolValue() - try m13.merge(with: m14, fieldMask: mask) - XCTAssertEqual(m13.value, m14.value) - - var m15 = Google_Protobuf_StringValue("str") - let m16 = Google_Protobuf_StringValue() - try m15.merge(with: m16, fieldMask: mask) - XCTAssertEqual(m15.value, m16.value) - - var m17 = Google_Protobuf_BytesValue("str".data(using: .utf8) ?? .init()) - let m18 = Google_Protobuf_BytesValue() - try m17.merge(with: m18, fieldMask: mask) - XCTAssertEqual(m17.value, m18.value) + 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(with: 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 + } + } + } + try m1.merge(with: m2, fieldMask: .init(protoPaths: ["singular_message.singular_message.singular_int32"])) + XCTAssertEqual(m1.singularMessage.singularMessage.singularInt32, Int32(1)) } } From 1ef69c9355ecb25d056be0a1a6c2f1a543c351af Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Wed, 15 May 2024 14:41:44 +0330 Subject: [PATCH 30/33] Fix comments --- ...Google_Protobuf_FieldMask+Extensions.swift | 2 + Sources/SwiftProtobuf/Message+FieldMask.swift | 3 +- Sources/SwiftProtobuf/NameMap.swift | 4 +- Sources/SwiftProtobuf/PathDecoder.swift | 36 ++-- Sources/SwiftProtobuf/PathVisitor.swift | 33 ++-- Tests/SwiftProtobufTests/Test_FieldMask.swift | 176 +++++++++++++++++- 6 files changed, 217 insertions(+), 37 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index e098e5f84..508a558be 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -199,6 +199,8 @@ extension Google_Protobuf_FieldMask { /// - 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 diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index 5f68202f3..b25ad9164 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -75,6 +75,7 @@ extension Message { 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, @@ -89,12 +90,12 @@ extension Message where Self: Equatable, Self: _ProtoNameProviding { // TODO: Re-implement using clear fields instead of copying message - @discardableResult /// 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( fieldMask: Google_Protobuf_FieldMask ) -> Bool { diff --git a/Sources/SwiftProtobuf/NameMap.swift b/Sources/SwiftProtobuf/NameMap.swift index 2d9f2cee6..8c027ac40 100644 --- a/Sources/SwiftProtobuf/NameMap.swift +++ b/Sources/SwiftProtobuf/NameMap.swift @@ -281,8 +281,6 @@ public struct _NameMap: ExpressibleByDictionaryLiteral { /// Returns all proto names internal var names: [Name] { - protoToNumberMap.map { - $0.key - } + protoToNumberMap.map(\.key) } } diff --git a/Sources/SwiftProtobuf/PathDecoder.swift b/Sources/SwiftProtobuf/PathDecoder.swift index 333b7ff0a..a4ead80b7 100644 --- a/Sources/SwiftProtobuf/PathDecoder.swift +++ b/Sources/SwiftProtobuf/PathDecoder.swift @@ -135,6 +135,24 @@ struct PathDecoder: Decoder { } } + 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? { @@ -343,19 +361,7 @@ struct PathDecoder: Decoder { mutating func decodeSingularMessageField( value: inout M? ) throws where M : Message { - 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) + try setMessageValue(&value) } mutating func decodeRepeatedMessageField( @@ -367,7 +373,7 @@ struct PathDecoder: Decoder { mutating func decodeSingularGroupField( value: inout G? ) throws where G : Message { - try setValue(&value, defaultValue: nil) + try setMessageValue(&value) } mutating func decodeRepeatedGroupField( @@ -403,7 +409,7 @@ struct PathDecoder: Decoder { fieldNumber: Int ) throws { preconditionFailure( - "Path decoder should never decode an extension field" + "Internal Error: Path decoder should never decode an extension field" ) } diff --git a/Sources/SwiftProtobuf/PathVisitor.swift b/Sources/SwiftProtobuf/PathVisitor.swift index 0cbec63f2..868fed574 100644 --- a/Sources/SwiftProtobuf/PathVisitor.swift +++ b/Sources/SwiftProtobuf/PathVisitor.swift @@ -38,6 +38,24 @@ struct PathVisitor: Visitor { } } + 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 { @@ -105,22 +123,11 @@ struct PathVisitor: Visitor { } mutating func visitSingularMessageField(value: M, fieldNumber: Int) throws { - guard var path = T.name(for: fieldNumber) else { - return - } - values[path] = value - if let prevPath { - path = "\(prevPath).\(path)" - } - var visitor = PathVisitor(prevPath: path) - try value.traverse(visitor: &visitor) - values.merge(visitor.values, uniquingKeysWith: { _, new in - new - }) + visitMessageField(value, fieldNumber: fieldNumber) } mutating func visitSingularGroupField(value: G, fieldNumber: Int) throws { - visit(value, fieldNumber: fieldNumber) + visitMessageField(value, fieldNumber: fieldNumber) } mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws { diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 0381e7801..a24be3b56 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -229,18 +229,21 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { // Checks `isPathValid` func // 1. Valid primitive path. - // 2. Valid nested path. - // 3. Invalid primitive path. - // 4, 5. Invalid nested path. - // 6, 7. Invalid path after map and repeated. + // 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. @@ -530,6 +533,148 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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(with: 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 @@ -618,7 +763,28 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } } } - try m1.merge(with: m2, fieldMask: .init(protoPaths: ["singular_message.singular_message.singular_int32"])) + let m3 = SwiftProtoTesting_Fuzz_Message.with { m in + m.singularMessage = .with { _m in + _m.singularMessage = .with { __m in + __m.singularInt32 = 2 + } + } + } + try m1.merge(with: m2, fieldMask: .init(protoPaths: ["singular_message.singular_message"])) XCTAssertEqual(m1.singularMessage.singularMessage.singularInt32, Int32(1)) + try m1.merge(with: 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(with: m2, fieldMask: .init(protoPaths: ["SingularGroup.group_field"])) + XCTAssertEqual(m1.singularGroup.groupField, m2.singularGroup.groupField) } } From 35163e1199973e70eeae398155f717cb3469e0d4 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 16 May 2024 14:38:29 +0330 Subject: [PATCH 31/33] Fix failing test --- Sources/SwiftProtobuf/PathDecoder.swift | 4 +++- Tests/SwiftProtobufTests/Test_FieldMask.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftProtobuf/PathDecoder.swift b/Sources/SwiftProtobuf/PathDecoder.swift index a4ead80b7..cb8465c7c 100644 --- a/Sources/SwiftProtobuf/PathDecoder.swift +++ b/Sources/SwiftProtobuf/PathDecoder.swift @@ -35,7 +35,9 @@ extension Message { guard let type = Self.self as? any _ProtoNameProviding.Type else { return nil } - return type._protobuf_nameMap.number(forJSONName: field) + return Array(field.utf8).withUnsafeBytes { bytes in + type._protobuf_nameMap.number(forProtoName: bytes) + } } static func name(for field: Int) -> String? { diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index a24be3b56..879ce8ae0 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -213,7 +213,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { var message = SwiftProtoTesting_Fuzz_Message() message.singularInt32 = 1 message.SwiftProtoTesting_Fuzz_singularInt32Ext = 1 - let mask = Google_Protobuf_FieldMask(protoPaths: ["singularString"]) + let mask = Google_Protobuf_FieldMask(protoPaths: ["singular_string"]) // Checks trim should retain extensions while removes other fields. let r1 = message.trim(fieldMask: mask) From 702679b4521bf6671a603a677a6b3047d1f7eac1 Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Thu, 16 May 2024 21:52:40 +0330 Subject: [PATCH 32/33] Fix issue of json path names --- Sources/SwiftProtobuf/NameMap.swift | 2 +- Sources/SwiftProtobuf/PathDecoder.swift | 8 +++++- Tests/SwiftProtobufTests/Test_FieldMask.swift | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftProtobuf/NameMap.swift b/Sources/SwiftProtobuf/NameMap.swift index 8c027ac40..ab22a3797 100644 --- a/Sources/SwiftProtobuf/NameMap.swift +++ b/Sources/SwiftProtobuf/NameMap.swift @@ -281,6 +281,6 @@ public struct _NameMap: ExpressibleByDictionaryLiteral { /// Returns all proto names internal var names: [Name] { - protoToNumberMap.map(\.key) + numberToNameMap.map(\.value.proto) } } diff --git a/Sources/SwiftProtobuf/PathDecoder.swift b/Sources/SwiftProtobuf/PathDecoder.swift index cb8465c7c..7b7e67a9c 100644 --- a/Sources/SwiftProtobuf/PathDecoder.swift +++ b/Sources/SwiftProtobuf/PathDecoder.swift @@ -35,9 +35,15 @@ extension Message { guard let type = Self.self as? any _ProtoNameProviding.Type else { return nil } - return Array(field.utf8).withUnsafeBytes { bytes in + 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? { diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 879ce8ae0..6e7c2d0cb 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -334,6 +334,15 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { 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"]) @@ -787,4 +796,21 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { try m1.merge(with: 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(with: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) + XCTAssertNotEqual(m1.singularGroup, m2.singularGroup) + // should merge with proto path + try m1.merge(with: m2, fieldMask: .with({ $0.paths = ["SingularGroup"] })) + XCTAssertEqual(m1.singularGroup, m2.singularGroup) + // should do nothing with json path (do not clear field) + try m1.merge(with: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) + XCTAssertEqual(m1.singularGroup, m2.singularGroup) + } } From e3d01ea9694b175ae72565f4bbca3be636cad7ab Mon Sep 17 00:00:00 2001 From: Pouya Yarandi Date: Fri, 16 Aug 2024 01:11:34 +0330 Subject: [PATCH 33/33] Fix comments --- ...Google_Protobuf_FieldMask+Extensions.swift | 9 +--- Sources/SwiftProtobuf/Message+FieldMask.swift | 21 ++++----- Tests/SwiftProtobufTests/Test_FieldMask.swift | 46 +++++++++---------- 3 files changed, 33 insertions(+), 43 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift index 508a558be..87cbdd65f 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift @@ -282,7 +282,7 @@ extension Google_Protobuf_FieldMask { public func intersect( _ mask: Google_Protobuf_FieldMask ) -> Google_Protobuf_FieldMask { - let set = mask.pathsSet + let set = Set(mask.paths) var paths: [String] = [] var buffer = Set() for path in self.paths where set.contains(path) && !buffer.contains(path) { @@ -302,7 +302,7 @@ extension Google_Protobuf_FieldMask { public func subtract( _ mask: Google_Protobuf_FieldMask ) -> Google_Protobuf_FieldMask { - let set = mask.pathsSet + let set = Set(mask.paths) var paths: [String] = [] var buffer = Set() for path in self.paths where !set.contains(path) && !buffer.contains(path) { @@ -329,11 +329,6 @@ extension Google_Protobuf_FieldMask { } return false } - - // Set containing paths of FieldMask - private var pathsSet: Set { - .init(paths) - } } extension Google_Protobuf_FieldMask { diff --git a/Sources/SwiftProtobuf/Message+FieldMask.swift b/Sources/SwiftProtobuf/Message+FieldMask.swift index b25ad9164..2622d7214 100644 --- a/Sources/SwiftProtobuf/Message+FieldMask.swift +++ b/Sources/SwiftProtobuf/Message+FieldMask.swift @@ -68,7 +68,7 @@ extension Message { /// - source: Message that should be merged to the original one. /// - fieldMask: FieldMask specifies which fields should be merged. public mutating func merge( - with source: Self, + from source: Self, fieldMask: Google_Protobuf_FieldMask, mergeOption: Google_Protobuf_FieldMask.MergeOptions = .init() ) throws { @@ -97,7 +97,7 @@ extension Message where Self: Equatable, Self: _ProtoNameProviding { /// - Returns: Boolean determines if the message is modified @discardableResult public mutating func trim( - fieldMask: Google_Protobuf_FieldMask + keeping fieldMask: Google_Protobuf_FieldMask ) -> Bool { if !fieldMask.isValid(for: Self.self) { return false @@ -107,7 +107,7 @@ extension Message where Self: Equatable, Self: _ProtoNameProviding { } var tmp = Self(removingAllFieldsOf: self) do { - try tmp.merge(with: self, fieldMask: fieldMask) + try tmp.merge(from: self, fieldMask: fieldMask) let changed = tmp != self self = tmp return changed @@ -119,19 +119,14 @@ extension Message where Self: Equatable, Self: _ProtoNameProviding { private extension Message { init(removingAllFieldsOf message: Self) { - if let type = Self.self as? any ExtensibleMessage.Type, + let newMessage: Self = .init() + if var newExtensible = newMessage as? any ExtensibleMessage, let extensible = message as? any ExtensibleMessage { - self = type.init(extensionsOf: extensible) as? Self ?? .init() + newExtensible._protobuf_extensionFieldValues = extensible._protobuf_extensionFieldValues + self = newExtensible as? Self ?? newMessage } else { - self = .init() + self = newMessage } self.unknownFields = message.unknownFields } } - -private extension Message where Self: ExtensibleMessage { - init(extensionsOf message: any ExtensibleMessage) { - self.init() - _protobuf_extensionFieldValues = message._protobuf_extensionFieldValues - } -} diff --git a/Tests/SwiftProtobufTests/Test_FieldMask.swift b/Tests/SwiftProtobufTests/Test_FieldMask.swift index 6e7c2d0cb..be0a8b9b4 100644 --- a/Tests/SwiftProtobufTests/Test_FieldMask.swift +++ b/Tests/SwiftProtobufTests/Test_FieldMask.swift @@ -124,12 +124,12 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } // Checks nested message merge - try message.merge(with: secondMessage, fieldMask: .init(protoPaths: "optional_nested_message.bb")) + 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(with: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) + try message.merge(from: secondMessage, fieldMask: .init(protoPaths: "optional_int32")) XCTAssertEqual(message.optionalInt32, 2) XCTAssertEqual(message.optionalNestedMessage.bb, 3) } @@ -147,13 +147,13 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["repeated_int32"]) // Checks without replacing repeated fields - try message.merge(with: secondMessage, fieldMask: fieldMask) + 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(with: secondMessage, fieldMask: fieldMask, mergeOption: options) + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: options) XCTAssertEqual(message.repeatedInt32, [3, 4]) } @@ -170,13 +170,13 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { let fieldMask = Google_Protobuf_FieldMask(protoPaths: ["map_int32_string"]) // Checks without replacing repeated fields - try message.merge(with: secondMessage, fieldMask: fieldMask) + 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(with: secondMessage, fieldMask: fieldMask, mergeOption: options) + try message.merge(from: secondMessage, fieldMask: fieldMask, mergeOption: options) XCTAssertEqual(message.mapInt32String, [2: "b"]) } @@ -190,21 +190,21 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } // Checks trim to be successful. - let r1 = message.trim(fieldMask: .init(protoPaths: "optional_nested_message.bb")) + 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(fieldMask: .init()) + let r2 = message.trim(keeping: .init()) XCTAssertFalse(r2) // Checks trim should return false if nothing has been changed. - let r3 = message.trim(fieldMask: .init(protoPaths: "optional_nested_message.bb")) + 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(fieldMask: .init(protoPaths: "invalid_path")) + let r4 = message.trim(keeping: .init(protoPaths: "invalid_path")) XCTAssertFalse(r4) } @@ -216,13 +216,13 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { let mask = Google_Protobuf_FieldMask(protoPaths: ["singular_string"]) // Checks trim should retain extensions while removes other fields. - let r1 = message.trim(fieldMask: mask) + 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(fieldMask: mask) + let r2 = message.trim(keeping: mask) XCTAssertFalse(r2) XCTAssertEqual(message.SwiftProtoTesting_Fuzz_singularInt32Ext, .init(1)) } @@ -478,7 +478,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { m.mapInt32AnEnum = [1: .one] m.mapInt32Message = [1: .init()] } - try m1.merge(with: m2, fieldMask: .init(allFieldsOf: SwiftProtoTesting_Fuzz_Message.self)) + 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) @@ -620,7 +620,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { "singular_sfixed32", "singular_sfixed64" ]) - try m1.merge(with: m2, fieldMask: mask) + try m1.merge(from: m2, fieldMask: mask) XCTAssertEqual(m1.singularInt32, m2.singularInt32) XCTAssertEqual(m1.singularInt64, m2.singularInt64) XCTAssertEqual(m1.singularUint32, m2.singularUint32) @@ -690,7 +690,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { m.singularInt32 = 1 } let m2 = SwiftProtoTesting_Fuzz_Message() - try m1.merge(with: m2, fieldMask: .init(protoPaths: ["singular_int32"])) + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["singular_int32"])) XCTAssertEqual(m1.singularInt32, m2.singularInt32) } @@ -700,7 +700,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { m.defaultInt32 = 1 } let m2 = SwiftProtoTesting_TestAllTypes() - try m1.merge(with: m2, fieldMask: .init(protoPaths: ["default_int32"])) + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["default_int32"])) XCTAssertEqual(m1.defaultInt32, m2.defaultInt32) } @@ -725,7 +725,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { m.optionalNestedEnum = .bar } let m2 = SwiftProtoTesting_Proto3_TestAllTypes() - try m1.merge(with: m2, fieldMask: .init(protoPaths: [ + try m1.merge(from: m2, fieldMask: .init(protoPaths: [ "optional_int32", "optional_int64", "optional_double", @@ -779,9 +779,9 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { } } } - try m1.merge(with: m2, fieldMask: .init(protoPaths: ["singular_message.singular_message"])) + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["singular_message.singular_message"])) XCTAssertEqual(m1.singularMessage.singularMessage.singularInt32, Int32(1)) - try m1.merge(with: m3, fieldMask: .init(protoPaths: ["singular_message.singular_message.singular_int32"])) + try m1.merge(from: m3, fieldMask: .init(protoPaths: ["singular_message.singular_message.singular_int32"])) XCTAssertEqual(m1.singularMessage.singularMessage.singularInt32, Int32(2)) } @@ -793,7 +793,7 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { _m.groupField = 1 } } - try m1.merge(with: m2, fieldMask: .init(protoPaths: ["SingularGroup.group_field"])) + try m1.merge(from: m2, fieldMask: .init(protoPaths: ["SingularGroup.group_field"])) XCTAssertEqual(m1.singularGroup.groupField, m2.singularGroup.groupField) } @@ -804,13 +804,13 @@ final class Test_FieldMask: XCTestCase, PBTestHelpers { m.singularGroup = .with { $0.groupField = 1 } } // should do nothing with json path (should not merge) - try m1.merge(with: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) + try m1.merge(from: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) XCTAssertNotEqual(m1.singularGroup, m2.singularGroup) // should merge with proto path - try m1.merge(with: m2, fieldMask: .with({ $0.paths = ["SingularGroup"] })) + 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(with: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) + try m1.merge(from: m2, fieldMask: .with({ $0.paths = ["singulargroup"] })) XCTAssertEqual(m1.singularGroup, m2.singularGroup) } }