diff --git a/Package.resolved b/Package.resolved index edb24aa5..d4658fe1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,22 @@ { - "originHash" : "531e10b955219c0de91ada74260f59bff8033189f1a9f9f78b199480c61f466a", + "originHash" : "b623c5be535be5b61f96545e9a4aaa177e3f131cfbd5a4270c6abdce87419cc3", "pins" : [ { "identity" : "async-http-client", "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/async-http-client", "state" : { - "revision" : "0ae99db85b2b9d1e79b362bd31fd1ffe492f7c47", - "version" : "1.21.2" + "revision" : "2119f0d9cc1b334e25447fe43d3693c0e60e6234", + "version" : "1.24.0" + } + }, + { + "identity" : "openapikit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattpolzin/OpenAPIKit", + "state" : { + "revision" : "749ceba9a6e91d40081f27996fd91fd3de9a8dfe", + "version" : "3.4.0" } }, { @@ -24,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "46989693916f56d1186bd59ac15124caef896560", - "version" : "1.3.1" + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" } }, { @@ -42,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", - "version" : "1.1.0" + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" } }, { @@ -51,14 +60,14 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-docc-plugin", "state" : { - "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", - "version" : "1.3.0" + "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", + "version" : "1.4.3" } }, { "identity" : "swift-docc-symbolkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-docc-symbolkit", + "location" : "https://github.com/swiftlang/swift-docc-symbolkit", "state" : { "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", "version" : "1.0.0" @@ -69,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-types", "state" : { - "revision" : "12358d55a3824bd5fed310b999ea8cf83a9a1a65", - "version" : "1.0.3" + "revision" : "ef18d829e8b92d731ad27bb81583edd2094d1ce3", + "version" : "1.3.1" } }, { @@ -78,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", - "version" : "1.5.4" + "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version" : "1.6.2" } }, { @@ -87,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "fc63f0cf4e55a4597407a9fc95b16a2bc44b4982", - "version" : "2.64.0" + "revision" : "ba72f31e11275fc5bf060c966cf6c1f36842a291", + "version" : "2.79.0" } }, { @@ -96,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "a3b640d7dc567225db7c94386a6e71aded1bfa63", - "version" : "1.22.0" + "revision" : "2e9746cfc57554f70b650b021b6ae4738abef3e6", + "version" : "1.24.1" } }, { @@ -105,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "0904bf0feb5122b7e5c3f15db7df0eabe623dd87", - "version" : "1.30.0" + "revision" : "170f4ca06b6a9c57b811293cebcb96e81b661310", + "version" : "1.35.0" } }, { @@ -114,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "7c381eb6083542b124a6c18fae742f55001dc2b5", - "version" : "2.26.0" + "revision" : "c7e95421334b1068490b5d41314a50e70bab23d1", + "version" : "2.29.0" } }, { @@ -123,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce", - "version" : "1.20.1" + "revision" : "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", + "version" : "1.23.0" } }, { @@ -136,13 +145,40 @@ "version" : "1.0.2" } }, + { + "identity" : "swift-openapi-async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-openapi-async-http-client", + "state" : { + "revision" : "915aeff168625b0f88a5810ee7ab8e9c00af671b", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-openapi-generator", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-openapi-generator", + "state" : { + "revision" : "84b693f9d0559dc488e691edb4837bafbce2aaea", + "version" : "1.7.0" + } + }, + { + "identity" : "swift-openapi-runtime", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-openapi-runtime", + "state" : { + "revision" : "23146bc8710ac5e57abb693113f02dc274cf39b6", + "version" : "1.8.0" + } + }, { "identity" : "swift-system", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-system.git", "state" : { - "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496", - "version" : "1.2.1" + "revision" : "c8a44d836fe7913603e246acab7c528c2e780168", + "version" : "1.4.0" } }, { @@ -162,6 +198,15 @@ "revision" : "c7ddb09c3381cff833106345721fc314dba49e20", "version" : "0.49.18" } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams", + "state" : { + "revision" : "3036ba9d69cf1fd04d433527bc339dc0dc75433d", + "version" : "5.1.3" + } } ], "version" : 3 diff --git a/Package.swift b/Package.swift index 71bed523..0b3ed218 100644 --- a/Package.swift +++ b/Package.swift @@ -15,10 +15,13 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), - .package(url: "https://github.com/swift-server/async-http-client", from: "1.21.2"), - .package(url: "https://github.com/apple/swift-nio.git", from: "2.64.0"), + .package(url: "https://github.com/swift-server/async-http-client", from: "1.24.0"), + .package(url: "https://github.com/swift-server/swift-openapi-async-http-client", from: "1.1.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.79.0"), .package(url: "https://github.com/apple/swift-tools-support-core.git", from: "0.7.2"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), // This dependency provides the correct version of the formatter so that you can run `swift run swiftformat Package.swift Plugins/ Sources/ Tests/` .package(url: "https://github.com/nicklockwood/SwiftFormat", exact: "0.49.18"), ], @@ -38,6 +41,11 @@ let package = Package( dependencies: [ .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), + .product(name: "OpenAPIAsyncHTTPClient", package: "swift-openapi-async-http-client"), + ], + plugins: [ + .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator"), ] ), .target( diff --git a/Sources/LinuxPlatform/Linux.swift b/Sources/LinuxPlatform/Linux.swift index 824786f2..ee6118c0 100644 --- a/Sources/LinuxPlatform/Linux.swift +++ b/Sources/LinuxPlatform/Linux.swift @@ -69,7 +69,7 @@ public struct Linux: Platform { You can install the ca-certificates package on your system to fix this. """ - throw Error(message: msg) + throw SwiftlyError(message: msg) } } @@ -258,7 +258,7 @@ public struct Linux: Platform { } msg += "\n" + Self.skipVerificationMessage - throw Error(message: msg) + throw SwiftlyError(message: msg) } // Import the latest swift keys, but only once per session, which will help with the performance in tests @@ -270,7 +270,7 @@ public struct Linux: Platform { } guard let url = URL(string: "https://www.swift.org/keys/all-keys.asc") else { - throw Error(message: "malformed URL to the swift gpg keys") + throw SwiftlyError(message: "malformed URL to the swift gpg keys") } try await httpClient.downloadFile(url: url, to: tmpFile) @@ -329,7 +329,7 @@ public struct Linux: Platform { public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws { guard tmpFile.fileExists() else { - throw Error(message: "\(tmpFile) doesn't exist") + throw SwiftlyError(message: "\(tmpFile) doesn't exist") } if !self.swiftlyToolchainsDir.fileExists() { @@ -361,7 +361,7 @@ public struct Linux: Platform { public func extractSwiftlyAndInstall(from archive: URL) throws { guard archive.fileExists() else { - throw Error(message: "\(archive) doesn't exist") + throw SwiftlyError(message: "\(archive) doesn't exist") } let tmpDir = self.getTempFilePath() @@ -414,7 +414,7 @@ public struct Linux: Platform { do { try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: !verbose) } catch { - throw Error(message: "Signature verification failed: \(error).") + throw SwiftlyError(message: "Signature verification failed: \(error).") } } @@ -471,7 +471,7 @@ public struct Linux: Platform { guard let releaseFile = releaseFile else { let message = "Unable to detect the type of Linux OS and the release" if disableConfirmation { - throw Error(message: message) + throw SwiftlyError(message: message) } else { print(message) } @@ -498,7 +498,7 @@ public struct Linux: Platform { guard let id, let versionID else { let message = "Unable to find release information from file \(releaseFile)" if disableConfirmation { - throw Error(message: message) + throw SwiftlyError(message: message) } else { print(message) } @@ -509,7 +509,7 @@ public struct Linux: Platform { guard versionID == "2" else { let message = "Unsupported version of Amazon Linux" if disableConfirmation { - throw Error(message: message) + throw SwiftlyError(message: message) } else { print(message) } @@ -521,7 +521,7 @@ public struct Linux: Platform { guard versionID.hasPrefix("9") else { let message = "Unsupported version of RHEL" if disableConfirmation { - throw Error(message: message) + throw SwiftlyError(message: message) } else { print(message) } @@ -535,7 +535,7 @@ public struct Linux: Platform { let message = "Unsupported Linux platform" if disableConfirmation { - throw Error(message: message) + throw SwiftlyError(message: message) } else { print(message) } diff --git a/Sources/MacOSPlatform/MacOS.swift b/Sources/MacOSPlatform/MacOS.swift index b0ad224f..6d3d3c63 100644 --- a/Sources/MacOSPlatform/MacOS.swift +++ b/Sources/MacOSPlatform/MacOS.swift @@ -51,7 +51,7 @@ public struct MacOS: Platform { public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws { guard tmpFile.fileExists() else { - throw Error(message: "\(tmpFile) doesn't exist") + throw SwiftlyError(message: "\(tmpFile) doesn't exist") } if !self.swiftlyToolchainsDir.fileExists() { @@ -85,7 +85,7 @@ public struct MacOS: Platform { public func extractSwiftlyAndInstall(from archive: URL) throws { guard archive.fileExists() else { - throw Error(message: "\(archive) doesn't exist") + throw SwiftlyError(message: "\(archive) doesn't exist") } let homeDir: URL @@ -111,7 +111,7 @@ public struct MacOS: Platform { // and the ones that are mocked here in the test framework. let payload = tmpDir.appendingPathComponent("Payload") guard payload.fileExists() else { - throw Error(message: "Payload file could not be found at \(tmpDir).") + throw SwiftlyError(message: "Payload file could not be found at \(tmpDir).") } try runProgram("tar", "-C", installDir.path, "-xf", payload.path) @@ -128,11 +128,11 @@ public struct MacOS: Platform { let decoder = PropertyListDecoder() let infoPlist = toolchainDir.appendingPathComponent("Info.plist") guard let data = try? Data(contentsOf: infoPlist) else { - throw Error(message: "could not open \(infoPlist)") + throw SwiftlyError(message: "could not open \(infoPlist)") } guard let pkgInfo = try? decoder.decode(SwiftPkgInfo.self, from: data) else { - throw Error(message: "could not decode plist at \(infoPlist)") + throw SwiftlyError(message: "could not decode plist at \(infoPlist)") } try FileManager.default.removeItem(at: toolchainDir) diff --git a/Sources/Swiftly/Config.swift b/Sources/Swiftly/Config.swift index 218f8cc6..58548801 100644 --- a/Sources/Swiftly/Config.swift +++ b/Sources/Swiftly/Config.swift @@ -40,7 +40,7 @@ public struct Config: Codable, Equatable { To begin using swiftly you can install it: '\(CommandLine.arguments[0]) init'. """ - throw Error(message: msg) + throw SwiftlyError(message: msg) } } diff --git a/Sources/Swiftly/Init.swift b/Sources/Swiftly/Init.swift index 8483ad41..ce8fc7b2 100644 --- a/Sources/Swiftly/Init.swift +++ b/Sources/Swiftly/Init.swift @@ -39,7 +39,7 @@ internal struct Init: SwiftlyCommand { if let config, !overwrite && config.version != SwiftlyCore.version { // We don't support downgrades, and we don't yet support upgrades - throw Error(message: "An existing swiftly installation was detected. You can try again with '--overwrite' to overwrite it.") + throw SwiftlyError(message: "An existing swiftly installation was detected. You can try again with '--overwrite' to overwrite it.") } // Give the user the prompt and the choice to abort at this point. @@ -64,7 +64,7 @@ internal struct Init: SwiftlyCommand { """) if SwiftlyCore.readLine(prompt: "Proceed with the installation? [Y/n] ") == "n" { - throw Error(message: "Swiftly installation has been cancelled") + throw SwiftlyError(message: "Swiftly installation has been cancelled") } } @@ -82,7 +82,7 @@ internal struct Init: SwiftlyCommand { let proceed = SwiftlyCore.readLine(prompt: "Proceed? [y/N]") ?? "n" guard proceed == "y" else { - throw Error(message: "Swiftly installation has been cancelled") + throw SwiftlyError(message: "Swiftly installation has been cancelled") } } @@ -121,7 +121,7 @@ internal struct Init: SwiftlyCommand { do { try FileManager.default.createDirectory(at: requiredDir, withIntermediateDirectories: true) } catch { - throw Error(message: "Failed to create required directory \"\(requiredDir.path)\": \(error)") + throw SwiftlyError(message: "Failed to create required directory \"\(requiredDir.path)\": \(error)") } } } @@ -136,7 +136,7 @@ internal struct Init: SwiftlyCommand { config = c } - guard var config else { throw Error(message: "Configuration could not be set") } + guard var config else { throw SwiftlyError(message: "Configuration could not be set") } // Move our executable over to the correct place try Swiftly.currentPlatform.installSwiftlyBin() diff --git a/Sources/Swiftly/Install.swift b/Sources/Swiftly/Install.swift index 7de678fa..a97ba5da 100644 --- a/Sources/Swiftly/Install.swift +++ b/Sources/Swiftly/Install.swift @@ -86,10 +86,10 @@ struct Install: SwiftlyCommand { } else if let error = error { throw error } else { - throw Error(message: "Internal error selecting toolchain to install.") + throw SwiftlyError(message: "Internal error selecting toolchain to install.") } } else { - throw Error(message: "Swiftly couldn't determine the toolchain version to install. Please set a version like this and try again: `swiftly install latest`") + throw SwiftlyError(message: "Swiftly couldn't determine the toolchain version to install. Please set a version like this and try again: `swiftly install latest`") } } @@ -115,7 +115,7 @@ struct Install: SwiftlyCommand { if let postInstallScript { guard let postInstallFile = self.postInstallFile else { - throw Error(message: """ + throw SwiftlyError(message: """ There are some dependencies that should be installed before using this toolchain. You can run the following script as the system administrator (e.g. root) to prepare @@ -189,7 +189,7 @@ struct Install: SwiftlyCommand { url += "\(version.identifier)-\(platformFullString).\(Swiftly.currentPlatform.toolchainFileExtension)" guard let url = URL(string: url) else { - throw Error(message: "Invalid toolchain URL: \(url)") + throw SwiftlyError(message: "Invalid toolchain URL: \(url)") } let animation = PercentProgressAnimation( @@ -223,7 +223,7 @@ struct Install: SwiftlyCommand { } ) } catch let notFound as SwiftlyHTTPClient.DownloadNotFoundError { - throw Error(message: "\(version) does not exist at URL \(notFound.url), exiting") + throw SwiftlyError(message: "\(version) does not exist at URL \(notFound.url), exiting") } catch { animation.complete(success: false) throw error @@ -269,7 +269,7 @@ struct Install: SwiftlyCommand { let proceed = SwiftlyCore.readLine(prompt: "Proceed? [y/N]") ?? "n" guard proceed == "y" else { - throw Error(message: "Toolchain installation has been cancelled") + throw SwiftlyError(message: "Toolchain installation has been cancelled") } } @@ -315,13 +315,13 @@ struct Install: SwiftlyCommand { SwiftlyCore.print("Fetching the latest stable Swift release...") guard let release = try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform, limit: 1).first else { - throw Error(message: "couldn't get latest releases") + throw SwiftlyError(message: "couldn't get latest releases") } return .stable(release) case let .stable(major, minor, patch): guard let minor else { - throw Error( + throw SwiftlyError( message: "Need to provide at least major and minor versions when installing a release toolchain." ) } @@ -338,7 +338,7 @@ struct Install: SwiftlyCommand { }.first guard let toolchain else { - throw Error(message: "No release toolchain found matching \(major).\(minor)") + throw SwiftlyError(message: "No release toolchain found matching \(major).\(minor)") } return .stable(toolchain) @@ -358,7 +358,7 @@ struct Install: SwiftlyCommand { snapshot.branch == branch } } catch let branchNotFoundErr as SwiftlyHTTPClient.SnapshotBranchNotFoundError { - throw Error(message: "You have requested to install a snapshot toolchain from branch \(branchNotFoundErr.branch). It cannot be found on swift.org. Note that snapshots are only available from the current `main` release and the latest x.y (major.minor) release. Try again with a different branch.") + throw SwiftlyError(message: "You have requested to install a snapshot toolchain from branch \(branchNotFoundErr.branch). It cannot be found on swift.org. Note that snapshots are only available from the current `main` release and the latest x.y (major.minor) release. Try again with a different branch.") } catch { throw error } @@ -366,7 +366,7 @@ struct Install: SwiftlyCommand { let firstSnapshot = snapshots.first guard let firstSnapshot else { - throw Error(message: "No snapshot toolchain found for branch \(branch)") + throw SwiftlyError(message: "No snapshot toolchain found for branch \(branch)") } return .snapshot(firstSnapshot) diff --git a/Sources/Swiftly/ListAvailable.swift b/Sources/Swiftly/ListAvailable.swift index 090bc43f..141e0d7b 100644 --- a/Sources/Swiftly/ListAvailable.swift +++ b/Sources/Swiftly/ListAvailable.swift @@ -54,7 +54,7 @@ struct ListAvailable: SwiftlyCommand { do { tc = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: branch).map { ToolchainVersion.snapshot($0) } } catch let branchNotFoundError as SwiftlyHTTPClient.SnapshotBranchNotFoundError { - throw Error(message: "The snapshot branch \(branchNotFoundError.branch) was not found on swift.org. Note that snapshot toolchains are only available for the current `main` release and the previous x.y (major.minor) release.") + throw SwiftlyError(message: "The snapshot branch \(branchNotFoundError.branch) was not found on swift.org. Note that snapshot toolchains are only available for the current `main` release and the previous x.y (major.minor) release.") } catch { throw error } diff --git a/Sources/Swiftly/Proxy.swift b/Sources/Swiftly/Proxy.swift index 72b642c2..5e75b9ca 100644 --- a/Sources/Swiftly/Proxy.swift +++ b/Sources/Swiftly/Proxy.swift @@ -53,13 +53,13 @@ public enum Proxy { } guard let toolchain = toolchain else { - throw Error(message: "No swift toolchain could be selected from either from a .swift-version file, or the default. You can try using `swiftly install ` to install one.") + throw SwiftlyError(message: "No swift toolchain could be selected from either from a .swift-version file, or the default. You can try using `swiftly install ` to install one.") } try await Swiftly.currentPlatform.proxy(toolchain, binName, Array(CommandLine.arguments[1...])) } catch let terminated as RunProgramError { exit(terminated.exitCode) - } catch let error as Error { + } catch let error as SwiftlyError { SwiftlyCore.print(error.message) exit(1) } catch { diff --git a/Sources/Swiftly/Run.swift b/Sources/Swiftly/Run.swift index 8080930f..474f23d1 100644 --- a/Sources/Swiftly/Run.swift +++ b/Sources/Swiftly/Run.swift @@ -65,7 +65,7 @@ internal struct Run: SwiftlyCommand { if let selector = selector { let matchedToolchain = config.listInstalledToolchains(selector: selector).max() guard let matchedToolchain = matchedToolchain else { - throw Error(message: "The selected toolchain \(selector.description) didn't match any of the installed toolchains. You can install it with `swiftly install \(selector.description)`") + throw SwiftlyError(message: "The selected toolchain \(selector.description) didn't match any of the installed toolchains. You can install it with `swiftly install \(selector.description)`") } toolchain = matchedToolchain @@ -81,7 +81,7 @@ internal struct Run: SwiftlyCommand { } guard let toolchain = toolchain else { - throw Error(message: "No swift toolchain could be selected from either from a .swift-version file, or the default. You can try using `swiftly install ` to install one.") + throw SwiftlyError(message: "No swift toolchain could be selected from either from a .swift-version file, or the default. You can try using `swiftly install ` to install one.") } do { @@ -128,7 +128,7 @@ public func extractProxyArguments(command: [String]) throws -> (command: [String } guard args.command.count > 0 else { - throw Error(message: "Provide at least one command to run.") + throw SwiftlyError(message: "Provide at least one command to run.") } return args diff --git a/Sources/Swiftly/SelfUpdate.swift b/Sources/Swiftly/SelfUpdate.swift index e41dea60..afcd3b78 100644 --- a/Sources/Swiftly/SelfUpdate.swift +++ b/Sources/Swiftly/SelfUpdate.swift @@ -21,7 +21,7 @@ internal struct SelfUpdate: SwiftlyCommand { let swiftlyBin = Swiftly.currentPlatform.swiftlyBinDir.appendingPathComponent("swiftly") guard FileManager.default.fileExists(atPath: swiftlyBin.path) else { - throw Error(message: "Self update doesn't work when swiftly has been installed externally. Please keep it updated from the source where you installed it in the first place.") + throw SwiftlyError(message: "Self update doesn't work when swiftly has been installed externally. Please keep it updated from the source where you installed it in the first place.") } let _ = try await Self.execute(verbose: self.root.verbose) @@ -30,9 +30,9 @@ internal struct SelfUpdate: SwiftlyCommand { public static func execute(verbose: Bool) async throws -> SwiftlyVersion { SwiftlyCore.print("Checking for swiftly updates...") - let swiftlyRelease = try await SwiftlyCore.httpClient.getSwiftlyRelease() + let swiftlyRelease = try await SwiftlyCore.httpClient.getCurrentSwiftlyRelease() - guard swiftlyRelease.version > SwiftlyCore.version else { + guard try swiftlyRelease.swiftlyVersion > SwiftlyCore.version else { SwiftlyCore.print("Already up to date.") return SwiftlyCore.version } @@ -40,27 +40,27 @@ internal struct SelfUpdate: SwiftlyCommand { var downloadURL: Foundation.URL? for platform in swiftlyRelease.platforms { #if os(macOS) - guard platform.platform == .Darwin else { + guard platform.isDarwin else { continue } #elseif os(Linux) - guard platform.platform == .Linux else { + guard platform.isLinux else { continue } #endif #if arch(x86_64) - downloadURL = platform.x86_64 + downloadURL = try platform.x86_64URL #elseif arch(arm64) - downloadURL = platform.arm64 + downloadURL = try platform.arm64URL #endif } - guard let downloadURL = downloadURL else { - throw Error(message: "The newest release of swiftly is incompatible with your current OS and/or processor architecture.") + guard let downloadURL else { + throw SwiftlyError(message: "The newest release of swiftly is incompatible with your current OS and/or processor architecture.") } - let version = swiftlyRelease.version + let version = try swiftlyRelease.swiftlyVersion SwiftlyCore.print("A new version is available: \(version)") diff --git a/Sources/Swiftly/Swiftly.swift b/Sources/Swiftly/Swiftly.swift index e0badf85..72260d8e 100644 --- a/Sources/Swiftly/Swiftly.swift +++ b/Sources/Swiftly/Swiftly.swift @@ -78,7 +78,7 @@ extension SwiftlyCommand { do { try FileManager.default.createDirectory(at: requiredDir, withIntermediateDirectories: true) } catch { - throw Error(message: "Failed to create required directory \"\(requiredDir.path)\": \(error)") + throw SwiftlyError(message: "Failed to create required directory \"\(requiredDir.path)\": \(error)") } continue } diff --git a/Sources/Swiftly/Update.swift b/Sources/Swiftly/Update.swift index 98540e5e..93805477 100644 --- a/Sources/Swiftly/Update.swift +++ b/Sources/Swiftly/Update.swift @@ -122,7 +122,7 @@ struct Update: SwiftlyCommand { if let postInstallScript = postInstallScript { guard let postInstallFile = self.postInstallFile else { - throw Error(message: """ + throw SwiftlyError(message: """ There are some system dependencies that should be installed before using this toolchain. You can run the following script as the system administrator (e.g. root) to prepare @@ -213,7 +213,7 @@ struct Update: SwiftlyCommand { snapshot.branch == old.branch && snapshot.date > old.date } } catch let branchNotFoundErr as SwiftlyHTTPClient.SnapshotBranchNotFoundError { - throw Error(message: "Snapshot branch \(branchNotFoundErr.branch) cannot be updated. One possible reason for this is that there has been a new release published to swift.org and this snapshot is for an older release. Snapshots are only available for the newest release and the main branch. You can install a fresh snapshot toolchain from the either the latest release x.y (major.minor) with `swiftly install x.y-snapshot` or from the main branch with `swiftly install main-snapshot`.") + throw SwiftlyError(message: "Snapshot branch \(branchNotFoundErr.branch) cannot be updated. One possible reason for this is that there has been a new release published to swift.org and this snapshot is for an older release. Snapshots are only available for the newest release and the main branch. You can install a fresh snapshot toolchain from the either the latest release x.y (major.minor) with `swiftly install x.y-snapshot` or from the main branch with `swiftly install main-snapshot`.") } catch { throw error } diff --git a/Sources/Swiftly/Use.swift b/Sources/Swiftly/Use.swift index aeecbe5b..29f3cfd9 100644 --- a/Sources/Swiftly/Use.swift +++ b/Sources/Swiftly/Use.swift @@ -90,7 +90,7 @@ internal struct Use: SwiftlyCommand { } guard !self.printLocation else { - throw Error(message: "The print location flag cannot be used with a toolchain version.") + throw SwiftlyError(message: "The print location flag cannot be used with a toolchain version.") } let selector = try ToolchainSelector(parsing: toolchain) @@ -203,11 +203,11 @@ public func selectToolchain(config: inout Config, globalDefault: Bool = false) a let contents = try? String(contentsOf: svFile, encoding: .utf8) guard let contents = contents else { - return (nil, .swiftVersionFile(svFile, nil, Error(message: "The swift version file could not be read: \(svFile)"))) + return (nil, .swiftVersionFile(svFile, nil, SwiftlyError(message: "The swift version file could not be read: \(svFile)"))) } guard !contents.isEmpty else { - return (nil, .swiftVersionFile(svFile, nil, Error(message: "The swift version file is empty: \(svFile)"))) + return (nil, .swiftVersionFile(svFile, nil, SwiftlyError(message: "The swift version file is empty: \(svFile)"))) } let selectorString = contents.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: "\r", with: "") @@ -215,15 +215,15 @@ public func selectToolchain(config: inout Config, globalDefault: Bool = false) a do { selector = try ToolchainSelector(parsing: selectorString) } catch { - return (nil, .swiftVersionFile(svFile, nil, Error(message: "The swift version file is malformed: \(svFile) \(error)"))) + return (nil, .swiftVersionFile(svFile, nil, SwiftlyError(message: "The swift version file is malformed: \(svFile) \(error)"))) } guard let selector = selector else { - return (nil, .swiftVersionFile(svFile, nil, Error(message: "The swift version file is malformed: \(svFile)"))) + return (nil, .swiftVersionFile(svFile, nil, SwiftlyError(message: "The swift version file is malformed: \(svFile)"))) } guard let selectedToolchain = config.listInstalledToolchains(selector: selector).max() else { - return (nil, .swiftVersionFile(svFile, selector, Error(message: "The swift version file `\(svFile.path)` uses toolchain version \(selector), but it doesn't match any of the installed toolchains. You can install the toolchain with `swiftly install`."))) + return (nil, .swiftVersionFile(svFile, selector, SwiftlyError(message: "The swift version file `\(svFile.path)` uses toolchain version \(selector), but it doesn't match any of the installed toolchains. You can install the toolchain with `swiftly install`."))) } return (selectedToolchain, .swiftVersionFile(svFile, selector, nil)) diff --git a/Sources/SwiftlyCore/Error.swift b/Sources/SwiftlyCore/Error.swift index f41296c4..2e10a08f 100644 --- a/Sources/SwiftlyCore/Error.swift +++ b/Sources/SwiftlyCore/Error.swift @@ -1,6 +1,6 @@ import Foundation -public struct Error: LocalizedError, CustomStringConvertible { +public struct SwiftlyError: LocalizedError, CustomStringConvertible { public let message: String public init(message: String) { diff --git a/Sources/SwiftlyCore/HTTPClient.swift b/Sources/SwiftlyCore/HTTPClient.swift index c3d00a50..57323344 100644 --- a/Sources/SwiftlyCore/HTTPClient.swift +++ b/Sources/SwiftlyCore/HTTPClient.swift @@ -1,12 +1,73 @@ import _StringProcessing import AsyncHTTPClient import Foundation +import HTTPTypes import NIO import NIOFoundationCompat import NIOHTTP1 +import OpenAPIAsyncHTTPClient +import OpenAPIRuntime + +extension Components.Schemas.SwiftlyRelease { + public var swiftlyVersion: SwiftlyVersion { + get throws { + guard let releaseVersion = try? SwiftlyVersion(parsing: self.version) else { + throw SwiftlyError(message: "Invalid swiftly version reported: \(self.version)") + } + + return releaseVersion + } + } +} + +extension Components.Schemas.SwiftlyReleasePlatformArtifacts { + public var isDarwin: Bool { + self.platform.value1 == .darwin + } + + public var isLinux: Bool { + self.platform.value1 == .linux + } + + public var x86_64URL: URL { + get throws { + guard let url = URL(string: self.x8664) else { + throw SwiftlyError(message: "The swiftly x86_64 URL is invalid: \(self.x8664)") + } + + return url + } + } + + public var arm64URL: URL { + get throws { + guard let url = URL(string: self.arm64) else { + throw SwiftlyError(message: "The swiftly arm64 URL is invalid: \(self.arm64)") + } + + return url + } + } +} public protocol HTTPRequestExecutor { func execute(_ request: HTTPClientRequest, timeout: TimeAmount) async throws -> HTTPClientResponse + func getCurrentSwiftlyRelease() async throws -> Components.Schemas.SwiftlyRelease +} + +internal struct SwiftlyUserAgentMiddleware: ClientMiddleware { + package func intercept( + _ request: HTTPRequest, + body: HTTPBody?, + baseURL: URL, + operationID _: String, + next: (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?) + ) async throws -> (HTTPResponse, HTTPBody?) { + var request = request + // Adds the `Authorization` header field with the provided value. + request.headerFields[.userAgent] = "swiftly/\(SwiftlyCore.version)" + return try await next(request, body, baseURL) + } } /// An `HTTPRequestExecutor` backed by the shared `HTTPClient`. @@ -53,6 +114,20 @@ internal class HTTPRequestExecutorImpl: HTTPRequestExecutor { public func execute(_ request: HTTPClientRequest, timeout: TimeAmount) async throws -> HTTPClientResponse { try await self.httpClient.execute(request, timeout: timeout) } + + public func getCurrentSwiftlyRelease() async throws -> Components.Schemas.SwiftlyRelease { + let config = AsyncHTTPClientTransport.Configuration(client: self.httpClient, timeout: .seconds(30)) + let swiftlyUserAgent = SwiftlyUserAgentMiddleware() + + let client = Client( + serverURL: try Servers.Server1.url(), + transport: AsyncHTTPClientTransport(configuration: config), + middlewares: [swiftlyUserAgent] + ) + + let response = try await client.getCurrentSwiftlyRelease() + return try response.ok.body.json + } } private func makeRequest(url: String) -> HTTPClientRequest { @@ -61,23 +136,6 @@ private func makeRequest(url: String) -> HTTPClientRequest { return request } -public enum SwiftOrgSwiftlyPlatformType: String, Codable { - case Darwin - case Linux - case Windows -} - -public struct SwiftOrgSwiftlyPlatform: Codable { - public var platform: SwiftOrgSwiftlyPlatformType - public var x86_64: URL - public var arm64: URL -} - -public struct SwiftOrgSwiftlyRelease: Codable { - public var version: SwiftlyVersion - public var platforms: [SwiftOrgSwiftlyPlatform] -} - struct SwiftOrgPlatform: Codable { var name: String var archs: [String]? @@ -165,7 +223,7 @@ public struct SwiftOrgSnapshot: Codable { let branch: ToolchainVersion.Snapshot.Branch if let majorString = match.output.1, let minorString = match.output.2 { guard let major = Int(majorString), let minor = Int(minorString) else { - throw Error(message: "malformatted release branch: \"\(majorString).\(minorString)\"") + throw SwiftlyError(message: "malformatted release branch: \"\(majorString).\(minorString)\"") } branch = .release(major: major, minor: minor) } else { @@ -218,17 +276,15 @@ public struct SwiftlyHTTPClient { throw SwiftlyHTTPClient.JSONNotFoundError(url: url) default: let json = String(buffer: response.buffer) - throw Error(message: "Received \(response.status) when reaching \(url) for JSON: \(json)") + throw SwiftlyError(message: "Received \(response.status) when reaching \(url) for JSON: \(json)") } return try JSONDecoder().decode(type.self, from: response.buffer) } /// Return the current Swiftly release using the swift.org API. - public func getSwiftlyRelease() async throws -> SwiftOrgSwiftlyRelease { - let url = "https://www.swift.org/api/v1/swiftly.json" - let swiftlyRelease: SwiftOrgSwiftlyRelease = try await self.getFromJSON(url: url, type: SwiftOrgSwiftlyRelease.self) - return swiftlyRelease + public func getCurrentSwiftlyRelease() async throws -> Components.Schemas.SwiftlyRelease { + try await SwiftlyCore.httpRequestExecutor.getCurrentSwiftlyRelease() } /// Return an array of released Swift versions that match the given filter, up to the provided @@ -269,7 +325,7 @@ public struct SwiftlyHTTPClient { guard let version = try? ToolchainVersion(parsing: swiftOrgRelease.stableName), case let .stable(release) = version else { - throw Error(message: "error parsing swift.org release version: \(swiftOrgRelease.stableName)") + throw SwiftlyError(message: "error parsing swift.org release version: \(swiftOrgRelease.stableName)") } if let filter { @@ -392,7 +448,7 @@ public struct SwiftlyHTTPClient { case .notFound: throw SwiftlyHTTPClient.DownloadNotFoundError(url: url.path) default: - throw Error(message: "Received \(response.status) when trying to download \(url)") + throw SwiftlyError(message: "Received \(response.status) when trying to download \(url)") } // if defined, the content-length headers announces the size of the body diff --git a/Sources/SwiftlyCore/Platform.swift b/Sources/SwiftlyCore/Platform.swift index a82b4ad2..b687a021 100644 --- a/Sources/SwiftlyCore/Platform.swift +++ b/Sources/SwiftlyCore/Platform.swift @@ -145,7 +145,7 @@ extension Platform { // Prevent circularities with a memento environment variable guard newEnv["SWIFTLY_PROXY_IN_PROGRESS"] == nil else { - throw Error(message: "Circular swiftly proxy invocation") + throw SwiftlyError(message: "Circular swiftly proxy invocation") } newEnv["SWIFTLY_PROXY_IN_PROGRESS"] = "1" diff --git a/Sources/SwiftlyCore/SwiftlyVersion.swift b/Sources/SwiftlyCore/SwiftlyVersion.swift index f45ae5c6..57f787e4 100644 --- a/Sources/SwiftlyCore/SwiftlyVersion.swift +++ b/Sources/SwiftlyCore/SwiftlyVersion.swift @@ -21,7 +21,7 @@ public struct SwiftlyVersion: Equatable, Comparable, CustomStringConvertible { public init(parsing tag: String) throws { guard let match = try Self.regex.wholeMatch(in: tag) else { - throw Error(message: "unable to parse release tag: \"\(tag)\"") + throw SwiftlyError(message: "unable to parse release tag: \"\(tag)\"") } self.major = Int(match.output.1)! diff --git a/Sources/SwiftlyCore/ToolchainVersion.swift b/Sources/SwiftlyCore/ToolchainVersion.swift index 72777343..ddb2b27a 100644 --- a/Sources/SwiftlyCore/ToolchainVersion.swift +++ b/Sources/SwiftlyCore/ToolchainVersion.swift @@ -116,7 +116,7 @@ public enum ToolchainVersion { let minor = Int(match.output.2), let patch = Int(match.output.3) else { - throw Error(message: "invalid stable version: \(string)") + throw SwiftlyError(message: "invalid stable version: \(string)") } self = ToolchainVersion(major: major, minor: minor, patch: patch) } else if let match = try Self.mainSnapshotRegex.wholeMatch(in: string) { @@ -126,11 +126,11 @@ public enum ToolchainVersion { let major = Int(match.output.1), let minor = Int(match.output.2) else { - throw Error(message: "invalid release snapshot version: \(string)") + throw SwiftlyError(message: "invalid release snapshot version: \(string)") } self = ToolchainVersion(snapshotBranch: .release(major: major, minor: minor), date: String(match.output.3)) } else { - throw Error(message: "invalid toolchain version: \"\(string)\"") + throw SwiftlyError(message: "invalid toolchain version: \"\(string)\"") } } @@ -264,7 +264,7 @@ public enum ToolchainSelector { return } - throw Error(message: "invalid toolchain selector: \"\(input)\"") + throw SwiftlyError(message: "invalid toolchain selector: \"\(input)\"") } public func isReleaseSelector() -> Bool { @@ -404,7 +404,7 @@ struct ReleaseSnapshotParser: ToolchainSelectorParser { let major = Int(match.output.1), let minor = Int(match.output.2) else { - throw Error(message: "malformatted version: \(match.output.1).\(match.output.2)") + throw SwiftlyError(message: "malformatted version: \(match.output.1).\(match.output.2)") } return .snapshot(branch: .release(major: major, minor: minor), date: match.output.3.map(String.init)) diff --git a/Sources/SwiftlyCore/openapi-generator-config.yaml b/Sources/SwiftlyCore/openapi-generator-config.yaml new file mode 100644 index 00000000..c8f6e7a7 --- /dev/null +++ b/Sources/SwiftlyCore/openapi-generator-config.yaml @@ -0,0 +1,7 @@ +generate: + - types + - client +namingStrategy: idiomatic +accessModifier: public +filter: + tags: ["Swiftly"] diff --git a/Sources/SwiftlyCore/openapi.yaml b/Sources/SwiftlyCore/openapi.yaml new file mode 100644 index 00000000..46eaebe0 --- /dev/null +++ b/Sources/SwiftlyCore/openapi.yaml @@ -0,0 +1,357 @@ +openapi: '3.0.3' +info: + title: swift.org API + description: API for retrieving Swift version release information, including date, platform support, tags, and toolchain details. + version: 1.1.0 +servers: + - url: https://www.swift.org/api/v1 + description: The production deployment. + - url: http://127.0.0.1:4000/api/v1 + description: A local deployment. +tags: + - name: Toolchains + description: Information about published toolchains builds. + - name: Swiftly + description: Information about the Swiftly installer. + - name: SSWG + description: Information about the Swift Server Workgroup. +paths: + /install/releases.json: + get: + operationId: listReleases + summary: Fetch all released toolchains + tags: + - Toolchains + responses: + '200': + description: A successful response. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Release' + /install/dev/{branch}/{platform}.json: + parameters: + - name: branch + in: path + required: true + schema: + $ref: '#/components/schemas/KnownSourceBranch' + - name: platform + in: path + required: true + schema: + $ref: '#/components/schemas/KnownPlatformIdentifier' + get: + operationId: listDevToolchains + summary: Fetch all development toolchains + tags: + - Toolchains + responses: + '200': + description: A successful response. + content: + application/json: + schema: + $ref: '#/components/schemas/DevToolchains' + /install/dev/{branch}/static-sdk.json: + parameters: + - name: branch + in: path + required: true + schema: + $ref: '#/components/schemas/KnownSourceBranch' + get: + operationId: listStaticSDKDevToolchains + summary: Fetch all static SDK development toolchains + tags: + - Toolchains + responses: + '200': + description: A successful response. + content: + application/json: + schema: + $ref: '#/components/schemas/DevToolchainsForArch' + /swiftly.json: + get: + operationId: getCurrentSwiftlyRelease + summary: Fetch the metadata of the current Swiftly release. + tags: + - Swiftly + responses: + '200': + description: A successful response. + content: + application/json: + schema: + $ref: '#/components/schemas/SwiftlyRelease' + /sswg/incubation/{filter}.json: + parameters: + - name: filter + in: path + required: true + schema: + $ref: '#/components/schemas/SSWGIncubationFilter' + get: + operationId: listSSWGIncubatedPackages + summary: List the packages incubated by the Swift Server Workgroup. + tags: + - SSWG + responses: + '200': + description: A successful response. + content: + application/json: + schema: + $ref: '#/components/schemas/SSWGPackageList' +components: + schemas: + Release: + type: object + properties: + name: + type: string + pattern: \d+\.\d+(\.\d+)? + description: Version number of the Swift release. + example: '6.0.2' + date: + type: string + pattern: \d{4}-\d{2}-\d{2} + description: Release date of the Swift version. + example: '2024-10-28' + platforms: + type: array + description: List of supported platforms for this release. + items: + $ref: '#/components/schemas/Platform' + tag: + type: string + description: Release tag identifier. + example: 'swift-6.0.2-RELEASE' + xcode: + type: string + description: Xcode version associated with the release. + example: 'Xcode 16.1' + xcode_release: + type: boolean + description: Indicates if this Swift version has an associated Xcode release. + required: + - name + - date + - platforms + - tag + - xcode + KnownPlatformIdentifier: + description: |- + A platform identifier known as of this snapshot of the API document. + + This is distinct from `PlatformIdentifier` to allow parsing unknown identifiers. + Parsing unknown identifiers is important to avoid a new identifier causing a major API break. + type: string + enum: + - amazonlinux2 + - centos7 + - macos + - ubi9 + - ubuntu2004 + - ubuntu2204 + - windows10 + PlatformIdentifier: + anyOf: + - $ref: '#/components/schemas/KnownPlatformIdentifier' + - type: string + description: The raw identifier, a fallback for when an unknown identifier is received. + KnownDevToolchainKind: + description: |- + A dev toolchain name known as of this snapshot of the API document. + + This is distinct from `DevToolchainKind` to allow parsing unknown names. + Parsing unknown names is important to avoid a new name causing a major API break. + type: string + enum: + - Swift Development Snapshot + DevToolchainKind: + anyOf: + - $ref: '#/components/schemas/KnownDevToolchainKind' + - type: string + description: The raw name, a fallback for when an unknown name is received. + DevToolchainForArch: + type: object + properties: + name: + $ref: '#/components/schemas/DevToolchainKind' + date: + type: string + pattern: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-+]\d{4} + description: Date and time of the snapshot in 'YYYY-MM-DD HH:MM:SS -ZZZZ' format. + example: '2024-10-12 10:10:00 -0600' + dir: + type: string + description: Directory name for the snapshot release. + example: 'swift-6.0-DEVELOPMENT-SNAPSHOT-2024-10-12-a' + download: + type: string + description: Filename of the snapshot archive to download. + example: 'swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-25-a-amazonlinux2-aarch64.tar.gz' + download_signature: + type: string + description: Filename of the signature file for the download. + example: 'swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-25-a-amazonlinux2-aarch64.tar.gz.sig' + debug_info: + type: string + description: Filename of the debug symbols file for the download. + example: 'swift-DEVELOPMENT-SNAPSHOT-2024-10-27-a-osx-symbols.pkg' + required: + - date + - dir + - download + DevToolchainsForArch: + type: array + items: + $ref: '#/components/schemas/DevToolchainForArch' + DevToolchains: + type: object + properties: + aarch64: + $ref: '#/components/schemas/DevToolchainsForArch' + arm64: + $ref: '#/components/schemas/DevToolchainsForArch' + x86_64: + $ref: '#/components/schemas/DevToolchainsForArch' + universal: + $ref: '#/components/schemas/DevToolchainsForArch' + KnownSourceBranch: + description: |- + A branch known as of this snapshot of the API document. + + This is distinct from `SourceBranch` to allow parsing unknown branches. + Parsing unknown branches is important to avoid a new branch causing a major API break. + type: string + enum: + - main + - '6.0' + SourceBranch: + anyOf: + - $ref: '#/components/schemas/KnownSourceBranch' + - type: string + description: The raw branch, a fallback for when an unknown branch is received. + KnownPlatformType: + description: |- + A platform type known as of this snapshot of the API document. + + This is distinct from `PlatformType` to allow parsing unknown platforms. + Parsing unknown platforms is important to avoid a new platform causing a major API break. + type: string + enum: + - Linux + - Windows + - static-sdk + PlatformType: + anyOf: + - $ref: '#/components/schemas/KnownPlatformType' + - type: string + description: The raw platform name, a fallback for when an unknown platform is received. + KnownArchitecture: + description: |- + An architecture known as of this snapshot of the API document. + + This is distinct from `Architecture` to allow parsing unknown architectures. + Parsing unknown architectures is important to avoid a new architecture causing a major API break. + type: string + enum: + - aarch64 + - arm64 + - x86_64 + - universal + Architecture: + anyOf: + - $ref: '#/components/schemas/KnownArchitecture' + - type: string + description: The raw architecture name, a fallback for when an unknown architecture is received. + Platform: + type: object + properties: + name: + type: string + description: Platform name. + example: 'Ubuntu 24.04' + platform: + $ref: '#/components/schemas/PlatformType' + archs: + type: array + items: + $ref: '#/components/schemas/Architecture' + description: List of supported architectures. + required: + - name + - platform + - archs + KnownSwiftlyPlatformIdentifier: + description: |- + A Swiftly platform identifier known as of this snapshot of the API document. + + This is distinct from `SwiftlyPlatformIdentifier` to allow parsing unknown platform identifiers. + Parsing unknown platform identifiers is important to avoid a new platform causing a major API break. + type: string + enum: + - Linux + - Darwin + - Windows + SwiftlyPlatformIdentifier: + anyOf: + - $ref: '#/components/schemas/KnownSwiftlyPlatformIdentifier' + - type: string + description: The raw platform identifier, a fallback for when an unknown platform is received. + SwiftlyArtifactDownloadURL: + type: string + description: A download URL for a Swiftly artifact. + example: 'https://download.swift.org/swiftly/linux/swiftly-0.4.0-dev-aarch64.tar.gz' + SwiftlyReleasePlatformArtifacts: + type: object + description: Information about the Swiftly artifacts for a specific platform. + properties: + platform: + $ref: '#/components/schemas/SwiftlyPlatformIdentifier' + arm64: + $ref: '#/components/schemas/SwiftlyArtifactDownloadURL' + x86_64: + $ref: '#/components/schemas/SwiftlyArtifactDownloadURL' + required: + - platform + - arm64 + - x86_64 + SwiftlyRelease: + type: object + description: Information about a release of the Swiftly installer. + properties: + version: + type: string + description: The current Swiftly version. + example: '0.4.0-dev' + platforms: + type: array + description: A list of supported platforms with artifact URLs. + items: + $ref: '#/components/schemas/SwiftlyReleasePlatformArtifacts' + required: + - version + - platforms + SSWGPackageURL: + type: string + description: A URL of an SSWG-incubated package. + example: 'http://github.com/apple/swift-nio.git' + SSWGPackageList: + type: array + description: A list of SSWG-incubated packages. + items: + $ref: '#/components/schemas/SSWGPackageURL' + SSWGIncubationFilter: + type: string + description: A filter for fetching a subset of the SSWG-incubated packages. + enum: + - all + - graduated + - incubating + - sandbox diff --git a/Tests/SwiftlyTests/HTTPClientTests.swift b/Tests/SwiftlyTests/HTTPClientTests.swift index 7f1f2d4d..83dd6647 100644 --- a/Tests/SwiftlyTests/HTTPClientTests.swift +++ b/Tests/SwiftlyTests/HTTPClientTests.swift @@ -45,7 +45,12 @@ final class HTTPClientTests: SwiftlyTests { XCTAssertTrue(exceptionThrown) } - func testGetMetdataFromSwiftOrg() async throws { + func testGetSwiftlyReleaseMetadataFromSwiftOrg() async throws { + let currentRelease = try await SwiftlyCore.httpClient.getCurrentSwiftlyRelease() + XCTAssertNoThrow(try currentRelease.swiftlyVersion) + } + + func testGetToolchainMetdataFromSwiftOrg() async throws { let supportedPlatforms = [ PlatformDefinition.macOS, PlatformDefinition.ubuntu2404, diff --git a/Tests/SwiftlyTests/RunTests.swift b/Tests/SwiftlyTests/RunTests.swift index 38a33a39..6b42c63b 100644 --- a/Tests/SwiftlyTests/RunTests.swift +++ b/Tests/SwiftlyTests/RunTests.swift @@ -31,7 +31,7 @@ final class RunTests: SwiftlyTests { do { try await run.run() XCTAssert(false) - } catch let e as Error { + } catch let e as SwiftlyError { XCTAssert(e.message.contains("didn't match any of the installed toolchains")) } // THEN: an error is shown because there is no matching toolchain that is installed diff --git a/Tests/SwiftlyTests/SwiftlyTests.swift b/Tests/SwiftlyTests/SwiftlyTests.swift index 1616cf51..3a5b4d14 100644 --- a/Tests/SwiftlyTests/SwiftlyTests.swift +++ b/Tests/SwiftlyTests/SwiftlyTests.swift @@ -463,6 +463,10 @@ private struct MockHTTPRequestExecutor: HTTPRequestExecutor { public func execute(_ request: HTTPClientRequest, timeout _: TimeAmount) async throws -> HTTPClientResponse { try await self.handler(request) } + + public func getCurrentSwiftlyRelease() async throws -> Components.Schemas.SwiftlyRelease { + throw SwiftlyTestError(message: "Mocking of fetching the current swiftly release is not implemented in MockHTTPRequestExecutor.") + } } /// An `HTTPRequestExecutor` which will return a mocked response to any toolchain download requests. @@ -490,6 +494,18 @@ public class MockToolchainDownloader: HTTPRequestExecutor { self.latestSwiftlyVersion = latestSwiftlyVersion } + public func getCurrentSwiftlyRelease() async throws -> Components.Schemas.SwiftlyRelease { + let release = Components.Schemas.SwiftlyRelease( + version: self.latestSwiftlyVersion.description, + platforms: [ + .init(platform: .init(value1: .darwin), arm64: "https://download.swift.org/swiftly-darwin.pkg", x8664: "https://download.swift.org/swiftly-darwin.pkg"), + .init(platform: .init(value1: .linux), arm64: "https://download.swift.org/swiftly-linux.tar.gz", x8664: "https://download.swift.org/swiftly-linux.tar.gz"), + ] + ) + + return release + } + public func execute(_ request: HTTPClientRequest, timeout: TimeAmount) async throws -> HTTPClientResponse { guard let url = URL(string: request.url) else { throw SwiftlyTestError(message: "invalid request URL: \(request.url)") @@ -501,31 +517,6 @@ public class MockToolchainDownloader: HTTPRequestExecutor { } else if url.host == "download.swift.org" && (url.path.hasPrefix("/swift-") || url.path.hasPrefix("/development")) { // Download a toolchain return try self.makeToolchainDownloadResponse(from: url) - } else if url.host == "www.swift.org" && url.path == "/api/v1/swiftly.json" { - // Mock a response that would represent the swiftly offerings using the latest version - let decoder = JSONDecoder() - let json = """ - { - "version": "\(self.latestSwiftlyVersion)", - "platforms": [ - { - "platform": "Darwin", - "x86_64": "https://download.swift.org/swiftly-darwin.pkg", - "arm64": "https://download.swift.org/swiftly-darwin.pkg" - }, - { - "platform": "Linux", - "x86_64": "https://download.swift.org/swiftly-linux.tar.gz", - "arm64": "https://download.swift.org/swiftly-linux.tar.gz" - } - ] - } - """.data(using: .utf8)! - - let swiftlyRelease = try decoder.decode(SwiftOrgSwiftlyRelease.self, from: json) - var buffer = ByteBuffer() - try buffer.writeJSONEncodable(swiftlyRelease) - return HTTPClientResponse(body: .bytes(buffer)) } else if url.host == "www.swift.org" { // Delegate any API requests to swift.org return try await self.delegate.execute(request, timeout: timeout)