Skip to content

Commit 980d8df

Browse files
Merge pull request #3568 from SwiftPackageIndex/issue-3469-dependency-transition-12
Issue 3469 dependency transition 12
2 parents 7df64b8 + fcd9085 commit 980d8df

14 files changed

+111
-38
lines changed

Sources/App/Core/AppEnvironment.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,12 @@ struct AppEnvironment: Sendable {
3636
var gitlabPipelineLimit: @Sendable () -> Int
3737
var hideStagingBanner: @Sendable () -> Bool
3838
var maintenanceMessage: @Sendable () -> String?
39-
var httpClient: @Sendable () -> Client
4039
var loadSPIManifest: @Sendable (String) -> SPIManifest.Manifest?
4140
var logger: @Sendable () -> Logger
4241
var metricsPushGatewayUrl: @Sendable () -> String?
4342
var plausibleBackendReportingSiteID: @Sendable () -> String?
4443
var processingBuildBacklog: @Sendable () -> Bool
4544
var runnerIds: @Sendable () -> [String]
46-
var setHTTPClient: @Sendable (Client) -> Void
4745
var setLogger: @Sendable (Logger) -> Void
4846
var shell: Shell
4947
var siteURL: @Sendable () -> String
@@ -65,7 +63,6 @@ struct AppEnvironment: Sendable {
6563

6664

6765
extension AppEnvironment {
68-
nonisolated(unsafe) static var httpClient: Client!
6966
nonisolated(unsafe) static var logger: Logger!
7067

7168
static let live = AppEnvironment(
@@ -96,7 +93,6 @@ extension AppEnvironment {
9693
maintenanceMessage: {
9794
Environment.get("MAINTENANCE_MESSAGE").flatMap(\.trimmed)
9895
},
99-
httpClient: { httpClient },
10096
loadSPIManifest: { path in SPIManifest.Manifest.load(in: path) },
10197
logger: { logger },
10298
metricsPushGatewayUrl: { Environment.get("METRICS_PUSHGATEWAY_URL") },
@@ -110,7 +106,6 @@ extension AppEnvironment {
110106
.flatMap { try? JSONDecoder().decode([String].self, from: $0) }
111107
?? []
112108
},
113-
setHTTPClient: { client in Self.httpClient = client },
114109
setLogger: { logger in Self.logger = logger },
115110
shell: .live,
116111
siteURL: { Environment.get("SITE_URL") ?? "http://localhost:8080" },

Sources/App/Core/Dependencies/EnvironmentClient.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ struct EnvironmentClient {
4242
var currentReferenceCache: @Sendable () -> CurrentReferenceCache?
4343
var dbId: @Sendable () -> String?
4444
var mastodonCredentials: @Sendable () -> Mastodon.Credentials?
45-
#warning("drop client parameter and move this to httpClient")
46-
var mastodonPost: @Sendable (_ client: Client, _ message: String) async throws -> Void
4745
var random: @Sendable (_ range: ClosedRange<Double>) -> Double = { XCTFail("random"); return Double.random(in: $0) }
4846

4947
enum FailureMode: String {
@@ -110,7 +108,6 @@ extension EnvironmentClient: DependencyKey {
110108
Environment.get("MASTODON_ACCESS_TOKEN")
111109
.map(Mastodon.Credentials.init(accessToken:))
112110
},
113-
mastodonPost: { client, message in try await Mastodon.post(client: client, message: message) },
114111
random: { range in Double.random(in: range) },
115112
shouldFail: { failureMode in
116113
let shouldFail = Environment.get("FAILURE_MODE")

Sources/App/Core/Dependencies/HTTPClient.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ struct HTTPClient {
2323
typealias Request = Vapor.HTTPClient.Request
2424
typealias Response = Vapor.HTTPClient.Response
2525

26-
var post: @Sendable (_ url: String, _ headers: HTTPHeaders, _ body: Data) async throws -> Response
26+
var post: @Sendable (_ url: String, _ headers: HTTPHeaders, _ body: Data?) async throws -> Response
2727
var fetchDocumentation: @Sendable (_ url: URI) async throws -> Response
2828
var fetchHTTPStatusCode: @Sendable (_ url: String) async throws -> HTTPStatus
29+
var mastodonPost: @Sendable (_ message: String) async throws -> Void
2930
var postPlausibleEvent: @Sendable (_ kind: Plausible.Event.Kind, _ path: Plausible.Path, _ user: User?) async throws -> Void
3031
}
3132

3233
extension HTTPClient: DependencyKey {
3334
static var liveValue: HTTPClient {
3435
.init(
3536
post: { url, headers, body in
36-
let req = try Request(url: url, method: .POST, headers: headers, body: .data(body))
37+
let req = try Request(url: url, method: .POST, headers: headers, body: body.map({.data($0)}))
3738
return try await Vapor.HTTPClient.shared.execute(request: req).get()
3839
},
3940
fetchDocumentation: { url in
@@ -53,6 +54,7 @@ extension HTTPClient: DependencyKey {
5354
try await client.shutdown()
5455
}
5556
},
57+
mastodonPost: { message in try await Mastodon.post(message: message) },
5658
postPlausibleEvent: { kind, path, user in
5759
try await Plausible.postEvent(kind: kind, path: path, user: user)
5860
}

Sources/App/Core/Mastodon.swift

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,45 @@ import Vapor
1818

1919
enum Mastodon {
2020

21-
private static let instance = "mas.to"
22-
private static let apiURL = "https://\(instance)/api/v1/statuses"
21+
private static let apiHost = "mas.to"
22+
private static let apiPath = "/api/v1/statuses"
2323
static let postMaxLength = 490 // 500, leaving some buffer for unicode accounting oddities
2424

2525
struct Credentials {
2626
var accessToken: String
2727
}
2828

29-
// NB: _testEncodedURL is a callback that exists purely to be able to regression test the encoded value
30-
static func post(client: Client, message: String, _testEncodedURL: (String) -> Void = { _ in }) async throws {
29+
static func apiURL(with message: String) throws -> String {
30+
var components = URLComponents()
31+
components.scheme = "https"
32+
components.host = apiHost
33+
components.path = apiPath
34+
components.queryItems = [URLQueryItem(name: "status", value: message)]
35+
guard let url = components.string else {
36+
throw Social.Error.invalidURL
37+
}
38+
return url
39+
}
40+
41+
static func post(message: String) async throws {
3142
@Dependency(\.environment) var environment
43+
@Dependency(\.httpClient) var httpClient
44+
@Dependency(\.uuid) var uuid
3245
guard let credentials = environment.mastodonCredentials() else {
3346
throw Social.Error.missingCredentials
3447
}
3548

3649
let headers = HTTPHeaders([
3750
("Authorization", "Bearer \(credentials.accessToken)"),
38-
("Idempotency-Key", UUID().uuidString),
51+
("Idempotency-Key", uuid().uuidString),
3952
])
4053

4154
struct Query: Encodable {
4255
var status: String
4356
}
4457

45-
let res = try await client.post(URI(string: apiURL), headers: headers) { req in
46-
try req.query.encode(Query(status: message))
47-
_testEncodedURL(req.url.string)
48-
}
58+
let res = try await httpClient.post(url: apiURL(with: message), headers: headers, body: nil)
59+
4960
guard res.status == .ok else {
5061
throw Social.Error.requestFailed(res.status, res.body?.asString() ?? "")
5162
}

Sources/App/Core/Social.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ enum Social {
2222

2323
enum Error: LocalizedError {
2424
case invalidMessage
25+
case invalidURL
2526
case missingCredentials
2627
case postingDisabled
2728
case requestFailed(HTTPStatus, String)
@@ -98,14 +99,15 @@ enum Social {
9899
package: Joined<Package, Repository>,
99100
version: Version) async throws {
100101
@Dependency(\.environment) var environment
102+
@Dependency(\.httpClient) var httpClient
101103
guard environment.allowSocialPosts() else { throw Error.postingDisabled }
102104
guard let message = firehoseMessage(package: package,
103105
version: version,
104106
maxLength: postMaxLength) else {
105107
throw Error.invalidMessage
106108
}
107109
// Ignore errors from here for now to keep concurrency simpler
108-
async let _ = try? await environment.mastodonPost(client: client, message: message)
110+
async let _ = try? await httpClient.mastodonPost(message: message)
109111
}
110112

111113
static func postToFirehose(client: Client,

Sources/App/configure.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public func configure(_ app: Application) async throws -> String {
2727

2828
app.logger.component = "server"
2929
Current.setLogger(app.logger)
30-
Current.setHTTPClient(app.client)
3130

3231
// It will be tempting to uncomment/re-add these lines in the future. We should not enable
3332
// server-side compression as long as we pass requests through Cloudflare, which compresses

Tests/AppTests/AnalyzeErrorTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ final class AnalyzeErrorTests: AppTestCase {
104104
withDependencies {
105105
$0.date.now = .t0
106106
$0.environment.allowSocialPosts = { true }
107-
$0.environment.mastodonPost = { @Sendable [socialPosts = self.socialPosts] _, message in
107+
$0.httpClient.mastodonPost = { @Sendable [socialPosts = self.socialPosts] message in
108108
socialPosts.withValue { $0.append(message) }
109109
}
110110
} operation: {

Tests/AppTests/AnalyzerTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class AnalyzerTests: AppTestCase {
3636
try await withDependencies {
3737
$0.date.now = .now
3838
$0.environment.allowSocialPosts = { true }
39-
$0.environment.mastodonPost = { @Sendable _, _ in }
39+
$0.httpClient.mastodonPost = { @Sendable _ in }
4040
} operation: {
4141
// setup
4242
let urls = ["https://github.com/foo/1", "https://github.com/foo/2"]
@@ -219,7 +219,7 @@ class AnalyzerTests: AppTestCase {
219219
try await withDependencies {
220220
$0.date.now = .now
221221
$0.environment.allowSocialPosts = { true }
222-
$0.environment.mastodonPost = { @Sendable _, _ in }
222+
$0.httpClient.mastodonPost = { @Sendable _ in }
223223
} operation: {
224224
// setup
225225
let pkgId = UUID()

Tests/AppTests/LiveTests.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
@testable import App
16+
17+
import Dependencies
18+
import XCTVapor
19+
20+
21+
class LiveTests: XCTestCase {
22+
23+
func test_Mastodon_post() async throws {
24+
// Only run this test manually to confirm posting works
25+
try XCTSkipIf(true)
26+
27+
try await withDependencies {
28+
$0.environment.mastodonCredentials = { .dev }
29+
$0.httpClient = .liveValue
30+
$0.uuid = .init(UUID.init)
31+
} operation: {
32+
let message = Social.versionUpdateMessage(
33+
packageName: "packageName",
34+
repositoryOwnerName: "owner",
35+
url: "http://localhost:8080/owner/SomePackage",
36+
version: .init(2, 6, 4),
37+
summary: "Testing, testing αβγ ÄÖÜß 🔤 ✅",
38+
maxLength: Social.postMaxLength
39+
)
40+
41+
try await Mastodon.post(message: message)
42+
}
43+
}
44+
45+
}
46+
47+
48+
extension Mastodon.Credentials {
49+
// https://mas.to/@spi_test
50+
static var dev: Self? {
51+
guard let accessToken = Environment.get("DEV_MASTODON_ACCESS_TOKEN") else { return nil }
52+
return .init(accessToken: accessToken)
53+
}
54+
}

Tests/AppTests/MastodonTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ final class MastodonTests: AppTestCase {
2525
let message = QueueIsolated<String?>(nil)
2626
try await withDependencies {
2727
$0.environment.allowSocialPosts = { true }
28-
$0.environment.mastodonPost = { @Sendable _, msg in
28+
$0.httpClient.mastodonPost = { @Sendable msg in
2929
if message.value == nil {
3030
message.setValue(msg)
3131
} else {

0 commit comments

Comments
 (0)