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 describeRepo typed endpoint

+95
+28
Packages/ATProto/Sources/ATProto/XRPC/AnyDecodable.swift
··· 1 + import Foundation 2 + 3 + /// Helper to decode arbitrary JSON values into `Any` for pass-through storage. 4 + /// Used by XRPC outputs that carry opaque record values through to callers. 5 + struct AnyDecodable: Decodable { 6 + let value: Any 7 + 8 + init(from decoder: Decoder) throws { 9 + let c = try decoder.singleValueContainer() 10 + if c.decodeNil() { 11 + value = NSNull() 12 + } else if let b = try? c.decode(Bool.self) { 13 + value = b 14 + } else if let i = try? c.decode(Int64.self) { 15 + value = i 16 + } else if let d = try? c.decode(Double.self) { 17 + value = d 18 + } else if let s = try? c.decode(String.self) { 19 + value = s 20 + } else if let arr = try? c.decode([AnyDecodable].self) { 21 + value = arr.map(\.value) 22 + } else if let dict = try? c.decode([String: AnyDecodable].self) { 23 + value = dict.mapValues(\.value) 24 + } else { 25 + throw DecodingError.dataCorruptedError(in: c, debugDescription: "Unsupported JSON value") 26 + } 27 + } 28 + }
+30
Packages/ATProto/Sources/ATProto/XRPC/DescribeRepoOutput.swift
··· 1 + import Foundation 2 + 3 + public struct DescribeRepoOutput: Decodable, Sendable, Equatable { 4 + public let handle: String 5 + public let did: String 6 + public let collections: [String] 7 + public let handleIsCorrect: Bool? 8 + 9 + // didDoc is JSON-any; keep it as a raw Data blob so callers can parse only 10 + // the pieces they need (avoids modelling the entire DID-core schema). 11 + public let didDoc: Data? 12 + 13 + enum CodingKeys: String, CodingKey { 14 + case handle, did, collections, handleIsCorrect, didDoc 15 + } 16 + 17 + public init(from decoder: Decoder) throws { 18 + let c = try decoder.container(keyedBy: CodingKeys.self) 19 + handle = try c.decode(String.self, forKey: .handle) 20 + did = try c.decode(String.self, forKey: .did) 21 + collections = try c.decode([String].self, forKey: .collections) 22 + handleIsCorrect = try c.decodeIfPresent(Bool.self, forKey: .handleIsCorrect) 23 + if c.contains(.didDoc) { 24 + let any = try c.decode(AnyDecodable.self, forKey: .didDoc) 25 + didDoc = try JSONSerialization.data(withJSONObject: any.value) 26 + } else { 27 + didDoc = nil 28 + } 29 + } 30 + }
+7
Packages/ATProto/Sources/ATProto/XRPC/XRPCEndpoints.swift
··· 1 + import Foundation 2 + 3 + public extension XRPCClient { 4 + func describeRepo(repo: String) async throws -> DescribeRepoOutput { 5 + try await get(nsid: "com.atproto.repo.describeRepo", params: ["repo": repo]) 6 + } 7 + }
+30
Packages/ATProto/Tests/ATProtoTests/XRPCEndpointsTests.swift
··· 1 + import Foundation 2 + import Testing 3 + @testable import ATProto 4 + 5 + @Suite("XRPC typed endpoints", .serialized) 6 + struct XRPCEndpointsTests { 7 + func makeClient(handler: @escaping (URLRequest) -> URLProtocolStub.Response) -> XRPCClient { 8 + let session = URLProtocolStub.install(handler: handler) 9 + return XRPCClient( 10 + service: URL(string: "https://bsky.social")!, 11 + session: session, 12 + auth: AnonymousAuthTokenProvider() 13 + ) 14 + } 15 + 16 + @Test("describeRepo returns collections + handle") 17 + func describeRepo() async throws { 18 + let client = makeClient { request in 19 + #expect(request.url?.path == "/xrpc/com.atproto.repo.describeRepo") 20 + #expect(request.url?.query?.contains("repo=did:plc:abc") == true) 21 + return .json(#"{"handle":"nate.natemoo.re","did":"did:plc:abc","didDoc":{"id":"did:plc:abc"},"collections":["app.bsky.feed.post","app.bsky.actor.profile"],"handleIsCorrect":true}"#) 22 + } 23 + defer { URLProtocolStub.reset() } 24 + 25 + let result = try await client.describeRepo(repo: "did:plc:abc") 26 + #expect(result.handle == "nate.natemoo.re") 27 + #expect(result.did == "did:plc:abc") 28 + #expect(result.collections == ["app.bsky.feed.post", "app.bsky.actor.profile"]) 29 + } 30 + }