From 2068605210c5504ec5dc95454ced3ebb2396ef86 Mon Sep 17 00:00:00 2001 From: Mahdi Bahrami Date: Sat, 14 Dec 2024 21:26:05 +0330 Subject: [PATCH] Better swift release message (#273) --- .vscode/settings.json | 3 +- .../CachesService/CachesStorage.swift | 43 +++++++++++++++++++ .../DefaultSwiftReleasesService.swift | 6 ++- Sources/Penny/SwiftReleasesChecker.swift | 23 ++++++++-- Tests/PennyTests/Fake/TestData.swift | 15 ++++--- .../Tests/GatewayProcessingTests.swift | 4 +- 6 files changed, 81 insertions(+), 13 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fd99883d..6d6adc71 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,8 +27,7 @@ "yaml.maxItemsComputed": 1000000, "yaml.format.enable": true, "editor.rulers": [120], - "editor.minimap.enabled": false, - "editor.wordWrapColumn": 100, + "editor.wordWrapColumn": 120, "githubPullRequests.pullBranch": "never", "errorLens.statusBarColorsEnabled": true, "errorLens.statusBarIconsEnabled": true, diff --git a/Sources/Penny/Services/CachesService/CachesStorage.swift b/Sources/Penny/Services/CachesService/CachesStorage.swift index 16bf73d3..65af23d4 100644 --- a/Sources/Penny/Services/CachesService/CachesStorage.swift +++ b/Sources/Penny/Services/CachesService/CachesStorage.swift @@ -17,6 +17,27 @@ struct CachesStorage: Sendable, Codable { var swiftReleasesData: SwiftReleasesChecker.Storage? var autoFaqsResponseRateLimiter: DefaultAutoFaqsService.ResponseRateLimiter? + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.reactionCacheData = container.decodeIfPresentWithLogging( + ReactionCache.Storage.self, + forKey: .reactionCacheData + ) + self.evolutionCheckerData = container.decodeIfPresentWithLogging( + EvolutionChecker.Storage.self, + forKey: .evolutionCheckerData + ) + self.soCheckerData = container.decodeIfPresentWithLogging(SOChecker.Storage.self, forKey: .soCheckerData) + self.swiftReleasesData = container.decodeIfPresentWithLogging( + SwiftReleasesChecker.Storage.self, + forKey: .swiftReleasesData + ) + self.autoFaqsResponseRateLimiter = container.decodeIfPresentWithLogging( + DefaultAutoFaqsService.ResponseRateLimiter.self, + forKey: .autoFaqsResponseRateLimiter + ) + } + init() {} static func makeFromCachedData(context: Context) async -> CachesStorage { @@ -75,3 +96,25 @@ struct CachesStorage: Sendable, Codable { ) } } + +extension KeyedDecodingContainer { + fileprivate func decodeIfPresentWithLogging( + _ type: T.Type, + forKey key: KeyedDecodingContainer.Key + ) -> T? where T: Decodable { + do { + let value = try self.decodeIfPresent(type, forKey: key) + return value + } catch { + Logger(label: "CachesStorage").warning( + "Failed to decode a cached value", + metadata: [ + "error": .string(String(reflecting: error)), + "key": .string(String(describing: key)), + "type": .string(Swift._typeName(type)), + ] + ) + return nil + } + } +} diff --git a/Sources/Penny/Services/SwiftReleasesService/DefaultSwiftReleasesService.swift b/Sources/Penny/Services/SwiftReleasesService/DefaultSwiftReleasesService.swift index 96764f89..401559ba 100644 --- a/Sources/Penny/Services/SwiftReleasesService/DefaultSwiftReleasesService.swift +++ b/Sources/Penny/Services/SwiftReleasesService/DefaultSwiftReleasesService.swift @@ -11,7 +11,11 @@ import Foundation struct DefaultSwiftReleasesService: SwiftReleasesService { let httpClient: HTTPClient let logger = Logger(label: "DefaultSwiftReleasesService") - let decoder = JSONDecoder() + let decoder: JSONDecoder = { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return decoder + }() func listReleases() async throws -> [SwiftOrgRelease] { let url = "https://www.swift.org/api/v1/install/releases.json" diff --git a/Sources/Penny/SwiftReleasesChecker.swift b/Sources/Penny/SwiftReleasesChecker.swift index 568157b3..1e783027 100644 --- a/Sources/Penny/SwiftReleasesChecker.swift +++ b/Sources/Penny/SwiftReleasesChecker.swift @@ -50,15 +50,18 @@ actor SwiftReleasesChecker: Service { self.storage.currentReleases = releases for release in newReleases { - let image = - "https://opengraph.githubassets.com/\(UUID().uuidString)/swiftlang/swift/releases/tag/\(release.tag)" + /// swiftlang's GitHub logo aka the Swift logo + let image = "https://avatars.githubusercontent.com/u/42816656" await discordService.sendMessage( channelId: Constants.Channels.news.id, payload: .init(embeds: [ .init( title: "Swift \(release.stableName) Release".unicodesPrefix(256), + description: """ + \((release.xcodeRelease == true) ? "Available on \(release.xcode)" : "Doesn't come with a dedicated Xcode release") + """, url: "https://github.com/swiftlang/swift/releases/tag/\(release.tag)", - color: .cyan, + color: .orange, image: .init(url: .exact(image)) ) ]) @@ -75,9 +78,11 @@ actor SwiftReleasesChecker: Service { } } -struct SwiftOrgRelease: Codable, Hashable { +struct SwiftOrgRelease: Codable { let name: String let tag: String + let xcode: String + let xcodeRelease: Bool? var stableName: String { let components = self.name.split( @@ -91,3 +96,13 @@ struct SwiftOrgRelease: Codable, Hashable { } } } + +extension SwiftOrgRelease: Hashable { + static func == (lhs: SwiftOrgRelease, rhs: SwiftOrgRelease) -> Bool { + lhs.tag == rhs.tag + } + + func hash(into hasher: inout Hasher) { + hasher.combine(self.tag) + } +} diff --git a/Tests/PennyTests/Fake/TestData.swift b/Tests/PennyTests/Fake/TestData.swift index 292c719c..8d89e870 100644 --- a/Tests/PennyTests/Fake/TestData.swift +++ b/Tests/PennyTests/Fake/TestData.swift @@ -13,8 +13,6 @@ import Foundation enum TestData { - private static let decoder = JSONDecoder() - private static func resource(named name: String) -> Data { let fileManager = FileManager.default let currentDirectory = fileManager.currentDirectoryPath @@ -27,9 +25,15 @@ enum TestData { return data } - private static func resource(named name: String, as: D.Type = D.self) -> D { + private static func resource( + named name: String, + keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, + as: D.Type = D.self + ) -> D { let data = resource(named: name) - return try! JSONDecoder().decode(D.self, from: data) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = keyDecodingStrategy + return try! decoder.decode(D.self, from: data) } static let vaporGuild = resource( @@ -54,6 +58,7 @@ enum TestData { ).items static let swiftReleases = TestData.resource( named: "swiftReleases.json", + keyDecodingStrategy: .convertFromSnakeCase, as: [SwiftOrgRelease].self ) static let swiftReleasesUpdated = TestData.resource( @@ -76,7 +81,7 @@ enum TestData { static func decodedFor(gatewayEventKey key: String) -> Gateway.Event { let data = gatewayEvents[key]! - let decoded = try! decoder.decode(Gateway.Event.self, from: data) + let decoded = try! JSONDecoder().decode(Gateway.Event.self, from: data) return decoded } diff --git a/Tests/PennyTests/Tests/GatewayProcessingTests.swift b/Tests/PennyTests/Tests/GatewayProcessingTests.swift index 47d09c84..a7eda8ee 100644 --- a/Tests/PennyTests/Tests/GatewayProcessingTests.swift +++ b/Tests/PennyTests/Tests/GatewayProcessingTests.swift @@ -525,7 +525,9 @@ extension SerializationNamespace.GatewayProcessingTests { let _message = await responseStorage.awaitResponse(at: endpoint).value let message = try #require(_message as? Payloads.CreateMessage, "\(_message)") - #expect(message.embeds?.first?.title == "Swift 6.0.1 Release") + let embed = try #require(message.embeds?.first) + #expect(embed.title == "Swift 6.0.1 Release") + #expect(embed.description == "Doesn't come with a dedicated Xcode release") /// No more messages should be sent let _newMessage = await responseStorage.awaitResponse(