-
Notifications
You must be signed in to change notification settings - Fork 49
[Runtime] Multiple content types support #29
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
Merged
czechboy0
merged 20 commits into
apple:main
from
czechboy0:hd-draft-multiple-content-types
Aug 4, 2023
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
f997ab1
[WIP] Multiple content types
czechboy0 66b78af
Try designing EncodedBodyContent away
czechboy0 00e9b40
Deprecate unit tests that call deprecated methods
czechboy0 f6c7f23
Stop using EncodableBodyContent in responses as well
czechboy0 59f199f
Update tests as well
czechboy0 25049e8
Move a function for a smaller PR diff
czechboy0 c384fc8
Merge branch 'main' into hd-draft-multiple-content-types
czechboy0 0ec13e5
Merge branch 'main' into hd-draft-multiple-content-types
czechboy0 a882dc8
Merge branch 'main' into hd-draft-multiple-content-types
czechboy0 ddc431d
Update Sources/OpenAPIRuntime/Conversion/Converter+Common.swift
czechboy0 efc85c2
PR feedback: Introduce a MIMEType wrapper type
czechboy0 7f832ac
Fix formatting
czechboy0 606aa0e
PR feedback: s/isValidContentType/isMatchingContentType
czechboy0 5752e18
PR feedback: no need to prefix fileprivate extensions
czechboy0 de2248b
PR feedback: restructure MIMEType to disallow json/*
czechboy0 09b68a7
PR feedback: rename MIMEType to OpenAPIMIMEType
czechboy0 6df8431
PR feedback: Add a comment about how we're handling duplicate paramet…
czechboy0 e0dae85
PR feedback: Make isMatchingContentType stricter + rename expected to…
czechboy0 f4678b2
Only allocate dictionaries if there are parameters
czechboy0 63f44b0
Merge branch 'main' into hd-draft-multiple-content-types
czechboy0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 Foundation | ||
|
||
/// A container for a parsed, valid MIME type. | ||
@_spi(Generated) | ||
public struct OpenAPIMIMEType: Equatable { | ||
|
||
/// The kind of the MIME type. | ||
public enum Kind: Equatable { | ||
|
||
/// Any, spelled as `*/*`. | ||
case any | ||
|
||
/// Any subtype of a concrete type, spelled as `type/*`. | ||
case anySubtype(type: String) | ||
|
||
/// A concrete value, spelled as `type/subtype`. | ||
case concrete(type: String, subtype: String) | ||
|
||
public static func == (lhs: Kind, rhs: Kind) -> Bool { | ||
switch (lhs, rhs) { | ||
case (.any, .any): | ||
return true | ||
case let (.anySubtype(lhsType), .anySubtype(rhsType)): | ||
return lhsType.lowercased() == rhsType.lowercased() | ||
case let (.concrete(lhsType, lhsSubtype), .concrete(rhsType, rhsSubtype)): | ||
return lhsType.lowercased() == rhsType.lowercased() | ||
&& lhsSubtype.lowercased() == rhsSubtype.lowercased() | ||
default: | ||
return false | ||
} | ||
} | ||
} | ||
|
||
/// The kind of the MIME type. | ||
public var kind: Kind | ||
|
||
/// Any optional parameters. | ||
public var parameters: [String: String] | ||
|
||
/// Creates a new MIME type. | ||
/// - Parameters: | ||
/// - kind: The kind of the MIME type. | ||
/// - parameters: Any optional parameters. | ||
public init(kind: Kind, parameters: [String: String] = [:]) { | ||
self.kind = kind | ||
self.parameters = parameters | ||
} | ||
|
||
public static func == (lhs: OpenAPIMIMEType, rhs: OpenAPIMIMEType) -> Bool { | ||
guard lhs.kind == rhs.kind else { | ||
return false | ||
} | ||
// Parameter names are case-insensitive, parameter values are | ||
// case-sensitive. | ||
guard lhs.parameters.count == rhs.parameters.count else { | ||
return false | ||
} | ||
if lhs.parameters.isEmpty { | ||
return true | ||
} | ||
func normalizeKeyValue(key: String, value: String) -> (String, String) { | ||
(key.lowercased(), value) | ||
} | ||
let normalizedLeftParams = Dictionary( | ||
uniqueKeysWithValues: lhs.parameters.map(normalizeKeyValue) | ||
) | ||
let normalizedRightParams = Dictionary( | ||
uniqueKeysWithValues: rhs.parameters.map(normalizeKeyValue) | ||
) | ||
return normalizedLeftParams == normalizedRightParams | ||
} | ||
} | ||
|
||
extension OpenAPIMIMEType.Kind: LosslessStringConvertible { | ||
public init?(_ description: String) { | ||
let typeAndSubtype = | ||
description | ||
.split(separator: "/") | ||
.map(String.init) | ||
guard typeAndSubtype.count == 2 else { | ||
return nil | ||
} | ||
switch (typeAndSubtype[0], typeAndSubtype[1]) { | ||
case ("*", let subtype): | ||
guard subtype == "*" else { | ||
return nil | ||
} | ||
self = .any | ||
case (let type, "*"): | ||
self = .anySubtype(type: type) | ||
case (let type, let subtype): | ||
self = .concrete(type: type, subtype: subtype) | ||
} | ||
} | ||
|
||
public var description: String { | ||
switch self { | ||
case .any: | ||
return "*/*" | ||
case .anySubtype(let type): | ||
return "\(type)/*" | ||
case .concrete(let type, let subtype): | ||
return "\(type)/\(subtype)" | ||
} | ||
} | ||
} | ||
|
||
extension OpenAPIMIMEType: LosslessStringConvertible { | ||
public init?(_ description: String) { | ||
var components = | ||
description | ||
// Split by semicolon | ||
.split(separator: ";") | ||
.map(String.init) | ||
// Trim leading/trailing spaces | ||
.map { $0.trimmingLeadingAndTrailingSpaces } | ||
guard !components.isEmpty else { | ||
return nil | ||
} | ||
let firstComponent = components.removeFirst() | ||
guard let kind = OpenAPIMIMEType.Kind(firstComponent) else { | ||
return nil | ||
} | ||
func parseParameter(_ string: String) -> (String, String)? { | ||
let components = | ||
string | ||
.split(separator: "=") | ||
.map(String.init) | ||
guard components.count == 2 else { | ||
return nil | ||
} | ||
return (components[0], components[1]) | ||
} | ||
let parameters = | ||
components | ||
.compactMap(parseParameter) | ||
self.init( | ||
kind: kind, | ||
parameters: Dictionary( | ||
parameters, | ||
// Pick the first value when duplicate parameters are provided. | ||
uniquingKeysWith: { a, _ in a } | ||
) | ||
) | ||
} | ||
|
||
public var description: String { | ||
([kind.description] | ||
+ parameters | ||
.sorted(by: { a, b in a.key < b.key }) | ||
.map { "\($0)=\($1)" }) | ||
.joined(separator: "; ") | ||
} | ||
} | ||
|
||
extension String { | ||
fileprivate var trimmingLeadingAndTrailingSpaces: Self { | ||
trimmingCharacters(in: .whitespacesAndNewlines) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 | ||
@_spi(Generated) import OpenAPIRuntime | ||
|
||
final class Test_OpenAPIMIMEType: Test_Runtime { | ||
func test() throws { | ||
let cases: [(String, OpenAPIMIMEType?, String?)] = [ | ||
|
||
// Common | ||
( | ||
"application/json", | ||
OpenAPIMIMEType(kind: .concrete(type: "application", subtype: "json")), | ||
"application/json" | ||
), | ||
|
||
// Subtype wildcard | ||
( | ||
"application/*", | ||
OpenAPIMIMEType(kind: .anySubtype(type: "application")), | ||
"application/*" | ||
), | ||
|
||
// Type wildcard | ||
( | ||
"*/*", | ||
OpenAPIMIMEType(kind: .any), | ||
"*/*" | ||
), | ||
|
||
// Common with a parameter | ||
( | ||
"application/json; charset=UTF-8", | ||
OpenAPIMIMEType( | ||
kind: .concrete(type: "application", subtype: "json"), | ||
parameters: [ | ||
"charset": "UTF-8" | ||
] | ||
), | ||
"application/json; charset=UTF-8" | ||
), | ||
|
||
// Common with two parameters | ||
( | ||
"application/json; charset=UTF-8; boundary=1234", | ||
OpenAPIMIMEType( | ||
kind: .concrete(type: "application", subtype: "json"), | ||
parameters: [ | ||
"charset": "UTF-8", | ||
"boundary": "1234", | ||
] | ||
), | ||
"application/json; boundary=1234; charset=UTF-8" | ||
), | ||
|
||
// Common case preserving, but case insensitive equality | ||
( | ||
"APPLICATION/JSON;CHARSET=UTF-8", | ||
OpenAPIMIMEType( | ||
kind: .concrete(type: "application", subtype: "json"), | ||
parameters: [ | ||
"charset": "UTF-8" | ||
] | ||
), | ||
"APPLICATION/JSON; CHARSET=UTF-8" | ||
), | ||
|
||
// Invalid | ||
("application", nil, nil), | ||
("application/foo/bar", nil, nil), | ||
("", nil, nil), | ||
] | ||
for (inputString, expectedMIME, outputString) in cases { | ||
let mime = OpenAPIMIMEType(inputString) | ||
XCTAssertEqual(mime, expectedMIME) | ||
XCTAssertEqual(mime?.description, outputString) | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.