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