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

Include parameters in the content type name #236

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ extension ContentType {
func docComment(typeName: TypeName) -> Comment? {
typeName.docCommentWithUserDescription(
nil,
subPath: lowercasedTypeAndSubtypeWithEscape
subPath: lowercasedTypeSubtypeAndParametersWithEscape
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ extension FileTranslator {
}
let chosenContent: (SchemaContent, OpenAPI.Content)?
if let (contentKey, contentValue) = map.first(where: { $0.key.isJSON }) {
let contentType = ContentType(contentKey.typeAndSubtype)
let contentType = contentKey.asGeneratorContentType
chosenContent = (
.init(
contentType: contentType,
Expand All @@ -164,7 +164,7 @@ extension FileTranslator {
contentValue
)
} else if let (contentKey, contentValue) = map.first(where: { $0.key.isText }) {
let contentType = ContentType(contentKey.typeAndSubtype)
let contentType = contentKey.asGeneratorContentType
chosenContent = (
.init(
contentType: contentType,
Expand All @@ -173,7 +173,7 @@ extension FileTranslator {
contentValue
)
} else if !excludeBinary, let (contentKey, contentValue) = map.first(where: { $0.key.isBinary }) {
let contentType = ContentType(contentKey.typeAndSubtype)
let contentType = contentKey.asGeneratorContentType
chosenContent = (
.init(
contentType: contentType,
Expand Down Expand Up @@ -225,7 +225,7 @@ extension FileTranslator {
foundIn: String
) -> SchemaContent? {
if contentKey.isJSON {
let contentType = ContentType(contentKey.typeAndSubtype)
let contentType = contentKey.asGeneratorContentType
if contentType.lowercasedType == "multipart"
|| contentType.lowercasedTypeAndSubtype.contains("application/x-www-form-urlencoded")
{
Expand All @@ -241,14 +241,14 @@ extension FileTranslator {
)
}
if contentKey.isText {
let contentType = ContentType(contentKey.typeAndSubtype)
let contentType = contentKey.asGeneratorContentType
return .init(
contentType: contentType,
schema: .b(.string)
)
}
if !excludeBinary, contentKey.isBinary {
let contentType = ContentType(contentKey.typeAndSubtype)
let contentType = contentKey.asGeneratorContentType
return .init(
contentType: contentType,
schema: .b(.string(format: .binary))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ extension FileTranslator {
///
/// - Parameter contentType: The content type for which to compute the name.
func contentSwiftName(_ contentType: ContentType) -> String {
switch contentType.lowercasedTypeAndSubtype {
let rawContentType = contentType.lowercasedTypeSubtypeAndParameters
switch rawContentType {
case "application/json":
return "json"
case "application/x-www-form-urlencoded":
Expand Down Expand Up @@ -50,7 +51,22 @@ extension FileTranslator {
default:
let safedType = swiftSafeName(for: contentType.originallyCasedType)
let safedSubtype = swiftSafeName(for: contentType.originallyCasedSubtype)
return "\(safedType)_\(safedSubtype)"
let prefix = "\(safedType)_\(safedSubtype)"
let params = contentType
.lowercasedParameterPairs
guard !params.isEmpty else {
return prefix
}
let safedParams =
params
.map { pair in
pair
.split(separator: "=")
.map { swiftSafeName(for: String($0)) }
.joined(separator: "_")
}
.joined(separator: "_")
return prefix + "_" + safedParams
}
}
}
62 changes: 54 additions & 8 deletions Sources/_OpenAPIGeneratorCore/Translator/Content/ContentType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,56 @@ struct ContentType: Hashable {
originallyCasedSubtype.lowercased()
}

/// The parameter key-value pairs.
///
/// Preserves the casing from the input, do not use this
/// for equality comparisons, use `lowercasedParameterPairs` instead.
let originallyCasedParameterPairs: [String]

/// The parameter key-value pairs, lowercased.
///
/// The raw value in its original casing is only provided by `originallyCasedParameterPairs`.
var lowercasedParameterPairs: [String] {
originallyCasedParameterPairs.map { $0.lowercased() }
}

/// The parameters string.
var originallyCasedParametersString: String {
originallyCasedParameterPairs.map { "; \($0)" }.joined()
}

/// The parameters string, lowercased.
var lowercasedParametersString: String {
originallyCasedParametersString.lowercased()
}

/// The type, subtype, and parameters components combined.
var originallyCasedTypeSubtypeAndParameters: String {
originallyCasedTypeAndSubtype + originallyCasedParametersString
}

/// The type, subtype, and parameters components combined and lowercased.
var lowercasedTypeSubtypeAndParameters: String {
originallyCasedTypeSubtypeAndParameters.lowercased()
}

/// Creates a new content type by parsing the specified MIME type.
/// - Parameter rawValue: A MIME type, for example "application/json". Must
/// not be empty.
init(_ rawValue: String) {
precondition(!rawValue.isEmpty, "rawValue of a ContentType cannot be empty.")
let rawTypeAndSubtype =
var semiComponents =
rawValue
.split(separator: ";")[0]
.split(separator: ";")
let typeAndSubtypeComponent = semiComponents.removeFirst()
self.originallyCasedParameterPairs = semiComponents.map { component in
component
.split(separator: "=")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.joined(separator: "=")
}
let rawTypeAndSubtype =
typeAndSubtypeComponent
.trimmingCharacters(in: .whitespaces)
let typeAndSubtype =
rawTypeAndSubtype
Expand Down Expand Up @@ -143,21 +185,25 @@ struct ContentType: Hashable {
"\(lowercasedType)/\(lowercasedSubtype)"
}

/// Returns the type and subtype as a "<type>\/<subtype>" string.
/// Returns the type, subtype and parameters (if present) as a "<type>\/<subtype>[;<param>...]" string.
///
/// Lowercased to ease case-insensitive comparisons, and escaped to show
/// that the slash between type and subtype is not a path separator.
var lowercasedTypeAndSubtypeWithEscape: String {
"\(lowercasedType)\\/\(lowercasedSubtype)"
var lowercasedTypeSubtypeAndParametersWithEscape: String {
"\(lowercasedType)\\/\(lowercasedSubtype)" + lowercasedParametersString
}

/// The header value used when sending a content-type header.
var headerValueForSending: String {
guard case .json = category else {
return lowercasedTypeAndSubtype
return lowercasedTypeSubtypeAndParameters
}
// We always encode JSON using JSONEncoder which uses UTF-8.
return lowercasedTypeAndSubtype + "; charset=utf-8"
// Check if it's already present, if not, append it.
guard !lowercasedParameterPairs.contains("charset=") else {
return lowercasedTypeSubtypeAndParameters
}
return lowercasedTypeSubtypeAndParameters + "; charset=utf-8"
}

/// The header value used when validating a content-type header.
Expand Down Expand Up @@ -223,6 +269,6 @@ extension OpenAPI.ContentType {
/// 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)
ContentType(rawValue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ final class Test_ContentSwiftName: Test_Core {
// Generic names.
("application/myformat+json", "application_myformat_plus_json"),
("foo/bar", "foo_bar"),

// Names with a parameter.
("application/foo", "application_foo"),
("application/foo; bar=baz; boo=foo", "application_foo_bar_baz_boo_foo"),
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
("application/foo; bar = baz", "application_foo_bar_baz"),
]
for item in cases {
let contentType = try XCTUnwrap(ContentType(item.0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ final class Test_ContentType: Test_Core {
category: ContentType.Category,
type: String,
subtype: String,
parameters: String,
lowercasedOutput: String,
originallyCasedOutput: String
originallyCasedOutput: String,
originallyCasedOutputWithParameters: String
)] = [
(
"application/json",
.json,
"application",
"json",
"",
"application/json",
"application/json",
"application/json"
),
Expand All @@ -40,22 +44,28 @@ final class Test_ContentType: Test_Core {
.json,
"application",
"json",
"",
"application/json",
"APPLICATION/JSON",
"APPLICATION/JSON"
),
(
"application/json; charset=utf-8",
.json,
"application",
"json",
"; charset=utf-8",
"application/json",
"application/json"
"application/json",
"application/json; charset=utf-8"
),
(
"application/x-www-form-urlencoded",
.binary,
"application",
"x-www-form-urlencoded",
"",
"application/x-www-form-urlencoded",
"application/x-www-form-urlencoded",
"application/x-www-form-urlencoded"
),
Expand All @@ -64,6 +74,8 @@ final class Test_ContentType: Test_Core {
.binary,
"multipart",
"form-data",
"",
"multipart/form-data",
"multipart/form-data",
"multipart/form-data"
),
Expand All @@ -72,6 +84,8 @@ final class Test_ContentType: Test_Core {
.text,
"text",
"plain",
"",
"text/plain",
"text/plain",
"text/plain"
),
Expand All @@ -80,6 +94,8 @@ final class Test_ContentType: Test_Core {
.binary,
"*",
"*",
"",
"*/*",
"*/*",
"*/*"
),
Expand All @@ -88,6 +104,8 @@ final class Test_ContentType: Test_Core {
.binary,
"application",
"xml",
"",
"application/xml",
"application/xml",
"application/xml"
),
Expand All @@ -96,6 +114,8 @@ final class Test_ContentType: Test_Core {
.binary,
"application",
"octet-stream",
"",
"application/octet-stream",
"application/octet-stream",
"application/octet-stream"
),
Expand All @@ -104,6 +124,8 @@ final class Test_ContentType: Test_Core {
.json,
"application",
"myformat+json",
"",
"application/myformat+json",
"application/myformat+json",
"application/myformat+json"
),
Expand All @@ -112,6 +134,8 @@ final class Test_ContentType: Test_Core {
.binary,
"foo",
"bar",
"",
"foo/bar",
"foo/bar",
"foo/bar"
),
Expand All @@ -120,24 +144,40 @@ final class Test_ContentType: Test_Core {
.json,
"foo",
"bar+json",
"",
"foo/bar+json",
"foo/bar+json",
"foo/bar+json"
),
(
"foo/bar+json; param1=a; param2=b",
.json,
"foo",
"bar+json",
"; param1=a; param2=b",
"foo/bar+json",
"foo/bar+json",
"foo/bar+json; param1=a; param2=b"
),
]
for (
rawValue,
category,
type,
subtype,
parameters,
lowercasedTypeAndSubtype,
originallyCasedTypeAndSubtype
originallyCasedTypeAndSubtype,
originallyCasedOutputWithParameters
) in cases {
let contentType = ContentType(rawValue)
XCTAssertEqual(contentType.category, category)
XCTAssertEqual(contentType.lowercasedType, type)
XCTAssertEqual(contentType.lowercasedSubtype, subtype)
XCTAssertEqual(contentType.lowercasedParametersString, parameters)
XCTAssertEqual(contentType.lowercasedTypeAndSubtype, lowercasedTypeAndSubtype)
XCTAssertEqual(contentType.originallyCasedTypeAndSubtype, originallyCasedTypeAndSubtype)
XCTAssertEqual(contentType.originallyCasedTypeSubtypeAndParameters, originallyCasedOutputWithParameters)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,9 @@ final class SnippetBasedReferenceTests: XCTestCase {
application/json:
schema:
type: integer
application/json; foo=bar:
schema:
type: integer
text/plain: {}
application/octet-stream: {}
""",
Expand All @@ -904,6 +907,7 @@ final class SnippetBasedReferenceTests: XCTestCase {
public struct MultipleContentTypes: Sendable, Hashable {
@frozen public enum Body: Sendable, Hashable {
case json(Swift.Int)
case application_json_foo_bar(Swift.Int)
case plainText(Swift.String)
case binary(Foundation.Data)
}
Expand Down