Coverage Report

Created: 2026-03-26 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-protobuf/Sources/SwiftProtobuf/SwiftProtobufError.swift
Line
Count
Source
1
// Sources/SwiftProtobuf/SwiftProtobufError.swift
2
//
3
// Copyright (c) 2024 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
/// A SwiftProtobuf specific error.
12
///
13
/// All errors have a high-level ``SwiftProtobufError/Code-swift.struct`` which identifies the domain
14
/// of the error. For example, an issue when encoding a proto into binary data will result in a
15
/// ``SwiftProtobufError/Code-swift.struct/binaryEncodingError`` error code.
16
/// Errors also include a message describing what went wrong and how to remedy it (if applicable). The
17
/// ``SwiftProtobufError/message`` is not static and may include dynamic information such as the
18
/// type URL for a type that could not be decoded, for example.
19
public struct SwiftProtobufError: Error, @unchecked Sendable {
20
    // Note: @unchecked because we use a backing class for storage.
21
22
    private var storage: Storage
23
0
    private mutating func ensureStorageIsUnique() {
24
0
        if !isKnownUniquelyReferenced(&self.storage) {
25
0
            self.storage = self.storage.copy()
26
0
        }
27
0
    }
28
29
    private final class Storage {
30
        var code: Code
31
        var message: String
32
        var location: SourceLocation
33
34
        init(
35
            code: Code,
36
            message: String,
37
            location: SourceLocation
38
131k
        ) {
39
131k
            self.code = code
40
131k
            self.message = message
41
131k
            self.location = location
42
131k
        }
43
44
0
        func copy() -> Self {
45
0
            Self(
46
0
                code: self.code,
47
0
                message: self.message,
48
0
                location: self.location
49
0
            )
50
0
        }
51
    }
52
53
    /// A high-level error code to provide broad a classification.
54
    public var code: Code {
55
0
        get { self.storage.code }
56
0
        set {
57
0
            self.ensureStorageIsUnique()
58
0
            self.storage.code = newValue
59
0
        }
60
    }
61
62
    /// A message describing what went wrong and how it may be remedied.
63
    package var message: String {
64
0
        get { self.storage.message }
65
0
        set {
66
0
            self.ensureStorageIsUnique()
67
0
            self.storage.message = newValue
68
0
        }
69
    }
70
71
    private var location: SourceLocation {
72
0
        get { self.storage.location }
73
0
        set {
74
0
            self.ensureStorageIsUnique()
75
0
            self.storage.location = newValue
76
0
        }
77
    }
78
79
    public init(
80
        code: Code,
81
        message: String,
82
        location: SourceLocation
83
131k
    ) {
84
131k
        self.storage = Storage(code: code, message: message, location: location)
85
131k
    }
86
}
87
88
extension SwiftProtobufError {
89
    /// A high level indication of the kind of error being thrown.
90
    public struct Code: Hashable, Sendable, CustomStringConvertible {
91
        private enum Wrapped: Hashable, Sendable, CustomStringConvertible {
92
            case binaryDecodingError
93
            case binaryStreamDecodingError
94
            case jsonDecodingError
95
            case jsonEncodingError
96
97
0
            var description: String {
98
0
                switch self {
99
0
                case .binaryDecodingError:
100
0
                    return "Binary decoding error"
101
0
                case .binaryStreamDecodingError:
102
0
                    return "Stream decoding error"
103
0
                case .jsonDecodingError:
104
0
                    return "JSON decoding error"
105
0
                case .jsonEncodingError:
106
0
                    return "JSON encoding error"
107
0
                }
108
0
            }
109
        }
110
111
        /// This Code's description.
112
0
        public var description: String {
113
0
            String(describing: self.code)
114
0
        }
115
116
        private var code: Wrapped
117
131k
        private init(_ code: Wrapped) {
118
131k
            self.code = code
119
131k
        }
120
121
        /// Errors arising from binary decoding of data into protobufs.
122
16
        public static var binaryDecodingError: Self {
123
16
            Self(.binaryDecodingError)
124
16
        }
125
126
        /// Errors arising from decoding streams of binary messages. These errors have to do with the framing
127
        /// of the messages in the stream, or the stream as a whole.
128
63.1k
        public static var binaryStreamDecodingError: Self {
129
63.1k
            Self(.binaryStreamDecodingError)
130
63.1k
        }
131
132
        /// Errors arising from JSON decoding of data into protobufs.
133
179
        public static var jsonDecodingError: Self {
134
179
            Self(.jsonDecodingError)
135
179
        }
136
137
        /// Errors arising from JSON encoding of messages.
138
0
        public static var jsonEncodingError: Self {
139
0
            Self(.jsonEncodingError)
140
0
        }
141
    }
142
143
    /// A location within source code.
144
    public struct SourceLocation: Sendable, Hashable {
145
        /// The function in which the error was thrown.
146
        public var function: String
147
148
        /// The file in which the error was thrown.
149
        public var file: String
150
151
        /// The line on which the error was thrown.
152
        public var line: Int
153
154
131k
        public init(function: String, file: String, line: Int) {
155
131k
            self.function = function
156
131k
            self.file = file
157
131k
            self.line = line
158
131k
        }
159
160
        @usableFromInline
161
        internal static func here(
162
            function: String = #function,
163
            file: String = #fileID,
164
            line: Int = #line
165
0
        ) -> Self {
166
0
            SourceLocation(function: function, file: file, line: line)
167
0
        }
168
    }
169
}
170
171
extension SwiftProtobufError: CustomStringConvertible {
172
0
    public var description: String {
173
0
        "\(self.code) (at \(self.location)): \(self.message)"
174
0
    }
175
}
176
177
extension SwiftProtobufError: CustomDebugStringConvertible {
178
0
    public var debugDescription: String {
179
0
        "\(String(reflecting: self.code)) (at \(String(reflecting: self.location))): \(String(reflecting: self.message))"
180
0
    }
181
}
182
183
// - MARK: Common errors
184
185
extension SwiftProtobufError {
186
    /// Errors arising from binary decoding of data into protobufs.
187
    public enum BinaryDecoding {
188
        /// Message is too large. Bytes and Strings have a max size of 2GB.
189
        public static func tooLarge(
190
            function: String = #function,
191
            file: String = #fileID,
192
            line: Int = #line
193
16
        ) -> SwiftProtobufError {
194
16
            SwiftProtobufError(
195
16
                code: .binaryDecodingError,
196
16
                message: "Message too large: Bytes and Strings have a max size of 2GB.",
197
16
                location: SourceLocation(function: function, file: file, line: line)
198
16
            )
199
16
        }
200
    }
201
202
    /// Errors arising from decoding streams of binary messages. These errors have to do with the framing
203
    /// of the messages in the stream, or the stream as a whole.
204
    public enum BinaryStreamDecoding {
205
        /// Message is too large. Bytes and Strings have a max size of 2GB.
206
        public static func tooLarge(
207
            function: String = #function,
208
            file: String = #fileID,
209
            line: Int = #line
210
0
        ) -> SwiftProtobufError {
211
0
            SwiftProtobufError(
212
0
                code: .binaryStreamDecodingError,
213
0
                message: "Message too large: Bytes and Strings have a max size of 2GB.",
214
0
                location: SourceLocation(function: function, file: file, line: line)
215
0
            )
216
0
        }
217
218
        /// While attempting to read the length of a message on the stream, the
219
        /// bytes were malformed for the protobuf format.
220
        public static func malformedLength(
221
            function: String = #function,
222
            file: String = #fileID,
223
            line: Int = #line
224
12
        ) -> SwiftProtobufError {
225
12
            SwiftProtobufError(
226
12
                code: .binaryStreamDecodingError,
227
12
                message: """
228
12
                      While attempting to read the length of a binary-delimited message \
229
12
                      on the stream, the bytes were malformed for the protobuf format.
230
12
                    """,
231
12
                location: .init(function: function, file: file, line: line)
232
12
            )
233
12
        }
234
235
        /// This isn't really an error. `InputStream` documents that
236
        /// `hasBytesAvailable` _may_ return `True` if a read is needed to
237
        /// determine if there really are bytes available. So this "error" is thrown
238
        /// when a `parse` or `merge` fails because there were no bytes available.
239
        /// If this is raised, the callers should decide via what ever other means
240
        /// are correct if the stream has completely ended or if more bytes might
241
        /// eventually show up.
242
        public static func noBytesAvailable(
243
            function: String = #function,
244
            file: String = #fileID,
245
            line: Int = #line
246
63.1k
        ) -> SwiftProtobufError {
247
63.1k
            SwiftProtobufError(
248
63.1k
                code: .binaryStreamDecodingError,
249
63.1k
                message: """
250
63.1k
                      This is not really an error: please read the documentation for
251
63.1k
                      `SwiftProtobufError/BinaryStreamDecoding/noBytesAvailable` for more information.
252
63.1k
                    """,
253
63.1k
                location: .init(function: function, file: file, line: line)
254
63.1k
            )
255
63.1k
        }
256
    }
257
258
    /// Errors arising from JSON decoding of data into protobufs.
259
    public enum JSONDecoding {
260
        /// While decoding a `google.protobuf.Any` encountered a malformed `@type` key for
261
        /// the `type_url` field.
262
        public static func invalidAnyTypeURL(
263
            type_url: String,
264
            function: String = #function,
265
            file: String = #fileID,
266
            line: Int = #line
267
46
        ) -> SwiftProtobufError {
268
46
            SwiftProtobufError(
269
46
                code: .jsonDecodingError,
270
46
                message: "google.protobuf.Any '@type' was invalid: \(type_url).",
271
46
                location: SourceLocation(function: function, file: file, line: line)
272
46
            )
273
46
        }
274
275
        /// While decoding a `google.protobuf.Any` no `@type` field but the message had other fields.
276
        public static func emptyAnyTypeURL(
277
            function: String = #function,
278
            file: String = #fileID,
279
            line: Int = #line
280
31
        ) -> SwiftProtobufError {
281
31
            SwiftProtobufError(
282
31
                code: .jsonDecodingError,
283
31
                message: "google.protobuf.Any '@type' was must be present if if the object is not empty.",
284
31
                location: SourceLocation(function: function, file: file, line: line)
285
31
            )
286
31
        }
287
    }
288
289
    /// Errors arising from JSON encoding of messages.
290
    public enum JSONEncoding {
291
        /// While encoding a `google.protobuf.Any` encountered a malformed `type_url` field.
292
        public static func invalidAnyTypeURL(
293
            type_url: String,
294
            function: String = #function,
295
            file: String = #fileID,
296
            line: Int = #line
297
0
        ) -> SwiftProtobufError {
298
0
            SwiftProtobufError(
299
0
                code: .jsonEncodingError,
300
0
                message: "google.protobuf.Any 'type_url' was invalid: \(type_url).",
301
0
                location: SourceLocation(function: function, file: file, line: line)
302
0
            )
303
0
        }
304
305
        /// While encoding a `google.protobuf.Any` encountered an empty `type_url` field.
306
        public static func emptyAnyTypeURL(
307
            function: String = #function,
308
            file: String = #fileID,
309
            line: Int = #line
310
0
        ) -> SwiftProtobufError {
311
0
            SwiftProtobufError(
312
0
                code: .jsonEncodingError,
313
0
                message: "google.protobuf.Any 'type_url' was empty, only allowed for empty objects.",
314
0
                location: SourceLocation(function: function, file: file, line: line)
315
0
            )
316
0
        }
317
    }
318
}