From 25fe2d416b4670783fe2944be631e98ab40895cf Mon Sep 17 00:00:00 2001 From: Brandon Bloom Date: Sat, 17 Jan 2026 16:59:29 -0800 Subject: [PATCH 1/3] Accept: match structured-syntax +json --- .../Conversion/Converter+Server.swift | 40 ++++++++++++++++++- .../Conversion/Test_Converter+Server.swift | 7 ++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index a3088bd3..dd244990 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -44,6 +44,9 @@ extension Converter { /// - substring: Expected content type, for example "application/json". /// - headerFields: Header fields in which to look for "Accept". /// Also supports wildcars, such as "application/\*" and "\*/\*". + /// Additionally, supports matching structured syntax suffixes (RFC 6839), + /// for example `Accept: application/json` is treated as compatible with + /// `Content-Type: application/problem+json`. /// - Throws: An error if the "Accept" header is present but incompatible with the provided content type, /// or if there are issues parsing the header. public func validateAcceptIfPresent(_ substring: String, in headerFields: HTTPFields) throws { @@ -495,8 +498,41 @@ fileprivate extension OpenAPIMIMEType { return acceptType.lowercased() == substringType.lowercased() case (.concrete(let acceptType, let acceptSubtype), .concrete(let substringType, let substringSubtype)): // Accept: type/subtype -- The content-type should match the concrete type. - return acceptType.lowercased() == substringType.lowercased() - && acceptSubtype.lowercased() == substringSubtype.lowercased() + let acceptTypeLowercased = acceptType.lowercased() + let substringTypeLowercased = substringType.lowercased() + guard acceptTypeLowercased == substringTypeLowercased else { return false } + + let acceptSubtypeLowercased = acceptSubtype.lowercased() + let substringSubtypeLowercased = substringSubtype.lowercased() + + // Exact match. + if acceptSubtypeLowercased == substringSubtypeLowercased { return true } + + // RFC 6839 structured syntax suffix matching (e.g. application/problem+json). + if let structuredSyntaxSuffix = structuredSyntaxSuffix(of: substringSubtypeLowercased), + structuredSyntaxSuffix == acceptSubtypeLowercased + { return true } + + // Accept: application/*+json matching (and treating it as also matching application/json). + if let structuredSyntaxWildcardSuffix = structuredSyntaxWildcardSuffix(of: acceptSubtypeLowercased) { + return substringSubtypeLowercased == structuredSyntaxWildcardSuffix + || structuredSyntaxSuffix(of: substringSubtypeLowercased) == structuredSyntaxWildcardSuffix + } + return false } } + + private func structuredSyntaxSuffix(of subtype: String) -> String? { + guard let plusIndex = subtype.lastIndex(of: "+") else { return nil } + let suffixStart = subtype.index(after: plusIndex) + guard suffixStart < subtype.endIndex else { return nil } + return String(subtype[suffixStart...]) + } + + private func structuredSyntaxWildcardSuffix(of acceptSubtype: String) -> String? { + guard acceptSubtype.hasPrefix("*+") else { return nil } + let suffixStart = acceptSubtype.index(acceptSubtype.startIndex, offsetBy: 2) + guard suffixStart < acceptSubtype.endIndex else { return nil } + return String(acceptSubtype[suffixStart...]) + } } diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift index 3d956bb2..d956f671 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift @@ -35,6 +35,8 @@ final class Test_ServerConverterExtensions: Test_Runtime { let wildcard: HTTPFields = [.accept: "*/*"] let partialWildcard: HTTPFields = [.accept: "text/*"] let short: HTTPFields = [.accept: "text/plain"] + let json: HTTPFields = [.accept: "application/json"] + let anyJsonStructuredSyntax: HTTPFields = [.accept: "application/*+json"] let long: HTTPFields = [ .accept: "text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8" ] @@ -54,6 +56,11 @@ final class Test_ServerConverterExtensions: Test_Runtime { (short, "text/plain", true), (short, "application/json", false), (short, "application/*", false), (short, "*/*", false), + // RFC 6839 structured syntax suffix matching (common with RFC 7807 Problem Details): + // If response is application/problem+json, treat Accept: application/json as compatible. + (json, "application/problem+json", true), + (anyJsonStructuredSyntax, "application/problem+json", true), + // A bunch of acceptable content types (long, "text/html", true), (long, "application/xhtml+xml", true), (long, "application/xml", true), (long, "image/webp", true), (long, "application/json", true), From 30fcc7964ba74e6ab66b74478098b0bdb242d21b Mon Sep 17 00:00:00 2001 From: Brandon Bloom Date: Sat, 28 Feb 2026 10:26:41 -0800 Subject: [PATCH 2/3] Accept: integrate +json in client content type matching --- .../OpenAPIRuntime/Base/OpenAPIMIMEType.swift | 44 +++++++++++++++++-- .../Conversion/Converter+Common.swift | 25 ++++++++++- .../Conversion/Converter+Server.swift | 20 ++------- .../Base/Test_OpenAPIMIMEType.swift | 30 +++++++++++++ .../Conversion/Test_Converter+Common.swift | 12 +++++ 5 files changed, 108 insertions(+), 23 deletions(-) diff --git a/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift b/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift index 3d7adef8..54c70757 100644 --- a/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift +++ b/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift @@ -210,10 +210,28 @@ extension OpenAPIMIMEType { guard receivedType.lowercased() == expectedType.lowercased() else { return .incompatible(.type) } return .subtypeWildcard case .concrete(let expectedType, let expectedSubtype): - guard - receivedType.lowercased() == expectedType.lowercased() - && receivedSubtype.lowercased() == expectedSubtype.lowercased() - else { return .incompatible(.subtype) } + let receivedTypeLowercased = receivedType.lowercased() + let expectedTypeLowercased = expectedType.lowercased() + guard receivedTypeLowercased == expectedTypeLowercased else { return .incompatible(.type) } + + let receivedSubtypeLowercased = receivedSubtype.lowercased() + let expectedSubtypeLowercased = expectedSubtype.lowercased() + let isExactSubtypeMatch = receivedSubtypeLowercased == expectedSubtypeLowercased + if !isExactSubtypeMatch { + let isStructuredSyntaxSuffixMatch = + Self.structuredSyntaxSuffix(of: receivedSubtypeLowercased) == expectedSubtypeLowercased + let isStructuredSyntaxWildcardMatch: Bool + if let structuredSyntaxWildcardSuffix = Self.structuredSyntaxWildcardSuffix(of: expectedSubtypeLowercased) { + isStructuredSyntaxWildcardMatch = + receivedSubtypeLowercased == structuredSyntaxWildcardSuffix + || Self.structuredSyntaxSuffix(of: receivedSubtypeLowercased) == structuredSyntaxWildcardSuffix + } else { + isStructuredSyntaxWildcardMatch = false + } + guard isStructuredSyntaxSuffixMatch || isStructuredSyntaxWildcardMatch else { + return .incompatible(.subtype) + } + } // A full concrete match, so also check parameters. // The rule is: @@ -244,4 +262,22 @@ extension OpenAPIMIMEType { return .typeAndSubtype(matchedParameterCount: matchedParameterCount) } } + + /// Returns the structured syntax suffix component of a subtype, if present. + /// For example, returns `"json"` for `"problem+json"`. + static func structuredSyntaxSuffix(of subtype: String) -> String? { + guard let plusIndex = subtype.lastIndex(of: "+") else { return nil } + let suffixStart = subtype.index(after: plusIndex) + guard suffixStart < subtype.endIndex else { return nil } + return String(subtype[suffixStart...]) + } + + /// Returns the structured syntax suffix of a wildcard subtype, if present. + /// For example, returns `"json"` for `"*+json"`. + static func structuredSyntaxWildcardSuffix(of subtype: String) -> String? { + guard subtype.hasPrefix("*+") else { return nil } + let suffixStart = subtype.index(subtype.startIndex, offsetBy: 2) + guard suffixStart < subtype.endIndex else { return nil } + return String(subtype[suffixStart...]) + } } diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift index 73f8fecb..9255b289 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift @@ -46,6 +46,8 @@ extension Converter { // either. return options[0] } + let receivedTypeLowercased = receivedType.lowercased() + let receivedSubtypeLowercased = receivedSubtype.lowercased() let evaluatedOptions = try options.map { stringOption in guard let parsedOption = OpenAPIMIMEType(stringOption) else { throw RuntimeError.invalidExpectedContentType(stringOption) @@ -56,10 +58,29 @@ extension Converter { receivedParameters: received.parameters, against: parsedOption ) - return (contentType: stringOption, match: match) + let isExactSubtypeMatch: Bool + if case let .concrete(type: optionType, subtype: optionSubtype) = parsedOption.kind { + isExactSubtypeMatch = + optionType.lowercased() == receivedTypeLowercased + && optionSubtype.lowercased() == receivedSubtypeLowercased + } else { + isExactSubtypeMatch = false + } + return (contentType: stringOption, match: match, isExactSubtypeMatch: isExactSubtypeMatch) + } + func rankingTuple( + _ option: (contentType: String, match: OpenAPIMIMEType.Match, isExactSubtypeMatch: Bool) + ) -> (Int, Int, Int) { + switch option.match { + case .incompatible: return (0, 0, 0) + case .wildcard: return (1, 0, 0) + case .subtypeWildcard: return (2, 0, 0) + case .typeAndSubtype(let matchedParameterCount): + return (3, option.isExactSubtypeMatch ? 1 : 0, matchedParameterCount) + } } // The force unwrap is safe, we only get here if the array is not empty. - let bestOption = evaluatedOptions.max { a, b in a.match.score < b.match.score }! + let bestOption = evaluatedOptions.max { a, b in rankingTuple(a) < rankingTuple(b) }! let bestContentType = bestOption.contentType if case .incompatible = bestOption.match { throw RuntimeError.unexpectedContentTypeHeader( diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index dd244990..0fcdd0a8 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -509,30 +509,16 @@ fileprivate extension OpenAPIMIMEType { if acceptSubtypeLowercased == substringSubtypeLowercased { return true } // RFC 6839 structured syntax suffix matching (e.g. application/problem+json). - if let structuredSyntaxSuffix = structuredSyntaxSuffix(of: substringSubtypeLowercased), + if let structuredSyntaxSuffix = Self.structuredSyntaxSuffix(of: substringSubtypeLowercased), structuredSyntaxSuffix == acceptSubtypeLowercased { return true } // Accept: application/*+json matching (and treating it as also matching application/json). - if let structuredSyntaxWildcardSuffix = structuredSyntaxWildcardSuffix(of: acceptSubtypeLowercased) { + if let structuredSyntaxWildcardSuffix = Self.structuredSyntaxWildcardSuffix(of: acceptSubtypeLowercased) { return substringSubtypeLowercased == structuredSyntaxWildcardSuffix - || structuredSyntaxSuffix(of: substringSubtypeLowercased) == structuredSyntaxWildcardSuffix + || Self.structuredSyntaxSuffix(of: substringSubtypeLowercased) == structuredSyntaxWildcardSuffix } return false } } - - private func structuredSyntaxSuffix(of subtype: String) -> String? { - guard let plusIndex = subtype.lastIndex(of: "+") else { return nil } - let suffixStart = subtype.index(after: plusIndex) - guard suffixStart < subtype.endIndex else { return nil } - return String(subtype[suffixStart...]) - } - - private func structuredSyntaxWildcardSuffix(of acceptSubtype: String) -> String? { - guard acceptSubtype.hasPrefix("*+") else { return nil } - let suffixStart = acceptSubtype.index(acceptSubtype.startIndex, offsetBy: 2) - guard suffixStart < acceptSubtype.endIndex else { return nil } - return String(acceptSubtype[suffixStart...]) - } } diff --git a/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift b/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift index a28ec47d..ffc66314 100644 --- a/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift +++ b/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift @@ -103,6 +103,7 @@ final class Test_OpenAPIMIMEType: Test_Runtime { let jsonWith2Params = OpenAPIMIMEType("application/json; charset=utf-8; version=1")! let jsonWith1Param = OpenAPIMIMEType("application/json; charset=utf-8")! let json = OpenAPIMIMEType("application/json")! + let anyJsonStructuredSyntax = OpenAPIMIMEType("application/*+json")! let fullWildcard = OpenAPIMIMEType("*/*")! let subtypeWildcard = OpenAPIMIMEType("application/*")! @@ -130,5 +131,34 @@ final class Test_OpenAPIMIMEType: Test_Runtime { testJSONWith2Params(against: json, expected: .typeAndSubtype(matchedParameterCount: 0)) testJSONWith2Params(against: subtypeWildcard, expected: .subtypeWildcard) testJSONWith2Params(against: fullWildcard, expected: .wildcard) + + testCase( + receivedType: "application", + receivedSubtype: "problem+json", + receivedParameters: [:], + against: json, + expected: .typeAndSubtype(matchedParameterCount: 0) + ) + testCase( + receivedType: "application", + receivedSubtype: "json", + receivedParameters: [:], + against: anyJsonStructuredSyntax, + expected: .typeAndSubtype(matchedParameterCount: 0) + ) + testCase( + receivedType: "application", + receivedSubtype: "problem+json", + receivedParameters: [:], + against: anyJsonStructuredSyntax, + expected: .typeAndSubtype(matchedParameterCount: 0) + ) + testCase( + receivedType: "application", + receivedSubtype: "problem+xml", + receivedParameters: [:], + against: json, + expected: .incompatible(.subtype) + ) } } diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift index 85d04f25..a35be083 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift @@ -82,6 +82,17 @@ final class Test_CommonConverterExtensions: Test_Runtime { try testCase(received: "application/json; charset=utf-8; version=1", options: ["*/*"], expected: "*/*") try testCase(received: "image/png", options: ["image/*", "*/*"], expected: "image/*") + try testCase(received: "application/problem+json", options: ["application/json"], expected: "application/json") + try testCase( + received: "application/problem+json", + options: ["application/json", "application/problem+json"], + expected: "application/problem+json" + ) + try testCase( + received: "application/problem+json; charset=utf-8; version=1", + options: ["application/json", "application/json; charset=utf-8"], + expected: "application/json; charset=utf-8" + ) XCTAssertThrowsError( try testCase(received: "text/csv", options: ["text/html", "application/json"], expected: "-") ) { error in @@ -104,6 +115,7 @@ final class Test_CommonConverterExtensions: Test_Runtime { } try testCase(received: nil, match: "application/json") try testCase(received: "application/json", match: "application/json") + try testCase(received: "application/problem+json", match: "application/json") try testCase(received: "application/json", match: "application/*") try testCase(received: "application/json", match: "*/*") } From 8ba96d9026a270c89d847f9ddfb4756dbe0c03ac Mon Sep 17 00:00:00 2001 From: Brandon Bloom Date: Sun, 29 Mar 2026 19:18:37 -0700 Subject: [PATCH 3/3] make +json content type matching symmetric --- .../OpenAPIRuntime/Base/OpenAPIMIMEType.swift | 38 +++++++++++-------- .../Conversion/Converter+Server.swift | 16 +------- .../Base/Test_OpenAPIMIMEType.swift | 15 ++++++++ .../Conversion/Test_Converter+Common.swift | 3 ++ .../Conversion/Test_Converter+Server.swift | 3 ++ 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift b/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift index 54c70757..9fb5035f 100644 --- a/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift +++ b/Sources/OpenAPIRuntime/Base/OpenAPIMIMEType.swift @@ -216,21 +216,8 @@ extension OpenAPIMIMEType { let receivedSubtypeLowercased = receivedSubtype.lowercased() let expectedSubtypeLowercased = expectedSubtype.lowercased() - let isExactSubtypeMatch = receivedSubtypeLowercased == expectedSubtypeLowercased - if !isExactSubtypeMatch { - let isStructuredSyntaxSuffixMatch = - Self.structuredSyntaxSuffix(of: receivedSubtypeLowercased) == expectedSubtypeLowercased - let isStructuredSyntaxWildcardMatch: Bool - if let structuredSyntaxWildcardSuffix = Self.structuredSyntaxWildcardSuffix(of: expectedSubtypeLowercased) { - isStructuredSyntaxWildcardMatch = - receivedSubtypeLowercased == structuredSyntaxWildcardSuffix - || Self.structuredSyntaxSuffix(of: receivedSubtypeLowercased) == structuredSyntaxWildcardSuffix - } else { - isStructuredSyntaxWildcardMatch = false - } - guard isStructuredSyntaxSuffixMatch || isStructuredSyntaxWildcardMatch else { - return .incompatible(.subtype) - } + guard Self.subtypesMatch(receivedSubtypeLowercased, expectedSubtypeLowercased) else { + return .incompatible(.subtype) } // A full concrete match, so also check parameters. @@ -280,4 +267,25 @@ extension OpenAPIMIMEType { guard suffixStart < subtype.endIndex else { return nil } return String(subtype[suffixStart...]) } + + /// Returns whether two lowercased subtypes are compatible. + /// Supports exact matches, same-type structured syntax suffix matches + /// in either direction, and wildcard structured syntax suffixes. + static func subtypesMatch(_ lhs: String, _ rhs: String) -> Bool { + if lhs == rhs { return true } + + if let lhsStructuredSyntaxWildcardSuffix = Self.structuredSyntaxWildcardSuffix(of: lhs) { + return rhs == lhsStructuredSyntaxWildcardSuffix + || Self.structuredSyntaxSuffix(of: rhs) == lhsStructuredSyntaxWildcardSuffix + } + if let rhsStructuredSyntaxWildcardSuffix = Self.structuredSyntaxWildcardSuffix(of: rhs) { + return lhs == rhsStructuredSyntaxWildcardSuffix + || Self.structuredSyntaxSuffix(of: lhs) == rhsStructuredSyntaxWildcardSuffix + } + + let lhsStructuredSyntaxSuffix = Self.structuredSyntaxSuffix(of: lhs) + let rhsStructuredSyntaxSuffix = Self.structuredSyntaxSuffix(of: rhs) + return lhsStructuredSyntaxSuffix == rhs || rhsStructuredSyntaxSuffix == lhs + || (lhsStructuredSyntaxSuffix != nil && lhsStructuredSyntaxSuffix == rhsStructuredSyntaxSuffix) + } } diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index 0fcdd0a8..3a38f006 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -504,21 +504,7 @@ fileprivate extension OpenAPIMIMEType { let acceptSubtypeLowercased = acceptSubtype.lowercased() let substringSubtypeLowercased = substringSubtype.lowercased() - - // Exact match. - if acceptSubtypeLowercased == substringSubtypeLowercased { return true } - - // RFC 6839 structured syntax suffix matching (e.g. application/problem+json). - if let structuredSyntaxSuffix = Self.structuredSyntaxSuffix(of: substringSubtypeLowercased), - structuredSyntaxSuffix == acceptSubtypeLowercased - { return true } - - // Accept: application/*+json matching (and treating it as also matching application/json). - if let structuredSyntaxWildcardSuffix = Self.structuredSyntaxWildcardSuffix(of: acceptSubtypeLowercased) { - return substringSubtypeLowercased == structuredSyntaxWildcardSuffix - || Self.structuredSyntaxSuffix(of: substringSubtypeLowercased) == structuredSyntaxWildcardSuffix - } - return false + return Self.subtypesMatch(acceptSubtypeLowercased, substringSubtypeLowercased) } } } diff --git a/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift b/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift index ffc66314..3fce710f 100644 --- a/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift +++ b/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIMIMEType.swift @@ -103,6 +103,7 @@ final class Test_OpenAPIMIMEType: Test_Runtime { let jsonWith2Params = OpenAPIMIMEType("application/json; charset=utf-8; version=1")! let jsonWith1Param = OpenAPIMIMEType("application/json; charset=utf-8")! let json = OpenAPIMIMEType("application/json")! + let problemJSON = OpenAPIMIMEType("application/problem+json")! let anyJsonStructuredSyntax = OpenAPIMIMEType("application/*+json")! let fullWildcard = OpenAPIMIMEType("*/*")! let subtypeWildcard = OpenAPIMIMEType("application/*")! @@ -139,6 +140,13 @@ final class Test_OpenAPIMIMEType: Test_Runtime { against: json, expected: .typeAndSubtype(matchedParameterCount: 0) ) + testCase( + receivedType: "application", + receivedSubtype: "json", + receivedParameters: [:], + against: problemJSON, + expected: .typeAndSubtype(matchedParameterCount: 0) + ) testCase( receivedType: "application", receivedSubtype: "json", @@ -160,5 +168,12 @@ final class Test_OpenAPIMIMEType: Test_Runtime { against: json, expected: .incompatible(.subtype) ) + testCase( + receivedType: "text", + receivedSubtype: "foo+json", + receivedParameters: [:], + against: json, + expected: .incompatible(.type) + ) } } diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift index a35be083..e7508d2c 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift @@ -83,6 +83,7 @@ final class Test_CommonConverterExtensions: Test_Runtime { try testCase(received: "image/png", options: ["image/*", "*/*"], expected: "image/*") try testCase(received: "application/problem+json", options: ["application/json"], expected: "application/json") + try testCase(received: "application/json", options: ["application/problem+json"], expected: "application/problem+json") try testCase( received: "application/problem+json", options: ["application/json", "application/problem+json"], @@ -116,8 +117,10 @@ final class Test_CommonConverterExtensions: Test_Runtime { try testCase(received: nil, match: "application/json") try testCase(received: "application/json", match: "application/json") try testCase(received: "application/problem+json", match: "application/json") + try testCase(received: "application/json", match: "application/problem+json") try testCase(received: "application/json", match: "application/*") try testCase(received: "application/json", match: "*/*") + XCTAssertThrowsError(try testCase(received: "text/foo+json", match: "application/json")) } func testExtractContentDispositionNameAndFilename() throws { diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift index d956f671..cee119a1 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift @@ -59,7 +59,10 @@ final class Test_ServerConverterExtensions: Test_Runtime { // RFC 6839 structured syntax suffix matching (common with RFC 7807 Problem Details): // If response is application/problem+json, treat Accept: application/json as compatible. (json, "application/problem+json", true), + (json, "application/json", true), + (anyJsonStructuredSyntax, "application/json", true), (anyJsonStructuredSyntax, "application/problem+json", true), + (json, "text/foo+json", false), // A bunch of acceptable content types (long, "text/html", true), (long, "application/xhtml+xml", true), (long, "application/xml", true),