Skip to content

Instantly share code, notes, and snippets.

@ptoffy
Last active August 22, 2025 07:50
Show Gist options
  • Select an option

  • Save ptoffy/61036564e6239817d57a30cbedf19dbc to your computer and use it in GitHub Desktop.

Select an option

Save ptoffy/61036564e6239817d57a30cbedf19dbc to your computer and use it in GitHub Desktop.
GoogleCloudKit extension example
app.googleCloud.credentials = try GoogleCloudCredentialsConfiguration(projectId: "...", credentialsFile: "....json")
app.googleCloud.storage.configuration = .default()
app.googleCloud.service.configuration = .init(
scope: [.cloudPlatform], serviceAccount: "default", project: "afa-markets-vapor", region: Environment.GCP.region
)
import GoogleCloudKit
extension GoogleCloudAPIRequest {
public func withToken<GoogleCloudModel>(
_ closure: @escaping (OAuthAccessToken) async throws -> GoogleCloudModel
) async throws -> GoogleCloudModel {
guard
let token = currentToken,
let created = tokenCreatedTime,
refreshableToken.isFresh(token: token, created: created)
else {
let newToken = try await refreshableToken.refresh().get()
self.currentToken = newToken
self.tokenCreatedTime = Date()
return try await closure(newToken)
}
return try await closure(token)
}
}
import AsyncHTTPClient
import GoogleCloud
struct GoogleCloudServiceClient: Sendable {
let request: GoogleCloudServiceRequest
init(
credentials: GoogleCloudCredentialsConfiguration,
config: GoogleCloudServiceConfiguration,
httpClient: HTTPClient,
logger: Logger,
eventLoop: any EventLoop
) throws {
let refreshableToken = OAuthCredentialLoader.getRefreshableToken(
credentials: credentials,
withConfig: config,
andClient: httpClient,
eventLoop: eventLoop
)
guard
let projectId = ProcessInfo.processInfo.environment["PROJECT_ID"] ?? (refreshableToken as? OAuthServiceAccount)?.credentials
.projectId ?? config.project ?? credentials.project
else {
throw GoogleCloudServiceError.projectIdMissing
}
self.request = .init(
httpClient: httpClient,
oauth: refreshableToken,
project: projectId,
region: config.region,
logger: logger
)
}
func getService(named name: String) async throws -> ServiceModel {
try await request.send(.GET, to: "https://run.googleapis.com/v2/\(name)")
}
}
@preconcurrency import GoogleCloud
// Here I've added what I need for the service I'm using but this is quite dynamic
struct GoogleCloudServiceConfiguration: GoogleCloudAPIConfiguration, Sendable {
var scope: [any GoogleCloudAPIScope]
let serviceAccount: String
let project: String?
let subscription: String? = nil
let region: String
init(scope: [GoogleCloudServiceScope], serviceAccount: String, project: String, region: String) {
self.scope = scope
self.serviceAccount = serviceAccount
self.project = project
self.region = region
}
}
enum GoogleCloudServiceScope: GoogleCloudAPIScope {
case cloudPlatform
var value: String {
switch self {
// Check out your scopes at https://developers.google.com/identity/protocols/oauth2/scopes
case .cloudPlatform: "https://www.googleapis.com/auth/cloud-platform"
}
}
}
import GoogleCloudKit
final class GoogleCloud<Service>Request: GoogleCloudAPIRequest, @unchecked Sendable {
let refreshableToken: any OAuthRefreshable
let project: String
let region: String
let httpClient: HTTPClient
let responseDecoder: JSONDecoder
var currentToken: Core.OAuthAccessToken?
var tokenCreatedTime: Date?
let logger: Logger
init(
httpClient: HTTPClient,
oauth: some OAuthRefreshable,
project: String,
region: String,
logger: Logger
) {
self.refreshableToken = oauth
self.httpClient = httpClient
self.project = project
self.region = region
self.logger = logger
self.responseDecoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
self.responseDecoder.dateDecodingStrategy = .formatted(dateFormatter)
}
public func send<Result: GoogleCloudModel>(
_ method: HTTPMethod,
to path: String,
headers: HTTPHeaders = [:],
query: String = "",
body: HTTPClientRequest.Body? = nil
) async throws -> Result {
try await withToken { token in
var headers = headers
headers.add(name: .authorization, value: "Bearer \(token.accessToken)")
let url = "\(path)?\(query)"
var request = HTTPClientRequest(url: url)
request.headers = headers
request.method = method
if let body {
request.body = body
}
let response = try await self.httpClient.execute(request, timeout: .seconds(60))
let responseBody = try await response.body.collect(upTo: 1024 * 1024)
guard 200...299 ~= response.status.code else {
self.logger.error("Sent \(method) request to \(url), headers: \(headers))")
throw Abort(.init(statusCode: Int(response.status.code)), reason: "GCP API Error: \(String(buffer: responseBody))")
}
return try self.responseDecoder.decode(Result.self, from: responseBody)
}
}
}
import GoogleCloudKit
// https://cloud.google.com/run/docs/reference/rest
struct ServiceModel: GoogleCloudModel, Codable {}
import GoogleCloud
import Vapor
extension Application.GoogleCloudPlatform {
private struct CloudServiceAPIKey: StorageKey {
typealias Value = GoogleCloudServiceAPI
}
private struct CloudServiceConfigurationKey: StorageKey {
typealias Value = GoogleCloudServiceConfiguration
}
var service: GoogleCloudServiceAPI {
get {
if let existing = self.application.storage[CloudServiceAPIKey.self] {
return existing
} else {
let new = GoogleCloudServiceAPI(application: self.application, eventLoop: self.application.eventLoopGroup.next())
self.application.storage[CloudServiceAPIKey.self] = new
return new
}
}
nonmutating set {
self.application.storage[CloudServiceAPIKey.self] = newValue
}
}
struct GoogleCloudServiceAPI: Sendable {
let application: Application
let eventLoop: any EventLoop
var client: GoogleCloudServiceClient {
do {
return try GoogleCloudServiceClient(
credentials: application.googleCloud.credentials,
config: configuration,
httpClient: application.http.client.shared,
logger: application.logger,
eventLoop: eventLoop
)
} catch {
fatalError("\(error.localizedDescription)")
}
}
var configuration: GoogleCloudServiceConfiguration {
get {
guard let configuration = application.storage[CloudServiceConfigurationKey.self] else {
fatalError("Cloud service configuration has not been set. Use app.googleCloud.service.configuration = ...")
}
return configuration
}
nonmutating set {
application.storage[CloudServiceConfigurationKey.self] = newValue
}
}
}
}
extension Request {
private struct GoogleCloudServiceKey: StorageKey {
typealias Value = GoogleCloudServiceClient
}
var gcServiceClient: GoogleCloudServiceClient {
if let existing = application.storage[GoogleCloudServiceKey.self] {
return existing
} else {
let new = Application.GoogleCloudPlatform.GoogleCloudServiceAPI(application: self.application, eventLoop: self.eventLoop)
.client
application.storage[GoogleCloudServiceKey.self] = new
return new
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment