diff --git a/Sources/ATResolve/ATResolver.swift b/Sources/ATResolve/ATResolver.swift index c3bfad6..f6e17cd 100644 --- a/Sources/ATResolve/ATResolver.swift +++ b/Sources/ATResolve/ATResolver.swift @@ -5,6 +5,11 @@ enum ATResolverError: Error { case requestFailed } +struct XRPCError: Decodable, Error { + let error: String + let message: String? +} + public struct ResolvedData: Codable, Hashable, Sendable { public let did: String public let handle: String @@ -61,16 +66,23 @@ public struct ATResolver { return did } - return try await blueskyGetProfile(handle).did + return try await blueskyGetProfile(handle)?.did } - public func blueskyGetProfile(_ actor: String) async throws -> BlueskyProfile { - try await provider.decodeJSON( - host: "public.api.bsky.app", - path: "/xrpc/app.bsky.actor.getProfile", - headers: ["Accept": "application/json"], - queryItems: [("actor", actor)] - ) + public func blueskyGetProfile(_ actor: String) async throws -> BlueskyProfile? { + do { + return try await provider.decodeJSON( + host: "public.api.bsky.app", + path: "/xrpc/app.bsky.actor.getProfile", + headers: ["Accept": "application/json"], + queryItems: [("actor", actor)] + ) + } catch let error as XRPCError + where error.error == "InvalidRequest" && + (error.message?.localizedCaseInsensitiveContains("not found") == true || + error.message?.localizedCaseInsensitiveContains("invalid app.bsky.actor.getProfile params") == true) { + return nil + } } public func plcDirectoryQuery( @@ -87,7 +99,6 @@ public struct ATResolver { guard let did = try await didForHandle(handle) else { return nil } - print("did: \(did)") let directoryResult = try await plcDirectoryQuery(did) diff --git a/Sources/ATResolve/Networking.swift b/Sources/ATResolve/Networking.swift index 3b9f823..b7e1356 100644 --- a/Sources/ATResolve/Networking.swift +++ b/Sources/ATResolve/Networking.swift @@ -31,8 +31,11 @@ extension URLSession: ResponseProviding { else { print("data:", String(decoding: data, as: UTF8.self)) print("response:", response) - - throw ATResolverError.requestFailed + if let xrpcError = try? JSONDecoder().decode(XRPCError.self, from: data) { + throw xrpcError + } else { + throw ATResolverError.requestFailed + } } return data } diff --git a/Tests/ATResolveTests/ATResolveTests.swift b/Tests/ATResolveTests/ATResolveTests.swift index a5f39b1..a6849e0 100644 --- a/Tests/ATResolveTests/ATResolveTests.swift +++ b/Tests/ATResolveTests/ATResolveTests.swift @@ -32,7 +32,25 @@ struct ATResolveTests { let profile = try await resolver.blueskyGetProfile("massicotte.org") - #expect(profile.did == "did:plc:klsh7edzj3jmxucibyjqstb3") + #expect(profile?.did == "did:plc:klsh7edzj3jmxucibyjqstb3") + } + + @Test + func blueskyGetProfileWithNonexistentHandle() async throws { + let resolver = ATResolver(provider: URLSession.shared) + + let profile = try await resolver.blueskyGetProfile("nonexistent.example.com") + + #expect(profile == nil) + } + + @Test + func blueskyGetProfileReturnsNilForInvalidFormatHandle() async throws { + let resolver = ATResolver(provider: URLSession.shared) + + let profile = try await resolver.blueskyGetProfile("nonexistent@example.com") + + #expect(profile == nil) } @Test func bskySocialHandle() async throws { @@ -40,7 +58,7 @@ struct ATResolveTests { let profile = try await resolver.resolveHandle("cjrdev.bsky.social") - #expect(profile != nil) + #expect(profile?.did == "did:plc:wlef3srsa3hlyzj2hy6yncrh") } @Test func decodeWithCustomProvider() async throws {