diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift index b6917860e..547993003 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift @@ -401,7 +401,12 @@ extension ServerFileTranslator { var caseCodeBlocks: [CodeBlock] = [] - let contentTypeHeaderValue = typedContent.content.contentType.headerValueForValidation + let contentType = typedContent.content.contentType + let contentTypeForServer = contentType.lowercasedTypeAndSubtype == "*/*" + ? ContentType.applicationOctetStream + : contentType + + let contentTypeHeaderValue = contentTypeForServer.headerValueForValidation let validateAcceptHeader: Expression = .try( .identifierPattern("converter").dot("validateAcceptIfPresent") .call([ @@ -411,9 +416,8 @@ extension ServerFileTranslator { ) caseCodeBlocks.append(.expression(validateAcceptHeader)) - let contentType = typedContent.content.contentType let extraBodyAssignArgs: [FunctionArgumentDescription] - if contentType.isMultipart { + if contentTypeForServer.isMultipart { extraBodyAssignArgs = try translateMultipartSerializerExtraArgumentsInServer(typedContent) } else { extraBodyAssignArgs = [] @@ -422,7 +426,7 @@ extension ServerFileTranslator { left: .identifierPattern("body"), right: .try( .identifierPattern("converter") - .dot("setResponseBodyAs\(contentType.codingStrategy.runtimeName)") + .dot("setResponseBodyAs\(contentTypeForServer.codingStrategy.runtimeName)") .call( [ .init(label: nil, expression: .identifierPattern("value")), @@ -432,7 +436,7 @@ extension ServerFileTranslator { ), .init( label: "contentType", - expression: .literal(contentType.headerValueForSending) + expression: .literal(contentTypeForServer.headerValueForSending) ), ] + extraBodyAssignArgs ) diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index d1e6341c0..0879a92d1 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -3604,6 +3604,123 @@ final class SnippetBasedReferenceTests: XCTestCase { ) } + + func testResponseWildcardContentTypeUsesApplicationOctetStreamInServer() throws { + try self.assertResponseInTypesClientServerTranslation( + """ + /download: + get: + operationId: download + responses: + '200': + description: ok + content: + '*/*': + schema: + type: string + format: binary + """, + output: """ + @frozen public enum Output: Sendable, Hashable { + public struct Ok: Sendable, Hashable { + @frozen public enum Body: Sendable, Hashable { + case any(OpenAPIRuntime.HTTPBody) + public var any: OpenAPIRuntime.HTTPBody { + get throws { + switch self { + case let .any(body): + return body + } + } + } + } + public var body: Operations.download.Output.Ok.Body + public init(body: Operations.download.Output.Ok.Body) { + self.body = body + } + } + case ok(Operations.download.Output.Ok) + public var ok: Operations.download.Output.Ok { + get throws { + switch self { + case let .ok(response): + return response + default: + try throwUnexpectedResponseStatus( + expectedStatus: "ok", + response: self + ) + } + } + } + case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) + } + """, + server: """ + { output, request in + switch output { + case let .ok(value): + suppressUnusedWarning(value) + var response = HTTPTypes.HTTPResponse(soar_statusCode: 200) + suppressMutabilityWarning(&response) + let body: OpenAPIRuntime.HTTPBody + switch value.body { + case let .any(value): + try converter.validateAcceptIfPresent( + "application/octet-stream", + in: request.headerFields + ) + body = try converter.setResponseBodyAsBinary( + value, + headerFields: &response.headerFields, + contentType: "application/octet-stream" + ) + } + return (response, body) + case let .undocumented(statusCode, _): + return (.init(soar_statusCode: statusCode), nil) + } + } + """, + client: """ + { response, responseBody in + switch response.status.code { + case 200: + let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) + let body: Operations.download.Output.Ok.Body + let chosenContentType = try converter.bestContentType( + received: contentType, + options: [ + "*/*" + ] + ) + switch chosenContentType { + case "*/*": + body = try converter.getResponseBodyAsBinary( + OpenAPIRuntime.HTTPBody.self, + from: responseBody, + transforming: { value in + .any(value) + } + ) + default: + preconditionFailure("bestContentType chose an invalid content type.") + } + return .ok(.init(body: body)) + default: + return .undocumented( + statusCode: response.status.code, + .init( + headerFields: response.headerFields, + body: responseBody + ) + ) + } + } + """ + ) + } + func testRequestMultipartBodyReferencedRequestBody() throws { try self.assertRequestInTypesClientServerTranslation( """