mount public data from the atmosphere to a virtual filesystem (macos only) pdfs.at
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(atproto): add error model with POSIX errno mapping

+97
+30
Packages/ATProto/Sources/ATProto/Errors/ATProtoError+POSIX.swift
··· 1 + import Foundation 2 + #if canImport(Darwin) 3 + import Darwin 4 + #endif 5 + 6 + public extension ATProtoError { 7 + var posixErrno: Int32 { 8 + switch self { 9 + case .network: return EIO 10 + case .http(let status, _): 11 + switch status { 12 + case 401, 403: return EACCES 13 + case 404: return ENOENT 14 + case 409: return EEXIST 15 + case 413: return EFBIG 16 + case 429: return EAGAIN 17 + case 500...599: return EIO 18 + default: return EIO 19 + } 20 + case .xrpc(_, _, let status): 21 + return ATProtoError.http(status: status, body: nil).posixErrno 22 + case .decoding: return EIO 23 + case .invalidIdentity: return EINVAL 24 + case .cidMismatch: return EEXIST 25 + case .notAuthenticated: return EACCES 26 + case .scopeDenied: return EACCES 27 + case .invalidURL: return EINVAL 28 + } 29 + } 30 + }
+18
Packages/ATProto/Sources/ATProto/Errors/ATProtoError.swift
··· 1 + import Foundation 2 + 3 + public enum ATProtoError: Error, Sendable { 4 + case network(URLError) 5 + case http(status: Int, body: Data?) 6 + case xrpc(code: String, message: String?, status: Int) 7 + case decoding(String) 8 + case invalidIdentity(String) 9 + case cidMismatch(expected: CID, actual: CID?) 10 + case notAuthenticated 11 + case scopeDenied(requested: String, granted: Set<String>) 12 + case invalidURL(String) 13 + 14 + public struct XRPCEnvelope: Decodable, Sendable { 15 + public let error: String 16 + public let message: String? 17 + } 18 + }
+49
Packages/ATProto/Tests/ATProtoTests/ErrorMappingTests.swift
··· 1 + import Foundation 2 + import Testing 3 + @testable import ATProto 4 + 5 + @Suite("Error → POSIX errno") 6 + struct ErrorMappingTests { 7 + @Test("401 → EACCES after refresh") 8 + func unauthorized() { 9 + #expect(ATProtoError.http(status: 401, body: nil).posixErrno == EACCES) 10 + } 11 + @Test("404 → ENOENT") 12 + func notFound() { 13 + #expect(ATProtoError.http(status: 404, body: nil).posixErrno == ENOENT) 14 + } 15 + @Test("409 → EEXIST") 16 + func conflict() { 17 + #expect(ATProtoError.http(status: 409, body: nil).posixErrno == EEXIST) 18 + } 19 + @Test("413 → EFBIG") 20 + func tooLarge() { 21 + #expect(ATProtoError.http(status: 413, body: nil).posixErrno == EFBIG) 22 + } 23 + @Test("429 → EAGAIN") 24 + func rateLimited() { 25 + #expect(ATProtoError.http(status: 429, body: nil).posixErrno == EAGAIN) 26 + } 27 + @Test("5xx → EIO") 28 + func serverError() { 29 + #expect(ATProtoError.http(status: 500, body: nil).posixErrno == EIO) 30 + #expect(ATProtoError.http(status: 503, body: nil).posixErrno == EIO) 31 + } 32 + @Test("network → EIO") 33 + func network() { 34 + let urlErr = URLError(.notConnectedToInternet) 35 + #expect(ATProtoError.network(urlErr).posixErrno == EIO) 36 + } 37 + @Test("cid mismatch → EEXIST") 38 + func cidMismatch() { 39 + let cid = CID("bafyreibyvxuebctujncksacu3qfxsv6pzm7z67vfrftndn6pzhknf7g5me")! 40 + #expect(ATProtoError.cidMismatch(expected: cid, actual: nil).posixErrno == EEXIST) 41 + } 42 + @Test("xrpc envelope decodes error/message") 43 + func envelope() throws { 44 + let json = #"{"error":"InvalidRequest","message":"bad rkey"}"#.data(using: .utf8)! 45 + let env = try JSONDecoder().decode(ATProtoError.XRPCEnvelope.self, from: json) 46 + #expect(env.error == "InvalidRequest") 47 + #expect(env.message == "bad rkey") 48 + } 49 + }