/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 | | } |