Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Sources/OpenAPIKit/Content/ContentEncoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ extension OpenAPI.Content {
/// where the values are anything codable.
public var vendorExtensions: [String: AnyCodable]

/// The singular `contentType` argument is only provided for backwards compatibility and
/// using the plural `contentTypes` argument should be preferred.
public init(
contentTypes: [OpenAPI.ContentType] = [],
headers: OpenAPI.Header.Map? = nil,
Expand Down
72 changes: 65 additions & 7 deletions Sources/OpenAPIKit30/Content/ContentEncoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension OpenAPI.Content {
public struct Encoding: Equatable, CodableVendorExtendable, Sendable {
public typealias Style = OpenAPI.Parameter.SchemaContext.Style

public let contentType: OpenAPI.ContentType?
public let contentTypes: [OpenAPI.ContentType]
public let headers: OpenAPI.Header.Map?
public let style: Style
public let explode: Bool
Expand All @@ -27,30 +27,74 @@ extension OpenAPI.Content {
/// where the values are anything codable.
public var vendorExtensions: [String: AnyCodable]

/// Get the content type if only one is specified.
///
/// - Important: This is **soft-deprecated**. Use the `contentTypes`
/// property instead.
public var contentType: OpenAPI.ContentType? {
guard contentTypes.count == 1 else { return nil }
return contentTypes.first
}

/// - Important: This is **soft-deprecated**. Use the initializer
/// that takes a `contentTypes` array instead.
public init(
contentType: OpenAPI.ContentType?,
headers: OpenAPI.Header.Map? = nil,
style: Style = Self.defaultStyle,
allowReserved: Bool = false,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.contentTypes = contentType.map { [$0] } ?? []
self.headers = headers
self.style = style
self.explode = style.defaultExplode
self.allowReserved = allowReserved
self.vendorExtensions = vendorExtensions
}

public init(
contentType: OpenAPI.ContentType? = nil,
contentTypes: [OpenAPI.ContentType] = [],
headers: OpenAPI.Header.Map? = nil,
style: Style = Self.defaultStyle,
allowReserved: Bool = false,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.contentType = contentType
self.contentTypes = contentTypes
self.headers = headers
self.style = style
self.explode = style.defaultExplode
self.allowReserved = allowReserved
self.vendorExtensions = vendorExtensions
}

/// - Important: This is **soft-deprecated**. Use the initializer
/// that takes a `contentTypes` array instead.
public init(
contentType: OpenAPI.ContentType? = nil,
contentType: OpenAPI.ContentType?,
headers: OpenAPI.Header.Map? = nil,
style: Style = Self.defaultStyle,
explode: Bool,
allowReserved: Bool = false,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.contentType = contentType
self.contentTypes = contentType.map { [$0] } ?? []
self.headers = headers
self.style = style
self.explode = explode
self.allowReserved = allowReserved
self.vendorExtensions = vendorExtensions
}

public init(
contentTypes: [OpenAPI.ContentType] = [],
headers: OpenAPI.Header.Map? = nil,
style: Style = Self.defaultStyle,
explode: Bool,
allowReserved: Bool = false,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.contentTypes = contentTypes
self.headers = headers
self.style = style
self.explode = explode
Expand All @@ -67,7 +111,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 {
Expand All @@ -90,7 +139,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 {
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)

Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenAPIKitCompat/Compat30To31.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ extension OpenAPIKit30.OpenAPI.Parameter.SchemaContext.Style: To31 {
extension OpenAPIKit30.OpenAPI.Content.Encoding: To31 {
fileprivate func to31() -> OpenAPIKit.OpenAPI.Content.Encoding {
OpenAPIKit.OpenAPI.Content.Encoding(
contentTypes: [contentType].compactMap { $0 },
contentTypes: contentTypes,
headers: headers?.mapValues(eitherRefTo31),
style: style.to31(),
explode: explode,
Expand Down
48 changes: 38 additions & 10 deletions Tests/OpenAPIKit30Tests/Content/ContentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ final class ContentTests: XCTestCase {
example: nil,
encoding: [
"hello": .init(
contentType: .json,
contentTypes: [.json],
headers: [
"world": .init(OpenAPI.Header(schemaOrContent: .init(.header(.string))))
],
Expand Down Expand Up @@ -358,7 +358,7 @@ extension ContentTests {
func test_encodingAndSchema_encode() {
let content = OpenAPI.Content(
schema: .init(.string),
encoding: ["json": .init(contentType: .json)]
encoding: ["json": .init(contentTypes: [.json])]
)
let encodedContent = try! orderUnstableTestStringFromEncoding(of: content)

Expand Down Expand Up @@ -400,7 +400,7 @@ extension ContentTests {
content,
OpenAPI.Content(
schema: .init(.string),
encoding: ["json": .init(contentType: .json)]
encoding: ["json": .init(contentTypes: [.json])]
)
)
}
Expand Down Expand Up @@ -503,18 +503,18 @@ extension ContentTests {
func test_encodingInit() {
let _ = OpenAPI.Content.Encoding()

let _ = OpenAPI.Content.Encoding(contentType: .json)
let _ = OpenAPI.Content.Encoding(contentTypes: [.json])

let _ = OpenAPI.Content.Encoding(headers: ["special": .a(.external(URL(string: "hello.yml")!))])

let _ = OpenAPI.Content.Encoding(allowReserved: true)

let _ = OpenAPI.Content.Encoding(contentType: .form,
let _ = OpenAPI.Content.Encoding(contentTypes: [.form],
headers: ["special": .a(.external(URL(string: "hello.yml")!))],
allowReserved: true)
let _ = OpenAPI.Content.Encoding(contentType: .json,
let _ = OpenAPI.Content.Encoding(contentTypes: [.json],
style: .form)
let _ = OpenAPI.Content.Encoding(contentType: .json,
let _ = OpenAPI.Content.Encoding(contentTypes: [.json],
style: .form,
explode: true)
}
Expand Down Expand Up @@ -545,7 +545,7 @@ extension ContentTests {
}

func test_encoding_contentType_encode() throws {
let encoding = OpenAPI.Content.Encoding(contentType: .csv)
let encoding = OpenAPI.Content.Encoding(contentTypes: [.csv])

let encodedEncoding = try! orderUnstableTestStringFromEncoding(of: encoding)

Expand All @@ -568,7 +568,34 @@ extension ContentTests {
""".data(using: .utf8)!
let encoding = try! orderUnstableDecode(OpenAPI.Content.Encoding.self, from: encodingData)

XCTAssertEqual(encoding, OpenAPI.Content.Encoding(contentType: .csv))
XCTAssertEqual(encoding, OpenAPI.Content.Encoding(contentTypes: [.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 {
Expand Down Expand Up @@ -711,7 +738,7 @@ extension ContentTests {

func test_encoding_vendorExtensions_encode() throws {
let encoding = OpenAPI.Content.Encoding(
contentType: .json,
contentTypes: [.json],
vendorExtensions: [
"x-custom": "value",
"x-nested": .init(["key": 123])
Expand Down Expand Up @@ -747,6 +774,7 @@ extension ContentTests {
""".data(using: .utf8)!
let encoding = try orderUnstableDecode(OpenAPI.Content.Encoding.self, from: encodingData)

XCTAssertEqual(encoding.contentTypes, [.json])
XCTAssertEqual(encoding.contentType, .json)
XCTAssertEqual(encoding.vendorExtensions["x-custom"]?.value as? String, "value")
XCTAssertEqual((encoding.vendorExtensions["x-nested"]?.value as? [String: Int])?["key"], 123)
Expand Down
27 changes: 26 additions & 1 deletion Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,31 @@ final class DocumentConversionTests: XCTestCase {
try assertEqualNewToOld(newParameter2, parameter2)
}

func test_ContentEncoding() throws {
let encoding1 = OpenAPIKit30.OpenAPI.Content.Encoding(
contentTypes: [.json, .txt], headers: ["Content-Type": .header(.init(schema: .string))],
style: .form, allowReserved: false)
let encoding2 = OpenAPIKit30.OpenAPI.Content.Encoding(contentTypes: [.json], )

let content = OpenAPIKit30.OpenAPI.Content(
schema: .string, encoding: ["one": encoding1, "two": encoding2])

let oldDoc = OpenAPIKit30.OpenAPI.Document(
info: .init(title: "test", version: "1.0.0"),
servers: [],
paths: [
"/root": .init(
summary: "path one", description: "the first path", servers: [], parameters: [],
get: .init(requestBody: .init(content: [.json: content]), responses: [:]))
],
components: .noComponents
)

let newDoc = oldDoc.convert(to: .v3_2_0)

try assertEqualNewToOld(newDoc, oldDoc)
}

// TODO: more tests
}

Expand Down Expand Up @@ -1302,7 +1327,7 @@ fileprivate func assertEqualNewToOld(_ newExample: OpenAPIKit.OpenAPI.Example, _
}

fileprivate func assertEqualNewToOld(_ newEncoding: OpenAPIKit.OpenAPI.Content.Encoding, _ oldEncoding: OpenAPIKit30.OpenAPI.Content.Encoding) throws {
XCTAssertEqual(newEncoding.contentTypes.first, oldEncoding.contentType)
XCTAssertEqual(newEncoding.contentTypes, oldEncoding.contentTypes)
if let newEncodingHeaders = newEncoding.headers {
let oldEncodingHeaders = try XCTUnwrap(oldEncoding.headers)
for ((newKey, newHeader), (oldKey, oldHeader)) in zip(newEncodingHeaders, oldEncodingHeaders) {
Expand Down