From c73c87266c152ffab0ee199680c9dd503c88ee44 Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Mon, 1 Dec 2025 16:02:29 -0800 Subject: [PATCH 1/7] generalize ResponseProviding to HTTPSRequester --- Sources/ATResolve/ATResolver.swift | 36 +++++++-------- Sources/ATResolve/HTTPRequester.swift | 56 +++++++++++++++++++++++ Sources/ATResolve/Networking.swift | 33 ++++++------- Tests/ATResolveTests/ATResolveTests.swift | 20 ++++---- 4 files changed, 99 insertions(+), 46 deletions(-) create mode 100644 Sources/ATResolve/HTTPRequester.swift diff --git a/Sources/ATResolve/ATResolver.swift b/Sources/ATResolve/ATResolver.swift index d1e81b2..f561bef 100644 --- a/Sources/ATResolve/ATResolver.swift +++ b/Sources/ATResolve/ATResolver.swift @@ -32,21 +32,11 @@ public struct PLCDirectoryResolveDidResponse: Codable, Hashable, Sendable { } } -public protocol ResponseProviding { - func decodeJSON(at urlString: String, queryItems: [(String, String)]) async throws -> T -} - -extension ResponseProviding { - public func decodeJSON(at urlString: String) async throws -> T { - try await decodeJSON(at: urlString, queryItems: []) - } -} - -public struct ATResolver { - public let provider: Provider +public struct ATResolver { + public let requester: Requester - public init(provider: Provider) { - self.provider = provider + public init(requester: Requester) { + self.requester = requester } public func didForDomain(_ name: String) async throws -> String? { @@ -75,15 +65,21 @@ public struct ATResolver { } public func blueskyGetProfile(_ actor: String) async throws -> BlueskyProfile { - try await provider.decodeJSON( - at: "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile", + try await requester.decodeJSON( + host: "public.api.bsky.app", + path: "/xrpc/app.bsky.actor.getProfile", + headers: ["Accept": "application/json"], queryItems: [("actor", actor)] ) } - public func plcDirectoryQuery(_ did: String) async throws -> PLCDirectoryResolveDidResponse { - try await provider.decodeJSON( - at: "https://plc.directory/\(did)" + public func plcDirectoryQuery( + _ did: String + ) async throws -> PLCDirectoryResolveDidResponse { + try await requester.decodeJSON( + host: "plc.directory", + path: "/\(did)", + headers: ["Accept": "application/json"] ) } @@ -99,7 +95,7 @@ public struct ATResolver { } } -extension ATResolver: Sendable where Provider: Sendable {} +extension ATResolver: Sendable where Requester: Sendable {} #if canImport(Foundation) import Foundation diff --git a/Sources/ATResolve/HTTPRequester.swift b/Sources/ATResolve/HTTPRequester.swift new file mode 100644 index 0000000..6740939 --- /dev/null +++ b/Sources/ATResolve/HTTPRequester.swift @@ -0,0 +1,56 @@ +// +// HTTPRequester.swift +// ATResolve +// +// Created by Mark @ Germ on 12/1/25. +// + +import Foundation + +///Allows the client to choose between URLSession (Foundation) or AsyncHTTPClient, +///Or another networking library of their choice + +//from Dave Delong +public struct HTTPMethod: Hashable, Sendable { + public static let get = HTTPMethod(rawValue: "GET") + public static let post = HTTPMethod(rawValue: "POST") + public static let put = HTTPMethod(rawValue: "PUT") + public static let delete = HTTPMethod(rawValue: "DELETE") + + public let rawValue: String +} + +public struct GenericHTTPSComponents { + let host: String + let path: String + let method: HTTPMethod + let headers: [String: String] + let queryItems: [(String, String?)] +} + +public protocol HTTPSRequester { + func request(parameters: GenericHTTPSComponents) async throws -> Data +} + +extension HTTPSRequester { + func decodeJSON( + host: String, + path: String, + method: HTTPMethod = .get, + headers: [String: String] = [:], + queryItems: [(String, String)] = [] + ) async throws -> T { + let result = try await request( + parameters: .init( + host: host, + path: path, + method: method, + headers: headers, + queryItems: queryItems + ) + ) + + return try JSONDecoder().decode(T.self, from: result) + } + +} diff --git a/Sources/ATResolve/Networking.swift b/Sources/ATResolve/Networking.swift index 729ca40..e2572e9 100644 --- a/Sources/ATResolve/Networking.swift +++ b/Sources/ATResolve/Networking.swift @@ -1,27 +1,28 @@ +#if canImport(Foundation) import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif -extension URLSession: ResponseProviding { - public func decodeJSON(at urlString: String, queryItems: [(String, String)]) async throws -> T where T : Decodable { - guard var components = URLComponents(string: urlString) else { - throw ATResolverError.urlInvalid - } - - components.queryItems = queryItems.map({ pair in +extension URLSession: HTTPSRequester { + public func request(parameters: GenericHTTPSComponents) async throws -> Data { + var components = URLComponents() + components.scheme = "https" + components.host = parameters.host + components.path = parameters.path + components.queryItems = parameters.queryItems.map({ pair in URLQueryItem(name: pair.0, value: pair.1) }) - + guard let url = components.url else { - throw ATResolverError.urlInvalid + throw URLError(.badURL) } - var request = URLRequest(url: url) - - request.httpMethod = "GET" - request.setValue("application/json", forHTTPHeaderField: "Accept") - + request.httpMethod = parameters.method.rawValue + for (key, value) in parameters.headers { + request.addValue(value, forHTTPHeaderField: key) + } + request.addValue("text/plain;charset=UTF-8", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) guard @@ -33,7 +34,7 @@ extension URLSession: ResponseProviding { throw ATResolverError.requestFailed } - - return try JSONDecoder().decode(T.self, from: data) + return data } } +#endif diff --git a/Tests/ATResolveTests/ATResolveTests.swift b/Tests/ATResolveTests/ATResolveTests.swift index 2b89692..9e88bc6 100644 --- a/Tests/ATResolveTests/ATResolveTests.swift +++ b/Tests/ATResolveTests/ATResolveTests.swift @@ -8,7 +8,7 @@ import FoundationNetworking struct ATResolveTests { @Test func resolveHandle() async throws { - let resolver = ATResolver(provider: URLSession.shared) + let resolver = ATResolver(requester: URLSession.shared) let data = try await resolver.resolveHandle("massicotte.org") @@ -19,7 +19,7 @@ struct ATResolveTests { @Test func didForDomain() async throws { - let resolver = ATResolver(provider: URLSession.shared) + let resolver = ATResolver(requester: URLSession.shared) let did = try await resolver.didForDomain("massicotte.org") @@ -28,7 +28,7 @@ struct ATResolveTests { @Test func blueskyGetProfile() async throws { - let resolver = ATResolver(provider: URLSession.shared) + let resolver = ATResolver(requester: URLSession.shared) let profile = try await resolver.blueskyGetProfile("massicotte.org") @@ -36,7 +36,7 @@ struct ATResolveTests { } @Test func bskySocialHandle() async throws { - let resolver = ATResolver(provider: URLSession.shared) + let resolver = ATResolver(requester: URLSession.shared) let profile = try await resolver.resolveHandle("cjrdev.bsky.social") @@ -44,20 +44,20 @@ struct ATResolveTests { } @Test func decodeWithCustomProvider() async throws { - struct CustomProvider: ResponseProviding { + struct CustomProvider: HTTPSRequester { let content = """ {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1","https://w3id.org/security/suites/secp256k1-2019/v1"],"id":"did:plc:klsh7edzj3jmxucibyjqstb3","alsoKnownAs":["at://massicotte.org"],"verificationMethod":[{"id":"did:plc:klsh7edzj3jmxucibyjqstb3#atproto","type":"Multikey","controller":"did:plc:klsh7edzj3jmxucibyjqstb3","publicKeyMultibase":"zQ3shP3NvazgSaEFpryzuyx8Q4MHho2KC2MNobAuQX3gdKAPW"}],"service":[{"id":"#atproto_pds","type":"AtprotoPersonalDataServer","serviceEndpoint":"https://milkcap.us-west.host.bsky.network"}]} """ - func decodeJSON(at urlString: String, queryItems: [(String, String)]) async throws -> T where T : Decodable { - try JSONDecoder().decode(T.self, from: Data(content.utf8)) + func request(parameters: GenericHTTPSComponents) async throws -> Data { + Data(content.utf8) } } - let resolver = ATResolver(provider: CustomProvider()) - + let resolver = ATResolver(requester: CustomProvider()) + let response = try await resolver.plcDirectoryQuery("did:plc:klsh7edzj3jmxucibyjqstb3") - + #expect(response.pds?.serviceEndpoint == "https://milkcap.us-west.host.bsky.network") } } From a3d22afb9898174d8a3437b81aa2aef36e6617b9 Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Mon, 1 Dec 2025 16:35:05 -0800 Subject: [PATCH 2/7] pull in AsyncHTTPRequests into Test dependencies to demonstrate usage of HTTPClient in place of URLSession --- Package.resolved | 191 +++++++++++++++++- Package.swift | 10 +- Sources/ATResolve/HTTPRequester.swift | 10 +- .../ATResolveAsyncHTTPTests.swift | 27 +++ .../AsyncHTTPClient+HTTPRequester.swift | 68 +++++++ 5 files changed, 298 insertions(+), 8 deletions(-) create mode 100644 Tests/ATResolveTests/ATResolveAsyncHTTPTests.swift create mode 100644 Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift diff --git a/Package.resolved b/Package.resolved index 1670f17..cd91f74 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,42 @@ { - "originHash" : "79772beb13b2d5e9ae03d91ea73aefd861946eb487105353f86977b6c2920fa2", + "originHash" : "9fdc3a6675148ed72144254c67fa4f97c5a54b4ac763b21e9e22db841a3b7bc8", "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "b2faff932b956df50668241d14f1b42f7bae12b4", + "version" : "1.30.0" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "40d25bbb2fc5b557a9aa8512210bded327c0f60d", + "version" : "1.5.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804", + "version" : "1.1.1" + } + }, { "identity" : "swift-async-dns-resolver", "kind" : "remoteSourceControl", @@ -9,6 +45,159 @@ "revision" : "08c07ff31a745ee5e522ac10132fb4949834d925", "version" : "0.4.0" } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-certificates", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-certificates.git", + "state" : { + "revision" : "66a8512c4e7466582bab21e0e0c333f01974e5b6", + "version" : "1.16.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "6f70fa9eab24c1fd982af18c281c4525d05e3095", + "version" : "4.2.0" + } + }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing.git", + "state" : { + "revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b", + "version" : "1.6.0" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types.git", + "state" : { + "revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca", + "version" : "1.5.1" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2", + "version" : "1.6.4" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "3eea09220e07d34ace722221cbda90306f48c86c", + "version" : "2.90.1" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "7ee281d816fa8e5f3967a2c294035a318ea551c7", + "version" : "1.31.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "c2ba4cfbb83f307c66f5a6df6bb43e3c88dfbf80", + "version" : "1.39.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "173cc69a058623525a58ae6710e2f5727c663793", + "version" : "2.36.0" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "60c3e187154421171721c1a38e800b390680fb5d", + "version" : "1.26.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-service-lifecycle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", + "state" : { + "revision" : "1de37290c0ab3c5a96028e0f02911b672fd42348", + "version" : "2.9.1" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db", + "version" : "1.6.3" + } } ], "version" : 3 diff --git a/Package.swift b/Package.swift index 0a792a3..d98f657 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,9 @@ let package = Package( targets: ["ATResolve"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-async-dns-resolver.git", from: "0.4.0") + .package(url: "https://github.com/apple/swift-async-dns-resolver.git", from: "0.4.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.30.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0") ], targets: [ .target( @@ -28,7 +30,11 @@ let package = Package( ]), .testTarget( name: "ATResolveTests", - dependencies: ["ATResolve"] + dependencies: [ + "ATResolve", + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "NIOHTTP1", package: "swift-nio") + ] ), ] ) diff --git a/Sources/ATResolve/HTTPRequester.swift b/Sources/ATResolve/HTTPRequester.swift index 6740939..835a3d0 100644 --- a/Sources/ATResolve/HTTPRequester.swift +++ b/Sources/ATResolve/HTTPRequester.swift @@ -21,11 +21,11 @@ public struct HTTPMethod: Hashable, Sendable { } public struct GenericHTTPSComponents { - let host: String - let path: String - let method: HTTPMethod - let headers: [String: String] - let queryItems: [(String, String?)] + public let host: String + public let path: String + public let method: HTTPMethod + public let headers: [String: String] + public let queryItems: [(String, String?)] } public protocol HTTPSRequester { diff --git a/Tests/ATResolveTests/ATResolveAsyncHTTPTests.swift b/Tests/ATResolveTests/ATResolveAsyncHTTPTests.swift new file mode 100644 index 0000000..fcbc61f --- /dev/null +++ b/Tests/ATResolveTests/ATResolveAsyncHTTPTests.swift @@ -0,0 +1,27 @@ +// +// ATResolveAsyncHTTPTests.swift +// ATResolve +// +// Created by Mark @ Germ on 12/1/25. +// + +#if canImport(AsyncHTTPClient) +import ATResolve +import Foundation +import AsyncHTTPClient +import Testing + +//Test API with AsyncHTTPClient +struct ATResolveAsyncHTTPTests { + @Test + func resolveHandle() async throws { + let resolver = ATResolver(requester: HTTPClient.shared) + + let data = try await resolver.resolveHandle("massicotte.org") + + #expect(data?.did == "did:plc:klsh7edzj3jmxucibyjqstb3") + #expect(data?.handle == "massicotte.org") + #expect(data?.serviceEndpoint == "https://milkcap.us-west.host.bsky.network") + } +} +#endif diff --git a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift new file mode 100644 index 0000000..85be44b --- /dev/null +++ b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift @@ -0,0 +1,68 @@ +// +// File.swift +// ATResolve +// +// Created by Mark @ Germ on 12/1/25. +// + +#if canImport(AsyncHTTPClient) +import ATResolve +import AsyncHTTPClient +//should be CoreFoundation, for Data type +import Foundation +import NIOHTTP1 + +extension ATResolve.HTTPMethod { + var convert: NIOHTTP1.HTTPMethod { + get throws { + switch rawValue { + case ATResolve.HTTPMethod.get.rawValue: + .GET + case ATResolve.HTTPMethod.post.rawValue: + .POST + case ATResolve.HTTPMethod.put.rawValue: + .PUT + case ATResolve.HTTPMethod.delete.rawValue: + .DELETE + default: + throw URLError(.badURL) + } + } + } +} + +extension HTTPClient: HTTPSRequester { + public func request( + parameters: ATResolve.GenericHTTPSComponents + ) async throws -> Data { + var components = URLComponents() + components.scheme = "https" + components.host = parameters.host + components.path = parameters.path + components.queryItems = parameters.queryItems.map({ pair in + URLQueryItem(name: pair.0, value: pair.1) + }) + + guard let url = components.url else { + throw URLError(.badURL) + } + + var request = HTTPClientRequest(url: url.absoluteString) + request.method = try parameters.method.convert + + let response = try await execute(request, timeout: .seconds(30)) + var body = try await response.body.collect(upTo: 1024 * 1024) + + guard response.status == .ok else { + print("response:", response) + throw URLError(.badServerResponse) + } + let result = body.readData(length: body.readableBytes) + + guard let result else { + throw URLError(.badServerResponse) + } + return result + } +} +#endif From 970852305bcb1b999dc2ae08137c0686d5ea91f7 Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Mon, 1 Dec 2025 17:04:37 -0800 Subject: [PATCH 3/7] import NIOFoundationCompat so we have access to ByteBuffer.readData in test --- Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift index 85be44b..a2437b5 100644 --- a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift +++ b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift @@ -11,6 +11,7 @@ import AsyncHTTPClient //should be CoreFoundation, for Data type import Foundation import NIOHTTP1 +import NIOFoundationCompat extension ATResolve.HTTPMethod { var convert: NIOHTTP1.HTTPMethod { From d8039c1223aff1764a39b671a5c153127972c9ad Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Mon, 1 Dec 2025 17:05:24 -0800 Subject: [PATCH 4/7] fix github runner issue by disambiguating the available vision OS test runners --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27de380..85bd56d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: - "platform=iOS Simulator,name=iPhone 16" - "platform=tvOS Simulator,name=Apple TV" - "platform=watchOS Simulator,name=Apple Watch Series 10 (42mm)" - - "platform=visionOS Simulator,name=Apple Vision Pro" + - "platform=visionOS Simulator,name=Apple Vision Pro,OS=26.1" steps: - uses: actions/checkout@v4 - name: Test platform ${{ matrix.destination }} From e0bf4f9d6c99cb99ca17dcac6bba92b58bebaab2 Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Tue, 9 Dec 2025 13:21:59 -0800 Subject: [PATCH 5/7] GenericHTTPSComponents -> Request --- Sources/ATResolve/HTTPRequester.swift | 4 ++-- Sources/ATResolve/Networking.swift | 2 +- Tests/ATResolveTests/ATResolveTests.swift | 2 +- Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/ATResolve/HTTPRequester.swift b/Sources/ATResolve/HTTPRequester.swift index 835a3d0..06a9703 100644 --- a/Sources/ATResolve/HTTPRequester.swift +++ b/Sources/ATResolve/HTTPRequester.swift @@ -20,7 +20,7 @@ public struct HTTPMethod: Hashable, Sendable { public let rawValue: String } -public struct GenericHTTPSComponents { +public struct Request { public let host: String public let path: String public let method: HTTPMethod @@ -29,7 +29,7 @@ public struct GenericHTTPSComponents { } public protocol HTTPSRequester { - func request(parameters: GenericHTTPSComponents) async throws -> Data + func request(parameters: Request) async throws -> Data } extension HTTPSRequester { diff --git a/Sources/ATResolve/Networking.swift b/Sources/ATResolve/Networking.swift index e2572e9..464be69 100644 --- a/Sources/ATResolve/Networking.swift +++ b/Sources/ATResolve/Networking.swift @@ -5,7 +5,7 @@ import FoundationNetworking #endif extension URLSession: HTTPSRequester { - public func request(parameters: GenericHTTPSComponents) async throws -> Data { + public func request(parameters: Request) async throws -> Data { var components = URLComponents() components.scheme = "https" components.host = parameters.host diff --git a/Tests/ATResolveTests/ATResolveTests.swift b/Tests/ATResolveTests/ATResolveTests.swift index 9e88bc6..a85aa4f 100644 --- a/Tests/ATResolveTests/ATResolveTests.swift +++ b/Tests/ATResolveTests/ATResolveTests.swift @@ -49,7 +49,7 @@ struct ATResolveTests { {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1","https://w3id.org/security/suites/secp256k1-2019/v1"],"id":"did:plc:klsh7edzj3jmxucibyjqstb3","alsoKnownAs":["at://massicotte.org"],"verificationMethod":[{"id":"did:plc:klsh7edzj3jmxucibyjqstb3#atproto","type":"Multikey","controller":"did:plc:klsh7edzj3jmxucibyjqstb3","publicKeyMultibase":"zQ3shP3NvazgSaEFpryzuyx8Q4MHho2KC2MNobAuQX3gdKAPW"}],"service":[{"id":"#atproto_pds","type":"AtprotoPersonalDataServer","serviceEndpoint":"https://milkcap.us-west.host.bsky.network"}]} """ - func request(parameters: GenericHTTPSComponents) async throws -> Data { + func request(parameters: Request) async throws -> Data { Data(content.utf8) } } diff --git a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift index a2437b5..2a3384b 100644 --- a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift +++ b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift @@ -34,7 +34,7 @@ extension ATResolve.HTTPMethod { extension HTTPClient: HTTPSRequester { public func request( - parameters: ATResolve.GenericHTTPSComponents + parameters: ATResolve.Request ) async throws -> Data { var components = URLComponents() components.scheme = "https" From 92e0a50f203d7bb674fde2794b8f98be8541686e Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Tue, 9 Dec 2025 13:31:06 -0800 Subject: [PATCH 6/7] HTTPSRequester -> ResponseProviding and change protocol function signature per PR discussion --- Sources/ATResolve/ATResolver.swift | 2 +- Sources/ATResolve/Networking.swift | 22 +++++++++---------- ...equester.swift => ResponseProviding.swift} | 12 +++++----- Tests/ATResolveTests/ATResolveTests.swift | 4 ++-- .../AsyncHTTPClient+HTTPRequester.swift | 18 +++++++-------- 5 files changed, 29 insertions(+), 29 deletions(-) rename Sources/ATResolve/{HTTPRequester.swift => ResponseProviding.swift} (84%) diff --git a/Sources/ATResolve/ATResolver.swift b/Sources/ATResolve/ATResolver.swift index f561bef..77a0933 100644 --- a/Sources/ATResolve/ATResolver.swift +++ b/Sources/ATResolve/ATResolver.swift @@ -32,7 +32,7 @@ public struct PLCDirectoryResolveDidResponse: Codable, Hashable, Sendable { } } -public struct ATResolver { +public struct ATResolver { public let requester: Requester public init(requester: Requester) { diff --git a/Sources/ATResolve/Networking.swift b/Sources/ATResolve/Networking.swift index 464be69..3b9f823 100644 --- a/Sources/ATResolve/Networking.swift +++ b/Sources/ATResolve/Networking.swift @@ -4,26 +4,26 @@ import Foundation import FoundationNetworking #endif -extension URLSession: HTTPSRequester { - public func request(parameters: Request) async throws -> Data { +extension URLSession: ResponseProviding { + public func data(for request: Request) async throws -> Data { var components = URLComponents() components.scheme = "https" - components.host = parameters.host - components.path = parameters.path - components.queryItems = parameters.queryItems.map({ pair in + components.host = request.host + components.path = request.path + components.queryItems = request.queryItems.map({ pair in URLQueryItem(name: pair.0, value: pair.1) }) guard let url = components.url else { throw URLError(.badURL) } - var request = URLRequest(url: url) - request.httpMethod = parameters.method.rawValue - for (key, value) in parameters.headers { - request.addValue(value, forHTTPHeaderField: key) + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = request.method.rawValue + for (key, value) in request.headers { + urlRequest.addValue(value, forHTTPHeaderField: key) } - request.addValue("text/plain;charset=UTF-8", forHTTPHeaderField: "Accept") - let (data, response) = try await URLSession.shared.data(for: request) + urlRequest.addValue("text/plain;charset=UTF-8", forHTTPHeaderField: "Accept") + let (data, response) = try await URLSession.shared.data(for: urlRequest) guard let httpResponse = response as? HTTPURLResponse, diff --git a/Sources/ATResolve/HTTPRequester.swift b/Sources/ATResolve/ResponseProviding.swift similarity index 84% rename from Sources/ATResolve/HTTPRequester.swift rename to Sources/ATResolve/ResponseProviding.swift index 06a9703..7839f24 100644 --- a/Sources/ATResolve/HTTPRequester.swift +++ b/Sources/ATResolve/ResponseProviding.swift @@ -1,5 +1,5 @@ // -// HTTPRequester.swift +// ResponseProviding.swift // ATResolve // // Created by Mark @ Germ on 12/1/25. @@ -28,11 +28,11 @@ public struct Request { public let queryItems: [(String, String?)] } -public protocol HTTPSRequester { - func request(parameters: Request) async throws -> Data +public protocol ResponseProviding { + func data(for: Request) async throws -> Data } -extension HTTPSRequester { +extension ResponseProviding { func decodeJSON( host: String, path: String, @@ -40,8 +40,8 @@ extension HTTPSRequester { headers: [String: String] = [:], queryItems: [(String, String)] = [] ) async throws -> T { - let result = try await request( - parameters: .init( + let result = try await data( + for: .init( host: host, path: path, method: method, diff --git a/Tests/ATResolveTests/ATResolveTests.swift b/Tests/ATResolveTests/ATResolveTests.swift index a85aa4f..f720956 100644 --- a/Tests/ATResolveTests/ATResolveTests.swift +++ b/Tests/ATResolveTests/ATResolveTests.swift @@ -44,12 +44,12 @@ struct ATResolveTests { } @Test func decodeWithCustomProvider() async throws { - struct CustomProvider: HTTPSRequester { + struct CustomProvider: ResponseProviding { let content = """ {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1","https://w3id.org/security/suites/secp256k1-2019/v1"],"id":"did:plc:klsh7edzj3jmxucibyjqstb3","alsoKnownAs":["at://massicotte.org"],"verificationMethod":[{"id":"did:plc:klsh7edzj3jmxucibyjqstb3#atproto","type":"Multikey","controller":"did:plc:klsh7edzj3jmxucibyjqstb3","publicKeyMultibase":"zQ3shP3NvazgSaEFpryzuyx8Q4MHho2KC2MNobAuQX3gdKAPW"}],"service":[{"id":"#atproto_pds","type":"AtprotoPersonalDataServer","serviceEndpoint":"https://milkcap.us-west.host.bsky.network"}]} """ - func request(parameters: Request) async throws -> Data { + func data(for: Request) async throws -> Data { Data(content.utf8) } } diff --git a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift index 2a3384b..af7b7de 100644 --- a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift +++ b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift @@ -32,15 +32,15 @@ extension ATResolve.HTTPMethod { } } -extension HTTPClient: HTTPSRequester { - public func request( - parameters: ATResolve.Request +extension HTTPClient: ResponseProviding { + public func data( + for request: ATResolve.Request ) async throws -> Data { var components = URLComponents() components.scheme = "https" - components.host = parameters.host - components.path = parameters.path - components.queryItems = parameters.queryItems.map({ pair in + components.host = request.host + components.path = request.path + components.queryItems = request.queryItems.map({ pair in URLQueryItem(name: pair.0, value: pair.1) }) @@ -48,10 +48,10 @@ extension HTTPClient: HTTPSRequester { throw URLError(.badURL) } - var request = HTTPClientRequest(url: url.absoluteString) - request.method = try parameters.method.convert + var httpRequest = HTTPClientRequest(url: url.absoluteString) + httpRequest.method = try request.method.convert - let response = try await execute(request, timeout: .seconds(30)) + let response = try await execute(httpRequest, timeout: .seconds(30)) var body = try await response.body.collect(upTo: 1024 * 1024) guard response.status == .ok else { From 763b044997059fb42694f1897cd0a3b85b690d11 Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Tue, 9 Dec 2025 13:34:33 -0800 Subject: [PATCH 7/7] remove AsyncHTTPClient package dependency --- Package.resolved | 191 +----------------- Package.swift | 16 +- .../AsyncHTTPClient+HTTPRequester.swift | 69 ------- 3 files changed, 8 insertions(+), 268 deletions(-) delete mode 100644 Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift diff --git a/Package.resolved b/Package.resolved index cd91f74..d968434 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,42 +1,6 @@ { - "originHash" : "9fdc3a6675148ed72144254c67fa4f97c5a54b4ac763b21e9e22db841a3b7bc8", + "originHash" : "96e0d45ae443bfd2b83137c042febcda2762013a24905f862d8634380a95d3c6", "pins" : [ - { - "identity" : "async-http-client", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/async-http-client.git", - "state" : { - "revision" : "b2faff932b956df50668241d14f1b42f7bae12b4", - "version" : "1.30.0" - } - }, - { - "identity" : "swift-algorithms", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-algorithms.git", - "state" : { - "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", - "version" : "1.2.1" - } - }, - { - "identity" : "swift-asn1", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-asn1.git", - "state" : { - "revision" : "40d25bbb2fc5b557a9aa8512210bded327c0f60d", - "version" : "1.5.0" - } - }, - { - "identity" : "swift-async-algorithms", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-async-algorithms.git", - "state" : { - "revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804", - "version" : "1.1.1" - } - }, { "identity" : "swift-async-dns-resolver", "kind" : "remoteSourceControl", @@ -45,159 +9,6 @@ "revision" : "08c07ff31a745ee5e522ac10132fb4949834d925", "version" : "0.4.0" } - }, - { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-certificates", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-certificates.git", - "state" : { - "revision" : "66a8512c4e7466582bab21e0e0c333f01974e5b6", - "version" : "1.16.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "6f70fa9eab24c1fd982af18c281c4525d05e3095", - "version" : "4.2.0" - } - }, - { - "identity" : "swift-distributed-tracing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-distributed-tracing.git", - "state" : { - "revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97", - "version" : "1.3.1" - } - }, - { - "identity" : "swift-http-structured-headers", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-structured-headers.git", - "state" : { - "revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b", - "version" : "1.6.0" - } - }, - { - "identity" : "swift-http-types", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-types.git", - "state" : { - "revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca", - "version" : "1.5.1" - } - }, - { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log.git", - "state" : { - "revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2", - "version" : "1.6.4" - } - }, - { - "identity" : "swift-nio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio.git", - "state" : { - "revision" : "3eea09220e07d34ace722221cbda90306f48c86c", - "version" : "2.90.1" - } - }, - { - "identity" : "swift-nio-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-extras.git", - "state" : { - "revision" : "7ee281d816fa8e5f3967a2c294035a318ea551c7", - "version" : "1.31.0" - } - }, - { - "identity" : "swift-nio-http2", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-http2.git", - "state" : { - "revision" : "c2ba4cfbb83f307c66f5a6df6bb43e3c88dfbf80", - "version" : "1.39.0" - } - }, - { - "identity" : "swift-nio-ssl", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-ssl.git", - "state" : { - "revision" : "173cc69a058623525a58ae6710e2f5727c663793", - "version" : "2.36.0" - } - }, - { - "identity" : "swift-nio-transport-services", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-transport-services.git", - "state" : { - "revision" : "60c3e187154421171721c1a38e800b390680fb5d", - "version" : "1.26.0" - } - }, - { - "identity" : "swift-numerics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-numerics.git", - "state" : { - "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", - "version" : "1.1.1" - } - }, - { - "identity" : "swift-service-context", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-service-context.git", - "state" : { - "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", - "version" : "1.2.1" - } - }, - { - "identity" : "swift-service-lifecycle", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/swift-service-lifecycle.git", - "state" : { - "revision" : "1de37290c0ab3c5a96028e0f02911b672fd42348", - "version" : "2.9.1" - } - }, - { - "identity" : "swift-system", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-system.git", - "state" : { - "revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db", - "version" : "1.6.3" - } } ], "version" : 3 diff --git a/Package.swift b/Package.swift index d98f657..8646a57 100644 --- a/Package.swift +++ b/Package.swift @@ -19,22 +19,20 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-async-dns-resolver.git", from: "0.4.0"), - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.30.0"), - .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0") ], targets: [ .target( name: "ATResolve", dependencies: [ - .product(name: "AsyncDNSResolver", package: "swift-async-dns-resolver"), - ]), + .product( + name: "AsyncDNSResolver", + package: "swift-async-dns-resolver" + ), + ] + ), .testTarget( name: "ATResolveTests", - dependencies: [ - "ATResolve", - .product(name: "AsyncHTTPClient", package: "async-http-client"), - .product(name: "NIOHTTP1", package: "swift-nio") - ] + dependencies: ["ATResolve"] ), ] ) diff --git a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift b/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift deleted file mode 100644 index af7b7de..0000000 --- a/Tests/ATResolveTests/AsyncHTTPClient+HTTPRequester.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// File.swift -// ATResolve -// -// Created by Mark @ Germ on 12/1/25. -// - -#if canImport(AsyncHTTPClient) -import ATResolve -import AsyncHTTPClient -//should be CoreFoundation, for Data type -import Foundation -import NIOHTTP1 -import NIOFoundationCompat - -extension ATResolve.HTTPMethod { - var convert: NIOHTTP1.HTTPMethod { - get throws { - switch rawValue { - case ATResolve.HTTPMethod.get.rawValue: - .GET - case ATResolve.HTTPMethod.post.rawValue: - .POST - case ATResolve.HTTPMethod.put.rawValue: - .PUT - case ATResolve.HTTPMethod.delete.rawValue: - .DELETE - default: - throw URLError(.badURL) - } - } - } -} - -extension HTTPClient: ResponseProviding { - public func data( - for request: ATResolve.Request - ) async throws -> Data { - var components = URLComponents() - components.scheme = "https" - components.host = request.host - components.path = request.path - components.queryItems = request.queryItems.map({ pair in - URLQueryItem(name: pair.0, value: pair.1) - }) - - guard let url = components.url else { - throw URLError(.badURL) - } - - var httpRequest = HTTPClientRequest(url: url.absoluteString) - httpRequest.method = try request.method.convert - - let response = try await execute(httpRequest, timeout: .seconds(30)) - var body = try await response.body.collect(upTo: 1024 * 1024) - - guard response.status == .ok else { - print("response:", response) - throw URLError(.badServerResponse) - } - let result = body.readData(length: body.readableBytes) - - guard let result else { - throw URLError(.badServerResponse) - } - return result - } -} -#endif