Repository navigation
Expand file tree
/
Copy pathStorageError.swift
More file actions
433 lines (349 loc) · 17.3 KB
/
Copy pathStorageError.swift
File metadata and controls
433 lines (349 loc) · 17.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
import Foundation
public import Helpers
/// An error thrown by the Storage client.
///
/// Check ``kind`` to learn what failed. For ``Kind-swift.struct/server``, ``code`` carries the
/// code Storage put in the body, ``serverError`` holds the rest of that body, including the
/// status Storage reports, and ``response`` holds the HTTP status, headers and request id. On the
/// object, bucket, render and CDN routes the HTTP status is 400 for nearly every failure, so
/// branch on ``code`` or `serverError?.statusCode`, not on `response?.status`.
///
/// ```swift
/// do {
/// _ = try await storage.from("avatars").download(path: "missing.png")
/// } catch let error as StorageError where error.isNotFound {
/// showPlaceholder()
/// } catch let error as StorageError {
/// print(error.code?.rawValue ?? "", error.message)
/// }
/// ```
public struct StorageError: SupabaseError {
/// What failed. Compare against the static members and keep a fallback branch.
public struct Kind: RawRepresentable, Hashable, Sendable, ExpressibleByStringLiteral {
/// The kind's name, e.g. `"server"`.
public let rawValue: String
/// Creates a kind from its name.
public init(rawValue: String) {
self.rawValue = rawValue
}
/// Creates a kind from a string literal.
public init(stringLiteral value: String) {
self.init(rawValue: value)
}
/// Storage answered with a non-2xx status. Branch on ``StorageError/code`` and
/// `serverError?.statusCode` for what Storage reported; ``StorageError/serverError`` is `nil`
/// when the body was not a Storage error payload.
public static let server: Kind = "server"
/// No response arrived, so whether Storage applied the request is unknown. Retry reads
/// freely; before retrying an upload, move or delete, check that it was not applied.
/// ``StorageError/underlyingError`` is usually a `URLError`.
public static let transport: Kind = "transport"
/// Storage answered 2xx but the body could not be used: it did not decode as the expected
/// type, or a required field was missing. Nothing to retry; report it.
/// ``StorageError/underlyingError`` is the `DecodingError` when there was one.
public static let decoding: Kind = "decoding"
/// The SDK refused to send: an object path was empty or contained a `..` segment, or a
/// per-call value such as an expiry was out of range. Fix the call. No request was sent.
public static let invalidRequest: Kind = "invalidRequest"
/// ``StorageClient/init(configuration:)`` refused the configuration: its URL has no scheme, no
/// host, or cannot be split into components. Fix the configuration. No client was created.
public static let invalidConfiguration: Kind = "invalidConfiguration"
/// The access token provider threw, so the request could not be authenticated and was not
/// sent. The provider is an `AccessTokenMiddleware` in ``StorageClient/Configuration/http``,
/// or `SupabaseClient`'s Auth session. ``StorageError/underlyingError`` is the provider's
/// error.
public static let accessToken: Kind = "accessToken"
}
/// A machine-readable code Storage sends alongside the error body.
///
/// Compare against the static members and keep a fallback branch: Storage adds codes, and a
/// server newer than the SDK can send one this version does not know.
public struct Code: RawRepresentable, Hashable, Sendable, ExpressibleByStringLiteral {
/// The code as Storage sends it, e.g. `"NoSuchKey"`.
public let rawValue: String
/// Creates a code from the value Storage sends.
public init(rawValue: String) {
self.rawValue = rawValue
}
/// Creates a code from a string literal.
public init(stringLiteral value: String) {
self.init(rawValue: value)
}
}
/// The error body Storage returns for a rejected request, without its code, which is
/// ``StorageError/code``.
///
/// Holds the flat shape the object, bucket, render, CDN and vector routes send,
/// `{"statusCode": "404", "error": "not_found", "message": "..."}`, and the Iceberg catalog
/// shape, `{"error": {"message": "...", "type": "...", "code": 404}}`, whose `type` lands in
/// ``error`` and whose numeric `code` lands in ``statusCode``.
public struct ServerError: Hashable, Sendable {
/// The status Storage reports in the body, e.g. `404`, which is the real outcome when the
/// HTTP status is 400. Storage sends it as `"404"` or as `404`.
public var statusCode: Int?
/// A short identifier such as `"not_found"` or `"Duplicate"`, when Storage sends one.
public var error: String?
/// The human-readable message.
public var message: String
/// Creates a ``ServerError``.
public init(statusCode: Int? = nil, error: String? = nil, message: String) {
self.statusCode = statusCode
self.error = error
self.message = message
}
}
/// What failed. Check it first.
public var kind: Kind
/// A human-readable description of the failure, from Storage or from the SDK.
public var message: String
/// The code Storage sent, such as ``Code-swift.struct/noSuchKey``. Set when ``kind`` is
/// ``Kind-swift.struct/server`` and the body carried one; `nil` otherwise.
public var code: Code?
/// The decoded error body. Set when ``kind`` is ``Kind-swift.struct/server`` and the body was
/// a Storage error payload; `nil` otherwise.
public var serverError: ServerError?
/// The HTTP status, headers, body and request id of a non-2xx response. `nil` when no
/// response arrived. The status is often 400 whatever the failure; read
/// `serverError?.statusCode`.
public var response: HTTPErrorResponse?
/// The error that caused this one, such as a `URLError` or a `DecodingError`.
public var underlyingError: (any Error)?
/// Whether Storage reported that the object or bucket does not exist: ``code`` is
/// ``Code-swift.struct/noSuchKey`` or ``Code-swift.struct/noSuchBucket``, or the server or
/// HTTP status is 404.
public var isNotFound: Bool {
code == .noSuchKey || code == .noSuchBucket || serverError?.statusCode == 404
|| response?.status == .notFound
}
/// Creates a ``StorageError``.
public init(
kind: Kind,
message: String,
code: Code? = nil,
serverError: ServerError? = nil,
response: HTTPErrorResponse? = nil,
underlyingError: (any Error)? = nil
) {
self.kind = kind
self.message = message
self.code = code
self.serverError = serverError
self.response = response
self.underlyingError = underlyingError
}
/// The kind and the message, plus the HTTP status and request id when a response arrived, and
/// the code when the server sent one.
public var description: String {
formattedDescription(kind: kind.rawValue, code: code?.rawValue)
}
}
extension StorageError.Code {
/// The specified bucket does not exist.
public static let noSuchBucket: StorageError.Code = "NoSuchBucket"
/// The bucket has no lifecycle configuration.
public static let noSuchLifecycleConfiguration: StorageError.Code = "NoSuchLifecycleConfiguration"
/// The specified key does not exist.
public static let noSuchKey: StorageError.Code = "NoSuchKey"
/// The specified upload does not exist.
public static let noSuchUpload: StorageError.Code = "NoSuchUpload"
/// The provided JWT is invalid.
public static let invalidJWT: StorageError.Code = "InvalidJWT"
/// The request is not properly formed.
public static let invalidRequest: StorageError.Code = "InvalidRequest"
/// An argument in the request is not valid.
public static let invalidArgument: StorageError.Code = "InvalidArgument"
/// The request body is not well formed XML.
public static let malformedXML: StorageError.Code = "MalformedXML"
/// The specified tenant does not exist.
public static let tenantNotFound: StorageError.Code = "TenantNotFound"
/// The entity being uploaded is too large.
public static let entityTooLarge: StorageError.Code = "EntityTooLarge"
/// An internal server error occurred.
public static let internalError: StorageError.Code = "InternalError"
/// The specified resource already exists.
public static let resourceAlreadyExists: StorageError.Code = "ResourceAlreadyExists"
/// The resource still has contents, such as a bucket that is not empty.
public static let resourceNotEmpty: StorageError.Code = "ResourceNotEmpty"
/// The specified bucket name is invalid.
public static let invalidBucketName: StorageError.Code = "InvalidBucketName"
/// The specified key is invalid.
public static let invalidKey: StorageError.Code = "InvalidKey"
/// The specified range is not valid.
public static let invalidRange: StorageError.Code = "InvalidRange"
/// The specified MIME type is not valid.
public static let invalidMimeType: StorageError.Code = "InvalidMimeType"
/// The specified upload ID is invalid.
public static let invalidUploadID: StorageError.Code = "InvalidUploadId"
/// The specified key already exists.
public static let keyAlreadyExists: StorageError.Code = "KeyAlreadyExists"
/// The specified bucket already exists.
public static let bucketAlreadyExists: StorageError.Code = "BucketAlreadyExists"
/// Timeout occurred while accessing the database.
public static let databaseTimeout: StorageError.Code = "DatabaseTimeout"
/// The database is currently in read-only mode.
public static let databaseReadOnly: StorageError.Code = "DatabaseReadOnly"
/// The database transaction was aborted and has to be rolled back before retrying.
public static let databaseTransactionAborted: StorageError.Code = "DatabaseTransactionAborted"
/// The database schema is invalid or incompatible.
public static let databaseInvalidObjectDefinition: StorageError.Code =
"DatabaseInvalidObjectDefinition"
/// The database schema is out of sync with Storage.
public static let databaseSchemaMismatch: StorageError.Code = "DatabaseSchemaMismatch"
/// The signature provided does not match the calculated signature.
public static let invalidSignature: StorageError.Code = "InvalidSignature"
/// The provided token has expired.
public static let expiredToken: StorageError.Code = "ExpiredToken"
/// The request signature does not match the calculated signature.
public static let signatureDoesNotMatch: StorageError.Code = "SignatureDoesNotMatch"
/// Access to the specified resource is denied.
public static let accessDenied: StorageError.Code = "AccessDenied"
/// The specified resource is locked.
public static let resourceLocked: StorageError.Code = "ResourceLocked"
/// The resource is still referenced by another resource.
public static let resourceReferenced: StorageError.Code = "ResourceReferenced"
/// An error occurred while accessing the database.
public static let databaseError: StorageError.Code = "DatabaseError"
/// The database transaction could not be completed.
public static let transactionError: StorageError.Code = "TransactionError"
/// The Content-Length header is missing.
public static let missingContentLength: StorageError.Code = "MissingContentLength"
/// A required parameter is missing in the request.
public static let missingParameter: StorageError.Code = "MissingParameter"
/// A parameter in the request is not valid.
public static let invalidParameter: StorageError.Code = "InvalidParameter"
/// The provided upload signature is invalid.
public static let invalidUploadSignature: StorageError.Code = "InvalidUploadSignature"
/// Timeout occurred while waiting for a lock.
public static let lockTimeout: StorageError.Code = "LockTimeout"
/// An error occurred in the S3 backend.
public static let s3Error: StorageError.Code = "S3Error"
/// The provided access key ID is invalid.
public static let invalidAccessKeyID: StorageError.Code = "InvalidAccessKeyId"
/// The maximum number of credentials has been reached.
public static let maximumCredentialsLimit: StorageError.Code = "MaximumCredentialsLimit"
/// The checksum of the entity does not match.
public static let invalidChecksum: StorageError.Code = "InvalidChecksum"
/// A part of the entity is missing.
public static let missingPart: StorageError.Code = "MissingPart"
/// The request rate is too high and has been throttled.
public static let slowDown: StorageError.Code = "SlowDown"
/// The resumable upload protocol reported an error.
public static let tusError: StorageError.Code = "TusError"
/// The request was aborted.
public static let aborted: StorageError.Code = "Aborted"
/// The request was aborted and the upload was terminated.
public static let abortedTerminate: StorageError.Code = "AbortedTerminate"
/// The feature is not enabled for this resource.
public static let featureNotEnabled: StorageError.Code = "FeatureNotEnabled"
/// The requested feature is not supported.
public static let notSupported: StorageError.Code = "NotSupported"
/// The maximum number of this catalog resource has been reached.
public static let icebergMaximumResourceLimit: StorageError.Code = "IcebergMaximumResourceLimit"
/// The catalog resource is not empty.
public static let icebergResourceNotEmpty: StorageError.Code = "IcebergResourceNotEmpty"
/// The specified catalog does not exist.
public static let noSuchCatalog: StorageError.Code = "NoSuchCatalog"
/// Storage could not classify the failure.
public static let unknownError: StorageError.Code = "UnknownError"
/// The vector resource already exists.
public static let conflictException: StorageError.Code = "ConflictException"
/// The vector resource does not exist.
public static let notFoundException: StorageError.Code = "NotFoundException"
/// The vector bucket is not empty.
public static let vectorBucketNotEmpty: StorageError.Code = "VectorBucketNotEmpty"
/// The maximum number of vector buckets has been reached.
public static let s3VectorMaxBucketsExceeded: StorageError.Code = "S3VectorMaxBucketsExceeded"
/// The maximum number of vector indexes has been reached.
public static let s3VectorMaxIndexesExceeded: StorageError.Code = "S3VectorMaxIndexesExceeded"
/// No shard is available to host the resource.
public static let noAvailableShard: StorageError.Code = "NoAvailableShard"
/// The specified shard does not exist.
public static let shardNotFound: StorageError.Code = "ShardNotFound"
}
extension StorageError.Kind {
/// Matches a ``StorageError`` of this ``StorageError/kind``, so a `catch` clause can name the
/// kind directly.
///
/// ```swift
/// do {
/// _ = try await storage.from("avatars").download(path: "a.png")
/// } catch StorageError.Kind.transport {
/// showOfflineBanner()
/// }
/// ```
public static func ~= (kind: StorageError.Kind, error: any Error) -> Bool {
(error as? StorageError)?.kind == kind
}
}
extension StorageError.Code {
/// Matches a ``StorageError`` with this ``StorageError/code``, so a `catch` clause can name the
/// code directly.
///
/// ```swift
/// do {
/// _ = try await storage.from("avatars").download(path: "a.png")
/// } catch StorageError.Code.noSuchKey {
/// showPlaceholder()
/// }
/// ```
public static func ~= (code: StorageError.Code, error: any Error) -> Bool {
(error as? StorageError)?.code == code
}
}
extension StorageError {
/// A decoded Storage error body: the code goes to ``StorageError/code`` and the rest to
/// ``StorageError/ServerError``.
struct Body: Decodable {
var code: Code?
var serverError: ServerError
private enum CodingKeys: String, CodingKey {
case statusCode, error, code, message
}
private enum IcebergKeys: String, CodingKey {
case message, type, code
}
/// A field Storage spells as a string on some routes and as a number on others.
private enum StringOrInt: Decodable {
case string(String)
case int(Int)
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let int = try? container.decode(Int.self) {
self = .int(int)
} else {
self = .string(try container.decode(String.self))
}
}
var int: Int? {
switch self {
case .int(let int): int
case .string(let string): Int(string)
}
}
}
/// Decodes the flat error body, or the Iceberg catalog shape.
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let iceberg = try? container.nestedContainer(keyedBy: IcebergKeys.self, forKey: .error) {
let type = try iceberg.decodeIfPresent(String.self, forKey: .type)
var statusCode: Int?
var codeText = type
switch try iceberg.decodeIfPresent(StringOrInt.self, forKey: .code) {
case .string(let value): codeText = value
case .int(let value): statusCode = value
case nil: break
}
code = codeText.map(Code.init(rawValue:))
serverError = ServerError(
statusCode: statusCode, error: type,
message: try iceberg.decode(String.self, forKey: .message))
return
}
code = try container.decodeIfPresent(String.self, forKey: .code).map(Code.init(rawValue:))
serverError = ServerError(
statusCode: try container.decodeIfPresent(StringOrInt.self, forKey: .statusCode)?.int,
error: try container.decodeIfPresent(String.self, forKey: .error),
message: try container.decode(String.self, forKey: .message))
}
}
}