diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 0d392008d..81b5994ae 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -8,7 +8,7 @@ jobs: image: swift:5.8 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: swift test --enable-test-discovery --enable-code-coverage - id: analysis uses: mattpolzin/swift-codecov-action@0.7.5 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index f5732698b..7ed549dc4 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # TODO: replace the documentation generator with something that is still maintained. # - name: Generate Documentation diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9d7d6c320..983f1f9dd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,23 +13,15 @@ jobs: fail-fast: false matrix: image: - - swift:5.1-xenial - - swift:5.1-bionic - - swift:5.2-xenial - - swift:5.2-bionic - swift:5.2-focal - swift:5.2-centos8 - - swift:5.2-amazonlinux2 - - swift:5.3-xenial - - swift:5.3-bionic - swift:5.3-focal - swift:5.3-centos8 - - swift:5.3-amazonlinux2 # see below for 5.4, 5.5, etc. container: ${{ matrix.image }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run tests run: swift test --enable-test-discovery # 5.4 is separate because there was a bug in the compiler that caused @@ -40,15 +32,12 @@ jobs: fail-fast: false matrix: image: - - swift:5.4-xenial - - swift:5.4-bionic - swift:5.4-focal - swift:5.4-centos8 - - swift:5.4-amazonlinux2 container: ${{ matrix.image }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run tests (without test discovery flag) run: swift test -Xswiftc -Xfrontend -Xswiftc -sil-verify-none linux-5_5-plus: @@ -57,36 +46,23 @@ jobs: fail-fast: false matrix: image: - - swift:5.5-xenial - - swift:5.5-bionic - swift:5.5-focal - swift:5.5-centos8 - - swift:5.5-amazonlinux2 - - swift:5.6-bionic - swift:5.6-focal - - swift:5.6-amazonlinux2 - - swift:5.7-bionic - swift:5.7-focal - swift:5.7-jammy - - swift:5.7-amazonlinux2 - - swift:5.8-bionic - swift:5.8-focal - swift:5.8-jammy - - swift:5.8-amazonlinux2 - swift:5.9-focal - swift:5.9-jammy - - swift:5.9-amazonlinux2 - swift:5.10-focal - swift:5.10-jammy - - swift:5.10-amazonlinux2 - - swiftlang/swift:nightly-bionic - swiftlang/swift:nightly-focal - swiftlang/swift:nightly-jammy - - swiftlang/swift:nightly-amazonlinux2 container: ${{ matrix.image }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run tests run: swift test osx: @@ -97,6 +73,6 @@ jobs: with: xcode-version: latest - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run tests run: swift test --enable-test-discovery diff --git a/Sources/OpenAPIKit/Content/ContentEncoding.swift b/Sources/OpenAPIKit/Content/ContentEncoding.swift index f0a34425b..20814859a 100644 --- a/Sources/OpenAPIKit/Content/ContentEncoding.swift +++ b/Sources/OpenAPIKit/Content/ContentEncoding.swift @@ -14,33 +14,54 @@ extension OpenAPI.Content { public struct Encoding: Equatable { public typealias Style = OpenAPI.Parameter.SchemaContext.Style - public let contentType: OpenAPI.ContentType? + /// If an encoding object only contains 1 content type, it will be populated here. + /// Two or more content types will result in a null value here but the `contentTypes` + /// (plural) property will contain all content types specified. + /// + /// The singular `contentType` property is only provided for backwards compatibility and + /// using the plural `contentTypes` property should be preferred. + @available(*, deprecated, message: "use contentTypes instead") + public var contentType: OpenAPI.ContentType? { + guard let contentType = contentTypes.first, + contentTypes.count == 1 else { + return nil + } + return contentType + } + + public let contentTypes: [OpenAPI.ContentType] public let headers: OpenAPI.Header.Map? public let style: Style public let explode: Bool public let allowReserved: Bool + /// The singular `contentType` argument is only provided for backwards compatibility and + /// using the plural `contentTypes` argument should be preferred. public init( contentType: OpenAPI.ContentType? = nil, + contentTypes: [OpenAPI.ContentType] = [], headers: OpenAPI.Header.Map? = nil, style: Style = Self.defaultStyle, allowReserved: Bool = false ) { - self.contentType = contentType + self.contentTypes = contentTypes + [contentType].compactMap { $0 } self.headers = headers self.style = style self.explode = style.defaultExplode self.allowReserved = allowReserved } + /// The singular `contentType` argument is only provided for backwards compatibility and + /// using the plural `contentTypes` argument should be preferred. public init( contentType: OpenAPI.ContentType? = nil, + contentTypes: [OpenAPI.ContentType] = [], headers: OpenAPI.Header.Map? = nil, style: Style = Self.defaultStyle, explode: Bool, allowReserved: Bool = false ) { - self.contentType = contentType + self.contentTypes = contentTypes + [contentType].compactMap { $0 } self.headers = headers self.style = style self.explode = explode @@ -56,7 +77,12 @@ extension OpenAPI.Content.Encoding: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(contentType, forKey: .contentType) + if contentTypes.count > 0 { + let contentTypesString = contentTypes + .map(\.rawValue) + .joined(separator: ", ") + try container.encode(contentTypesString, forKey: .contentType) + } try container.encodeIfPresent(headers, forKey: .headers) if style != Self.defaultStyle { @@ -77,7 +103,16 @@ extension OpenAPI.Content.Encoding: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - contentType = try container.decodeIfPresent(OpenAPI.ContentType.self, forKey: .contentType) + let contentTypesString = try container.decodeIfPresent(String.self, forKey: .contentType) + if let contentTypesString = contentTypesString { + contentTypes = contentTypesString + .split(separator: ",") + .compactMap { string in + OpenAPI.ContentType.init(rawValue: string.trimmingCharacters(in: .whitespaces)) + } + } else { + contentTypes = [] + } headers = try container.decodeIfPresent(OpenAPI.Header.Map.self, forKey: .headers) diff --git a/Tests/OpenAPIKitTests/Content/ContentTests.swift b/Tests/OpenAPIKitTests/Content/ContentTests.swift index 357759b45..72afe5a15 100644 --- a/Tests/OpenAPIKitTests/Content/ContentTests.swift +++ b/Tests/OpenAPIKitTests/Content/ContentTests.swift @@ -570,6 +570,33 @@ extension ContentTests { XCTAssertEqual(encoding, OpenAPI.Content.Encoding(contentType: .csv)) } + func test_encoding_multiple_contentTypes_encode() throws { + let encoding = OpenAPI.Content.Encoding(contentTypes: [.csv, .json]) + + let encodedEncoding = try! orderUnstableTestStringFromEncoding(of: encoding) + + assertJSONEquivalent( + encodedEncoding, + """ + { + "contentType" : "text\\/csv, application\\/json" + } + """ + ) + } + + func test_encoding_multiple_contentTypes_decode() throws { + let encodingData = + """ + { + "contentType": "text/csv, application/json" + } + """.data(using: .utf8)! + let encoding = try! orderUnstableDecode(OpenAPI.Content.Encoding.self, from: encodingData) + + XCTAssertEqual(encoding, OpenAPI.Content.Encoding(contentTypes: [.csv, .json])) + } + func test_encoding_headers_encode() throws { let encoding = OpenAPI.Content.Encoding(headers: [ "X-CustomThing": .init(OpenAPI.Header(schema: .string))