SwiftProtobufError.swift 11 KB

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