diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9aeedd053..6000f3e84 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,6 +19,9 @@ jobs: - swift:5.9-jammy - swift:5.10-focal - swift:5.10-jammy + - swift:6.0-focal + - swift:6.0-jammy + - swift:6.0-noble - swiftlang/swift:nightly-focal - swiftlang/swift:nightly-jammy container: ${{ matrix.image }} @@ -28,7 +31,13 @@ jobs: - name: Run tests run: swift test osx: - runs-on: macOS-13 + strategy: + fail-fast: false + matrix: + os: + - macos-14 + - macos-15 + runs-on: ${{ matrix.os }} steps: - name: Select latest available Xcode uses: maxim-lobanov/setup-xcode@v1 diff --git a/Sources/OpenAPIKit/JSONReference.swift b/Sources/OpenAPIKit/JSONReference.swift index 1509f029f..ba66b671a 100644 --- a/Sources/OpenAPIKit/JSONReference.swift +++ b/Sources/OpenAPIKit/JSONReference.swift @@ -469,7 +469,19 @@ extension JSONReference: Decodable { } self = .internal(internalReference) } else { - guard let externalReference = URL(string: referenceString) else { + let externalReferenceCandidate: URL? + #if canImport(FoundationEssentials) + externalReferenceCandidate = URL(string: referenceString, encodingInvalidCharacters: false) + #elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + externalReferenceCandidate = URL(string: referenceString, encodingInvalidCharacters: false) + } else { + externalReferenceCandidate = URL(string: referenceString) + } + #else + externalReferenceCandidate = URL(string: referenceString) + #endif + guard let externalReference = externalReferenceCandidate else { throw InconsistencyError( subjectName: "JSON Reference", details: "Failed to parse a valid URI for a JSON Reference from '\(referenceString)'", diff --git a/Sources/OpenAPIKit/Utility/Container+DecodeURLAsString.swift b/Sources/OpenAPIKit/Utility/Container+DecodeURLAsString.swift index a95f78732..ae5fd9fb5 100644 --- a/Sources/OpenAPIKit/Utility/Container+DecodeURLAsString.swift +++ b/Sources/OpenAPIKit/Utility/Container+DecodeURLAsString.swift @@ -11,7 +11,19 @@ import Foundation extension KeyedDecodingContainerProtocol { internal func decodeURLAsString(forKey key: Self.Key) throws -> URL { let string = try decode(String.self, forKey: key) - guard let url = URL(string: string) else { + let urlCandidate: URL? +#if canImport(FoundationEssentials) + urlCandidate = URL(string: string, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + urlCandidate = URL(string: string, encodingInvalidCharacters: false) + } else { + urlCandidate = URL(string: string) + } +#else + urlCandidate = URL(string: string) +#endif + guard let url = urlCandidate else { throw InconsistencyError( subjectName: key.stringValue, details: "If specified, must be a valid URL", @@ -26,7 +38,19 @@ extension KeyedDecodingContainerProtocol { return nil } - guard let url = URL(string: string) else { + let urlCandidate: URL? +#if canImport(FoundationEssentials) + urlCandidate = URL(string: string, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + urlCandidate = URL(string: string, encodingInvalidCharacters: false) + } else { + urlCandidate = URL(string: string) + } +#else + urlCandidate = URL(string: string) +#endif + guard let url = urlCandidate else { throw InconsistencyError( subjectName: key.stringValue, details: "If specified, must be a valid URL", diff --git a/Sources/OpenAPIKit30/JSONReference.swift b/Sources/OpenAPIKit30/JSONReference.swift index 6e6350751..e1d85c684 100644 --- a/Sources/OpenAPIKit30/JSONReference.swift +++ b/Sources/OpenAPIKit30/JSONReference.swift @@ -333,7 +333,19 @@ extension JSONReference: Decodable { } self = .internal(internalReference) } else { - guard let externalReference = URL(string: referenceString) else { + let externalReferenceCandidate: URL? +#if canImport(FoundationEssentials) + externalReferenceCandidate = URL(string: referenceString, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + externalReferenceCandidate = URL(string: referenceString, encodingInvalidCharacters: false) + } else { + externalReferenceCandidate = URL(string: referenceString) + } +#else + externalReferenceCandidate = URL(string: referenceString) +#endif + guard let externalReference = externalReferenceCandidate else { throw InconsistencyError( subjectName: "JSON Reference", details: "Failed to parse a valid URI for a JSON Reference from '\(referenceString)'", diff --git a/Sources/OpenAPIKit30/Utility/Container+DecodeURLAsString.swift b/Sources/OpenAPIKit30/Utility/Container+DecodeURLAsString.swift index a95f78732..ae5fd9fb5 100644 --- a/Sources/OpenAPIKit30/Utility/Container+DecodeURLAsString.swift +++ b/Sources/OpenAPIKit30/Utility/Container+DecodeURLAsString.swift @@ -11,7 +11,19 @@ import Foundation extension KeyedDecodingContainerProtocol { internal func decodeURLAsString(forKey key: Self.Key) throws -> URL { let string = try decode(String.self, forKey: key) - guard let url = URL(string: string) else { + let urlCandidate: URL? +#if canImport(FoundationEssentials) + urlCandidate = URL(string: string, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + urlCandidate = URL(string: string, encodingInvalidCharacters: false) + } else { + urlCandidate = URL(string: string) + } +#else + urlCandidate = URL(string: string) +#endif + guard let url = urlCandidate else { throw InconsistencyError( subjectName: key.stringValue, details: "If specified, must be a valid URL", @@ -26,7 +38,19 @@ extension KeyedDecodingContainerProtocol { return nil } - guard let url = URL(string: string) else { + let urlCandidate: URL? +#if canImport(FoundationEssentials) + urlCandidate = URL(string: string, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + urlCandidate = URL(string: string, encodingInvalidCharacters: false) + } else { + urlCandidate = URL(string: string) + } +#else + urlCandidate = URL(string: string) +#endif + guard let url = urlCandidate else { throw InconsistencyError( subjectName: key.stringValue, details: "If specified, must be a valid URL", diff --git a/Sources/OpenAPIKitCore/URLTemplate/URLTemplate.swift b/Sources/OpenAPIKitCore/URLTemplate/URLTemplate.swift index d588e6016..0bebbc73c 100644 --- a/Sources/OpenAPIKitCore/URLTemplate/URLTemplate.swift +++ b/Sources/OpenAPIKitCore/URLTemplate/URLTemplate.swift @@ -74,7 +74,19 @@ public struct URLTemplate: Hashable, RawRepresentable { /// Templated URLs with variables in them will not be valid URLs /// and are therefore guaranteed to return `nil`. public var url: URL? { - return URL(string: rawValue) + let urlCandidate: URL? +#if canImport(FoundationEssentials) + urlCandidate = URL(string: rawValue, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + urlCandidate = URL(string: rawValue, encodingInvalidCharacters: false) + } else { + urlCandidate = URL(string: rawValue) + } +#else + urlCandidate = URL(string: rawValue) +#endif + return urlCandidate } /// Get the names of all variables in the URL Template. diff --git a/Sources/OpenAPIKitCore/Utility/Container+DecodeURLAsString.swift b/Sources/OpenAPIKitCore/Utility/Container+DecodeURLAsString.swift index 787a05f60..a1402ddb8 100644 --- a/Sources/OpenAPIKitCore/Utility/Container+DecodeURLAsString.swift +++ b/Sources/OpenAPIKitCore/Utility/Container+DecodeURLAsString.swift @@ -10,7 +10,19 @@ import Foundation extension KeyedDecodingContainerProtocol { internal func decodeURLAsString(forKey key: Self.Key) throws -> URL { let string = try decode(String.self, forKey: key) - guard let url = URL(string: string) else { + let urlCandidate: URL? +#if canImport(FoundationEssentials) + urlCandidate = URL(string: string, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + urlCandidate = URL(string: string, encodingInvalidCharacters: false) + } else { + urlCandidate = URL(string: string) + } +#else + urlCandidate = URL(string: string) +#endif + guard let url = urlCandidate else { throw InconsistencyError( subjectName: key.stringValue, details: "If specified, must be a valid URL", @@ -25,7 +37,19 @@ extension KeyedDecodingContainerProtocol { return nil } - guard let url = URL(string: string) else { + let urlCandidate: URL? +#if canImport(FoundationEssentials) + urlCandidate = URL(string: string, encodingInvalidCharacters: false) +#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + urlCandidate = URL(string: string, encodingInvalidCharacters: false) + } else { + urlCandidate = URL(string: string) + } +#else + urlCandidate = URL(string: string) +#endif + guard let url = urlCandidate else { throw InconsistencyError( subjectName: key.stringValue, details: "If specified, must be a valid URL", diff --git a/Tests/OpenAPIKit30RealSpecSuite/GitHubAPITests.swift b/Tests/OpenAPIKit30RealSpecSuite/GitHubAPITests.swift index b174e7cd8..b0d0229f5 100644 --- a/Tests/OpenAPIKit30RealSpecSuite/GitHubAPITests.swift +++ b/Tests/OpenAPIKit30RealSpecSuite/GitHubAPITests.swift @@ -36,7 +36,10 @@ final class GitHubAPICampatibilityTests: XCTestCase { } } - func test_successfullyParsedDocument() { + func test_successfullyParsedDocument() throws { + #if os(Linux) && compiler(>=6.0) + throw XCTSkip("Swift bug causes CI failure currently (line 48): failed - The operation could not be completed. The file doesn’t exist.") + #endif switch githubAPI { case nil: XCTFail("Did not attempt to pull GitHub API documentation like expected.") diff --git a/Tests/OpenAPIKit30RealSpecSuite/GoogleBooksAPITests.swift b/Tests/OpenAPIKit30RealSpecSuite/GoogleBooksAPITests.swift index 21ec10907..6dd077c22 100644 --- a/Tests/OpenAPIKit30RealSpecSuite/GoogleBooksAPITests.swift +++ b/Tests/OpenAPIKit30RealSpecSuite/GoogleBooksAPITests.swift @@ -31,7 +31,10 @@ final class GoogleBooksAPICampatibilityTests: XCTestCase { } } - func test_successfullyParsedDocument() { + func test_successfullyParsedDocument() throws { + #if os(Linux) && compiler(>=6.0) + throw XCTSkip("Swift bug causes CI failure currently (line 48): failed - The operation could not be completed. The file doesn’t exist.") + #endif switch booksAPI { case nil: XCTFail("Did not attempt to pull Google Books API documentation like expected.") diff --git a/Tests/OpenAPIKit30RealSpecSuite/PetStoreAPITests.swift b/Tests/OpenAPIKit30RealSpecSuite/PetStoreAPITests.swift index f272d92a9..d33d12aa8 100644 --- a/Tests/OpenAPIKit30RealSpecSuite/PetStoreAPITests.swift +++ b/Tests/OpenAPIKit30RealSpecSuite/PetStoreAPITests.swift @@ -31,7 +31,10 @@ final class PetStoreAPICampatibilityTests: XCTestCase { } } - func test_successfullyParsedDocument() { + func test_successfullyParsedDocument() throws { + #if os(Linux) && compiler(>=6.0) + throw XCTSkip("Swift bug causes CI failure currently (line 48): failed - The operation could not be completed. The file doesn’t exist.") + #endif switch petStoreAPI { case nil: XCTFail("Did not attempt to pull Pet Store API documentation like expected.") diff --git a/Tests/OpenAPIKit30RealSpecSuite/TomTomAPITests.swift b/Tests/OpenAPIKit30RealSpecSuite/TomTomAPITests.swift index 045a76e32..00e338422 100644 --- a/Tests/OpenAPIKit30RealSpecSuite/TomTomAPITests.swift +++ b/Tests/OpenAPIKit30RealSpecSuite/TomTomAPITests.swift @@ -31,7 +31,10 @@ final class TomTomAPICampatibilityTests: XCTestCase { } } - func test_successfullyParsedDocument() { + func test_successfullyParsedDocument() throws { + #if os(Linux) && compiler(>=6.0) + throw XCTSkip("Swift bug causes CI failure currently (line 43): failed - The operation could not be completed. The file doesn’t exist.") + #endif switch tomtomAPI { case nil: XCTFail("Did not attempt to pull TomTom API documentation like expected.") diff --git a/Tests/OpenAPIKit30Tests/Validator/Validation+ConvenienceTests.swift b/Tests/OpenAPIKit30Tests/Validator/Validation+ConvenienceTests.swift index efc75ff38..91f154145 100644 --- a/Tests/OpenAPIKit30Tests/Validator/Validation+ConvenienceTests.swift +++ b/Tests/OpenAPIKit30Tests/Validator/Validation+ConvenienceTests.swift @@ -318,7 +318,7 @@ final class ValidationConvenienceTests: XCTestCase { ) } - func test_subject_unwrapAndlookup() { + func test_subject_unwrapAndlookup() throws { let v = Validation( description: "parameter is named test", check: \.name == "test" @@ -358,6 +358,13 @@ final class ValidationConvenienceTests: XCTestCase { XCTAssertFalse( unwrapAndLookup(\OpenAPI.Document.paths["/test"]?.pathItemValue?.parameters[2], thenApply: v)(context).isEmpty ) + #if os(macOS) + if #available(macOS 15.0, *) { + // this is just here because if #unavailable inside this block causes a compilation failure prior to Swift 5.6 on Linux :/ + } else { + throw XCTSkip("Skipping due to Swift/macOS bug resulting in error (line 368): throwing \"std::bad_alloc: std::bad_alloc\"") + } + #endif // nil keypath XCTAssertFalse( unwrapAndLookup(\OpenAPI.Document.paths["/test2"]?.pathItemValue?.parameters.first, thenApply: v)(context).isEmpty diff --git a/Tests/OpenAPIKitTests/Validator/Validation+ConvenienceTests.swift b/Tests/OpenAPIKitTests/Validator/Validation+ConvenienceTests.swift index f8102b83b..61f028303 100644 --- a/Tests/OpenAPIKitTests/Validator/Validation+ConvenienceTests.swift +++ b/Tests/OpenAPIKitTests/Validator/Validation+ConvenienceTests.swift @@ -318,7 +318,7 @@ final class ValidationConvenienceTests: XCTestCase { ) } - func test_subject_unwrapAndlookup() { + func test_subject_unwrapAndlookup() throws { let v = Validation( description: "parameter is named test", check: \.name == "test" @@ -358,6 +358,13 @@ final class ValidationConvenienceTests: XCTestCase { XCTAssertFalse( unwrapAndLookup(\OpenAPI.Document.paths["/test"]?.pathItemValue?.parameters[2], thenApply: v)(context).isEmpty ) + #if os(macOS) + if #available(macOS 15.0, *) { + // this is just here because if #unavailable inside this block causes a compilation failure prior to Swift 5.6 on Linux :/ + } else { + throw XCTSkip("Skipping due to Swift/macOS bug resulting in error (line 368): throwing \"std::bad_alloc: std::bad_alloc\"") + } + #endif // nil keypath XCTAssertFalse( unwrapAndLookup(\OpenAPI.Document.paths["/test2"]?.pathItemValue?.parameters.first, thenApply: v)(context).isEmpty