From 2b3e20d2d0adde1e290b1b14b93d629a8999691f Mon Sep 17 00:00:00 2001 From: Diana Ma Date: Thu, 30 Apr 2026 05:19:48 +0000 Subject: [PATCH 1/3] refactor connection --- .../GitHub.Client.Connection.swift | 160 ++++++++---------- 1 file changed, 71 insertions(+), 89 deletions(-) diff --git a/Sources/GitHubClient/GitHub.Client.Connection.swift b/Sources/GitHubClient/GitHub.Client.Connection.swift index 8e6f456..2dd2e17 100644 --- a/Sources/GitHubClient/GitHub.Client.Connection.swift +++ b/Sources/GitHubClient/GitHub.Client.Connection.swift @@ -22,55 +22,76 @@ extension GitHub.Client { } } } -extension GitHub.Client.Connection { - /// Run a GraphQL API request. - /// - /// The request will be charged to the user associated with the stored token. It is not - /// possible to run a GraphQL API request without a token. - @inlinable public func post( - query: String, - expecting _: T.Type = T.self, - with authorization: GitHub.ClientAuthorization - ) async throws -> GraphQL.Response - where T: JSONDecodable { - let request: HTTP.Client2.Request = .init( - headers: [ - ":method": "POST", +extension GitHub.Client.Connection { + @inlinable func fetch( + from endpoint: String, + with authorization: GitHub.ClientAuthorization, + method: String, + ) async throws -> JSON { + var endpoint: String = endpoint + var status: UInt? = nil + + following: + for _: Int in 0 ... 1 { + let request: HPACKHeaders = [ + ":method": method, ":scheme": "https", ":authority": self.http2.remote, - ":path": "/graphql", + ":path": endpoint, "authorization": authorization.header, - // GitHub will reject the API request if the user-agent is not set. "user-agent": self.agent, "accept": "application/vnd.github+json" - ], - body: self.http2.buffer(string: query) - ) + ] - /// GraphQL should never return redirects. - let response: HTTP.Client2.Facet = try await self.http2.fetch(request) + let response: HTTP.Client2.Facet = try await self.http2.fetch(request) - switch response.status { - case 200?: - let json: JSON = .init(utf8: response.body[...]) - return try json.decode() - - case 403?: - if let second: String = response.headers?["x-ratelimit-reset"].first, - let second: Int64 = .init(second) { - throw GitHub.Client.RateLimitError.init(until: .second(second)) - } else { - fallthrough + // TODO: support If-None-Match + switch response.status { + case 200?: + return JSON.init(utf8: response.body[...]) + + case 301?: + if let location: String = response.headers?["location"].first { + endpoint = .init(location.trimmingPrefix("https://\(self.http2.remote)")) + continue following + } + + case 403?: + if let second: String = response.headers?["x-ratelimit-reset"].first, + let second: Int64 = .init(second) { + throw GitHub.Client.RateLimitError.init( + until: .second(second) + ) + } + + case _: + break } - case _: - throw HTTP.StatusError.init(code: response.status) + status = response.status + break following } + + throw HTTP.StatusError.init(code: status) } } extension GitHub.Client.Connection { + /// Run a REST API request with the given credentials, following up to one redirect. + @inlinable public func get( + expecting _: Response.Type = Response.self, + from endpoint: String, + with authorization: GitHub.ClientAuthorization + ) async throws -> Response where Response: JSONDecodable { + let json: JSON = try await self.fetch( + from: endpoint, + with: authorization, + method: "GET" + ) + return try json.decode() + } + @discardableResult @inlinable public func post( to endpoint: String, @@ -135,62 +156,23 @@ extension GitHub.Client.Connection { return (status, try json.decode()) } } - - /// Run a REST API request with the given credentials, following up to one redirect. - @inlinable public func get( - expecting _: Response.Type = Response.self, - from endpoint: String, +} +extension GitHub.Client<()>.Connection { + /// Run a GraphQL API request. + /// + /// The request will be charged to the user associated with the stored token. It is not + /// possible to run a GraphQL API request without a token. + @inlinable public func post( + query: String, + expecting _: T.Type = T.self, with authorization: GitHub.ClientAuthorization - ) async throws -> Response - where Response: JSONDecodable { - var endpoint: String = endpoint - var status: UInt? = nil - - following: - for _: Int in 0 ... 1 { - let request: HPACKHeaders = [ - ":method": "GET", - ":scheme": "https", - ":authority": self.http2.remote, - ":path": endpoint, - - "authorization": authorization.header, - // GitHub will reject the API request if the user-agent is not set. - "user-agent": self.agent, - "accept": "application/vnd.github+json" - ] - - let response: HTTP.Client2.Facet = try await self.http2.fetch(request) - - // TODO: support If-None-Match - switch response.status { - case 200?: - let json: JSON = .init(utf8: response.body[...]) - return try json.decode() - - case 301?: - if let location: String = response.headers?["location"].first { - endpoint = .init(location.trimmingPrefix("https://\(self.http2.remote)")) - continue following - } - - case 403?: - if let second: String = response.headers?["x-ratelimit-reset"].first, - let second: Int64 = .init(second) { - throw GitHub.Client.RateLimitError.init( - until: .second(second) - ) - } - - case _: - break - } - - status = response.status - break following - } - - throw HTTP.StatusError.init(code: status) + ) async throws -> GraphQL.Response where T: JSONDecodable { + let json: JSON = try await self.fetch( + from: "/graphql", + with: authorization, + method: "POST" + ) + return try json.decode() } } extension GitHub.Client.Connection { From e921d6a8cecc96a09bcbe27153604aa2ede8dd54 Mon Sep 17 00:00:00 2001 From: Diana Ma Date: Thu, 30 Apr 2026 05:51:55 +0000 Subject: [PATCH 2/3] feedback Co-authored-by: Copilot --- .../GitHub.Client.Connection.swift | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Sources/GitHubClient/GitHub.Client.Connection.swift b/Sources/GitHubClient/GitHub.Client.Connection.swift index 2dd2e17..3208fce 100644 --- a/Sources/GitHubClient/GitHub.Client.Connection.swift +++ b/Sources/GitHubClient/GitHub.Client.Connection.swift @@ -1,7 +1,7 @@ public import GitHubAPI public import HTTPClient public import JSON -import NIOCore +public import NIOCore public import NIOHPACK public import UnixTime @@ -27,23 +27,27 @@ extension GitHub.Client.Connection { from endpoint: String, with authorization: GitHub.ClientAuthorization, method: String, + body: ByteBuffer? ) async throws -> JSON { var endpoint: String = endpoint var status: UInt? = nil following: for _: Int in 0 ... 1 { - let request: HPACKHeaders = [ - ":method": method, - ":scheme": "https", - ":authority": self.http2.remote, - ":path": endpoint, - - "authorization": authorization.header, - // GitHub will reject the API request if the user-agent is not set. - "user-agent": self.agent, - "accept": "application/vnd.github+json" - ] + let request: HTTP.Client2.Request = .init( + headers: [ + ":method": method, + ":scheme": "https", + ":authority": self.http2.remote, + ":path": endpoint, + + "authorization": authorization.header, + // GitHub will reject the API request if the user-agent is not set. + "user-agent": self.agent, + "accept": "application/vnd.github+json" + ], + body: body + ) let response: HTTP.Client2.Facet = try await self.http2.fetch(request) @@ -61,7 +65,7 @@ extension GitHub.Client.Connection { case 403?: if let second: String = response.headers?["x-ratelimit-reset"].first, let second: Int64 = .init(second) { - throw GitHub.Client.RateLimitError.init( + throw GitHub.Client.RateLimitError.init( until: .second(second) ) } @@ -87,7 +91,8 @@ extension GitHub.Client.Connection { let json: JSON = try await self.fetch( from: endpoint, with: authorization, - method: "GET" + method: "GET", + body: nil ) return try json.decode() } @@ -170,7 +175,8 @@ extension GitHub.Client<()>.Connection { let json: JSON = try await self.fetch( from: "/graphql", with: authorization, - method: "POST" + method: "POST", + body: self.http2.buffer(string: query), ) return try json.decode() } From 6bd1628945eed11c939669f867107ce58160d54f Mon Sep 17 00:00:00 2001 From: Diana Ma Date: Thu, 30 Apr 2026 06:17:33 +0000 Subject: [PATCH 3/3] status --- Sources/GitHubClient/GitHub.Client.Connection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/GitHubClient/GitHub.Client.Connection.swift b/Sources/GitHubClient/GitHub.Client.Connection.swift index 3208fce..419e892 100644 --- a/Sources/GitHubClient/GitHub.Client.Connection.swift +++ b/Sources/GitHubClient/GitHub.Client.Connection.swift @@ -50,6 +50,7 @@ extension GitHub.Client.Connection { ) let response: HTTP.Client2.Facet = try await self.http2.fetch(request) + status = response.status // TODO: support If-None-Match switch response.status { @@ -74,7 +75,6 @@ extension GitHub.Client.Connection { break } - status = response.status break following }