Coverage Report

Created: 2025-08-11 06:15

/src/swift-protobuf/Sources/SwiftProtobuf/Message+FieldMask.swift
Line
Count
Source (jump to first uncovered line)
1
// Sources/SwiftProtobuf/Message+FieldMask.swift - Message field mask extensions
2
//
3
// Copyright (c) 2014 - 2023 Apple Inc. and the project authors
4
// Licensed under Apache License v2.0 with Runtime Library Exception
5
//
6
// See LICENSE.txt for license information:
7
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
8
//
9
// -----------------------------------------------------------------------------
10
///
11
/// Extend the Message types with FieldMask utilities.
12
///
13
// -----------------------------------------------------------------------------
14
15
import Foundation
16
17
extension Message {
18
19
    /// Checks whether the given path is valid for Message type.
20
    ///
21
    /// - Parameter path: Path to be checked
22
    /// - Returns: Boolean determines path is valid.
23
    public static func isPathValid(
24
        _ path: String
25
0
    ) -> Bool {
26
0
        var message = Self()
27
0
        return message.hasPath(path: path)
28
0
    }
29
30
0
    internal mutating func hasPath(path: String) -> Bool {
31
0
        do {
32
0
            try set(path: path, value: nil, mergeOption: .init())
33
0
            return true
34
0
        } catch let error as PathDecodingError {
35
0
            return error != .pathNotFound
36
0
        } catch {
37
0
            return false
38
0
        }
39
0
    }
40
41
    internal mutating func isPathValid(
42
        _ path: String
43
0
    ) -> Bool {
44
0
        hasPath(path: path)
45
0
    }
46
}
47
48
extension Google_Protobuf_FieldMask {
49
50
    /// Defines available options for merging two messages.
51
    public struct MergeOptions {
52
53
0
        public init() {}
54
55
        /// The default merging behavior will append entries from the source
56
        /// repeated field to the destination repeated field. If you only want
57
        /// to keep the entries from the source repeated field, set this flag
58
        /// to true.
59
0
        public var replaceRepeatedFields = false
60
    }
61
}
62
63
extension Message {
64
65
    /// Merges fields specified in a FieldMask into another message.
66
    ///
67
    /// - Parameters:
68
    ///   - source: Message that should be merged to the original one.
69
    ///   - fieldMask: FieldMask specifies which fields should be merged.
70
    public mutating func merge(
71
        from source: Self,
72
        fieldMask: Google_Protobuf_FieldMask,
73
        mergeOption: Google_Protobuf_FieldMask.MergeOptions = .init()
74
0
    ) throws {
75
0
        var visitor = PathVisitor<Self>()
76
0
        try source.traverse(visitor: &visitor)
77
0
        let values = visitor.values
78
0
        // TODO: setting all values with only one decoding
79
0
        for path in fieldMask.paths {
80
0
            try? set(
81
0
                path: path,
82
0
                value: values[path],
83
0
                mergeOption: mergeOption
84
0
            )
85
0
        }
86
0
    }
87
}
88
89
extension Message where Self: Equatable, Self: _ProtoNameProviding {
90
91
    // TODO: Re-implement using clear fields instead of copying message
92
93
    /// Removes from 'message' any field that is not represented in the given
94
    /// FieldMask. If the FieldMask is empty, does nothing.
95
    ///
96
    /// - Parameter fieldMask: FieldMask specifies which fields should be kept.
97
    /// - Returns: Boolean determines if the message is modified
98
    @discardableResult
99
    public mutating func trim(
100
        keeping fieldMask: Google_Protobuf_FieldMask
101
0
    ) -> Bool {
102
0
        if !fieldMask.isValid(for: Self.self) {
103
0
            return false
104
0
        }
105
0
        if fieldMask.paths.isEmpty {
106
0
            return false
107
0
        }
108
0
        var tmp = Self(removingAllFieldsOf: self)
109
0
        do {
110
0
            try tmp.merge(from: self, fieldMask: fieldMask)
111
0
            let changed = tmp != self
112
0
            self = tmp
113
0
            return changed
114
0
        } catch {
115
0
            return false
116
0
        }
117
0
    }
118
}
119
120
extension Message {
121
0
    fileprivate init(removingAllFieldsOf message: Self) {
122
0
        let newMessage: Self = .init()
123
0
        if var newExtensible = newMessage as? any ExtensibleMessage,
124
0
            let extensible = message as? any ExtensibleMessage
125
0
        {
126
0
            newExtensible._protobuf_extensionFieldValues = extensible._protobuf_extensionFieldValues
127
0
            self = newExtensible as? Self ?? newMessage
128
0
        } else {
129
0
            self = newMessage
130
0
        }
131
0
        self.unknownFields = message.unknownFields
132
0
    }
133
}