Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ContentType + improve content type matching #173

Merged
merged 6 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -162,30 +162,26 @@ extension FileTranslator {
)
}
let chosenContent: (SchemaContent, OpenAPI.Content)?
if let (contentKey, contentValue) = map.first(where: { $0.key.isJSON }),
if let (contentKey, contentValue) = map.first(where: { $0.key.isJSON }) {
let contentType = ContentType(contentKey.typeAndSubtype)
{
chosenContent = (
.init(
contentType: contentType,
schema: contentValue.schema
),
contentValue
)
} else if let (contentKey, contentValue) = map.first(where: { $0.key.isText }),
} else if let (contentKey, contentValue) = map.first(where: { $0.key.isText }) {
let contentType = ContentType(contentKey.typeAndSubtype)
{
chosenContent = (
.init(
contentType: contentType,
schema: .b(.string)
),
contentValue
)
} else if !excludeBinary,
let (contentKey, contentValue) = map.first(where: { $0.key.isBinary }),
} else if !excludeBinary, let (contentKey, contentValue) = map.first(where: { $0.key.isBinary }) {
let contentType = ContentType(contentKey.typeAndSubtype)
{
chosenContent = (
.init(
contentType: contentType,
Expand Down Expand Up @@ -234,9 +230,8 @@ extension FileTranslator {
excludeBinary: Bool = false,
foundIn: String
) -> SchemaContent? {
if contentKey.isJSON,
if contentKey.isJSON {
let contentType = ContentType(contentKey.typeAndSubtype)
{
diagnostics.emitUnsupportedIfNotNil(
contentValue.encoding,
"Custom encoding for JSON content",
Expand All @@ -247,18 +242,15 @@ extension FileTranslator {
schema: contentValue.schema
)
}
if contentKey.isText,
if contentKey.isText {
let contentType = ContentType(contentKey.typeAndSubtype)
{
return .init(
contentType: contentType,
schema: .b(.string)
)
}
if !excludeBinary,
contentKey.isBinary,
if !excludeBinary, contentKey.isBinary {
let contentType = ContentType(contentKey.typeAndSubtype)
{
return .init(
contentType: contentType,
schema: .b(.string(format: .binary))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ extension FileTranslator {
return swiftSafeName(for: rawMIMEType)
}
} else {
switch contentType {
switch contentType.category {
case .json:
return "json"
case .text:
Expand Down
138 changes: 69 additions & 69 deletions Sources/_OpenAPIGeneratorCore/Translator/Content/ContentType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,103 +17,106 @@ import OpenAPIKit30
///
/// Represents the serialization method of the payload and affects
/// the generated serialization code.
enum ContentType: Hashable {
struct ContentType: Hashable {

/// The category of a content type.
enum Category: Hashable {

/// A content type for JSON.
case json

/// A content type for any plain text.
case text

/// A content type for raw binary data.
case binary

/// Creates a category from the provided raw string.
///
/// First checks if the provided content type is a JSON, then text,
/// and uses binary if none of the two match.
/// - Parameter rawValue: A string with the content type to create.
init(rawValue: String) {
// https://json-schema.org/draft/2020-12/json-schema-core.html#section-4.2
if rawValue == "application/json" || rawValue.hasSuffix("+json") {
self = .json
return
}
if rawValue.hasPrefix("text/") {
self = .text
return
}
self = .binary
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
}

/// A content type for JSON.
case json(String)
/// The coding strategy appropriate for this content type.
var codingStrategy: CodingStrategy {
switch self {
case .json:
return .json
case .text:
return .text
case .binary:
return .binary
}
}
}

/// A content type for any plain text.
case text(String)
/// The underlying raw content type string.
private let rawValue: String

/// A content type for raw binary data.
case binary(String)
/// The mapped content type category.
let category: Category

/// Creates a new content type by parsing the specified MIME type.
/// - Parameter rawValue: A MIME type, for example "application/json".
init?(_ rawValue: String) {
if rawValue.hasPrefix("application/") && rawValue.hasSuffix("json") {
self = .json(rawValue)
return
}
if rawValue.hasPrefix("text/") {
self = .text(rawValue)
return
}
self = .binary(rawValue)
init(_ rawValue: String) {
self.rawValue = rawValue
self.category = Category(rawValue: rawValue)
}

/// Returns the original raw MIME type.
var rawMIMEType: String {
switch self {
case .json(let string), .text(let string), .binary(let string):
return string
}
rawValue
}

/// The header value used when sending a content-type header.
var headerValueForSending: String {
switch self {
case .json(let string):
// We always encode JSON using JSONEncoder which uses UTF-8.
return string + "; charset=utf-8"
case .text(let string):
return string
case .binary(let string):
return string
guard case .json = category else {
return rawValue
}
// We always encode JSON using JSONEncoder which uses UTF-8.
return rawValue + "; charset=utf-8"
}

/// The header value used when validating a content-type header.
///
/// This should be less strict, e.g. not require `charset`.
var headerValueForValidation: String {
switch self {
case .json(let string):
return string
case .text(let string):
return string
case .binary(let string):
return string
}
rawValue
}

/// The coding strategy appropriate for this content type.
var codingStrategy: CodingStrategy {
switch self {
case .json:
return .json
case .text:
return .text
case .binary:
return .binary
}
category.codingStrategy
}

/// A Boolean value that indicates whether the content type
/// is a type of JSON.
var isJSON: Bool {
if case .json = self {
return true
}
return false
category == .json
}

/// A Boolean value that indicates whether the content type
/// is a type of plain text.
var isText: Bool {
if case .text = self {
return true
}
return false
category == .text
}

/// A Boolean value that indicates whether the content type
/// is just binary data.
var isBinary: Bool {
if case .binary = self {
return true
}
return false
category == .binary
}
}

Expand All @@ -122,27 +125,24 @@ extension OpenAPI.ContentType {
/// A Boolean value that indicates whether the content type
/// is a type of JSON.
var isJSON: Bool {
guard let contentType = ContentType(typeAndSubtype) else {
return false
}
return contentType.isJSON
asGeneratorContentType.isJSON
}

/// A Boolean value that indicates whether the content type
/// is a type of plain text.
var isText: Bool {
guard let contentType = ContentType(typeAndSubtype) else {
return false
}
return contentType.isText
asGeneratorContentType.isText
}

/// A Boolean value that indicates whether the content type
/// is just binary data.
var isBinary: Bool {
guard let contentType = ContentType(typeAndSubtype) else {
return false
}
return contentType.isBinary
asGeneratorContentType.isBinary
}

/// Returns the content type wrapped in the generator's representation
/// of a content type, as opposed to the one from OpenAPIKit.
var asGeneratorContentType: ContentType {
ContentType(typeAndSubtype)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import XCTest
import OpenAPIKit30
@testable import _OpenAPIGeneratorCore

final class Test_ContentType: Test_Core {

func testDecoding() throws {
let cases: [(String, ContentType.Category)] = [
("application/json", .json),
("application/x-www-form-urlencoded", .binary),
("multipart/form-data", .binary),
("text/plain", .text),
("*/*", .binary),
("application/xml", .binary),
("application/octet-stream", .binary),
("application/myformat+json", .json),
("foo/bar", .binary),
("foo/bar+json", .json),
]
for (rawValue, category) in cases {
let contentType = ContentType(rawValue)
XCTAssertEqual(contentType.category, category)
XCTAssertEqual(contentType.rawMIMEType, rawValue)
}
}
}