Skip to content

Commit 0bebccd

Browse files
authored
[Generator] Accept header (#185)
[Generator] Accept header ### Motivation Fixes #160. SOAR-0003 was accepted, this is the generator side of the implementation. The runtime side, which must land first, is at: apple/swift-openapi-runtime#37. ### Modifications - Adapted the generator logic for the changes - see the file-based reference tests for concrete examples of what the generated `AcceptableContentType` enum looks like. - Introduced `translateRawRepresentableEnum`, which allows sharing logic between generating this new enum and other string-based enums. - Explicitly skip parameters that match reserved headers, as dictated by the specification. ### Result SOAR-0003 as proposed, working behind the `multipleContentTypes` feature flag. ### Test Plan Adapted PetstoreConsumerTests to test the new behavior, adapter reference tests. Reviewed by: gjcairo, glbrntt Builds: ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. #185
1 parent 3b12834 commit 0bebccd

File tree

22 files changed

+961
-281
lines changed

22 files changed

+961
-281
lines changed

Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ let package = Package(
7777
from: "1.0.1"
7878
),
7979

80-
// Tests-only: Runtime library linked by generated code
81-
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.9")),
80+
// Tests-only: Runtime library linked by generated code, and also
81+
// helps keep the runtime library new enough to work with the generated
82+
// code.
83+
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.10")),
8284

8385
// Build and preview docs
8486
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),

Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,20 @@ extension ClientFileTranslator {
7171
for: description
7272
)
7373
if !acceptContent.isEmpty {
74-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
75-
let acceptValue =
76-
acceptContent
77-
.map(\.headerValueForValidation)
78-
.joined(separator: ", ")
79-
let addAcceptHeaderExpr: Expression = .try(
80-
.identifier("converter").dot("setHeaderFieldAsText")
81-
.call([
82-
.init(
83-
label: "in",
84-
expression: .inOut(.identifier("request").dot("headerFields"))
85-
),
86-
.init(label: "name", expression: "accept"),
87-
.init(label: "value", expression: .literal(acceptValue)),
88-
])
89-
)
90-
requestExprs.append(addAcceptHeaderExpr)
74+
let setAcceptHeaderExpr: Expression =
75+
.identifier("converter")
76+
.dot("setAcceptHeader")
77+
.call([
78+
.init(
79+
label: "in",
80+
expression: .inOut(.identifier("request").dot("headerFields"))
81+
),
82+
.init(
83+
label: "contentTypes",
84+
expression: .identifier("input").dot("headers").dot("accept")
85+
),
86+
])
87+
requestExprs.append(setAcceptHeaderExpr)
9188
}
9289

9390
if let requestBody = try typedRequestBody(in: description) {
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import OpenAPIKit30
15+
16+
extension FileTranslator {
17+
18+
/// Returns a declaration of the specified raw representable enum.
19+
/// - Parameters:
20+
/// - typeName: The name of the type to give to the declared enum.
21+
/// - conformances: The list of types the enum conforms to.
22+
/// - userDescription: The contents of the documentation comment.
23+
/// - cases: The list of cases to generate.
24+
/// - unknownCaseName: The name of the extra unknown case that preserves
25+
/// the string value that doesn't fit any of the cases. If nil is
26+
/// passed, the unknown case is not generated.
27+
/// - unknownCaseDescription: The contents of the documentation comment
28+
/// for the unknown case.
29+
/// - customSwitchedExpression: A closure
30+
func translateRawRepresentableEnum(
31+
typeName: TypeName,
32+
conformances: [String],
33+
userDescription: String?,
34+
cases: [(caseName: String, rawValue: String)],
35+
unknownCaseName: String?,
36+
unknownCaseDescription: String?,
37+
customSwitchedExpression: (Expression) -> Expression = { $0 }
38+
) throws -> Declaration {
39+
40+
let generateUnknownCases = unknownCaseName != nil
41+
let knownCases: [Declaration] =
42+
cases
43+
.map { caseName, rawValue in
44+
.enumCase(
45+
name: caseName,
46+
kind: generateUnknownCases ? .nameOnly : .nameWithRawValue(rawValue)
47+
)
48+
}
49+
50+
let otherMembers: [Declaration]
51+
if let unknownCaseName {
52+
let undocumentedCase: Declaration = .commentable(
53+
unknownCaseDescription.flatMap { .doc($0) },
54+
.enumCase(
55+
name: unknownCaseName,
56+
kind: .nameWithAssociatedValues([
57+
.init(type: "String")
58+
])
59+
)
60+
)
61+
let rawRepresentableInitializer: Declaration
62+
do {
63+
let knownCases: [SwitchCaseDescription] = cases.map { caseName, rawValue in
64+
.init(
65+
kind: .case(.literal(rawValue)),
66+
body: [
67+
.expression(
68+
.assignment(
69+
Expression
70+
.identifier("self")
71+
.equals(
72+
.dot(caseName)
73+
)
74+
)
75+
)
76+
]
77+
)
78+
}
79+
let unknownCase = SwitchCaseDescription(
80+
kind: .default,
81+
body: [
82+
.expression(
83+
.assignment(
84+
Expression
85+
.identifier("self")
86+
.equals(
87+
.functionCall(
88+
calledExpression: .dot(
89+
unknownCaseName
90+
),
91+
arguments: [
92+
.identifier("rawValue")
93+
]
94+
)
95+
)
96+
)
97+
)
98+
]
99+
)
100+
rawRepresentableInitializer = .function(
101+
.init(
102+
accessModifier: config.access,
103+
kind: .initializer(failable: true),
104+
parameters: [
105+
.init(label: "rawValue", type: "String")
106+
],
107+
body: [
108+
.expression(
109+
.switch(
110+
switchedExpression: customSwitchedExpression(
111+
.identifier("rawValue")
112+
),
113+
cases: knownCases + [unknownCase]
114+
)
115+
)
116+
]
117+
)
118+
)
119+
}
120+
121+
let rawValueGetter: Declaration
122+
do {
123+
let knownCases: [SwitchCaseDescription] = cases.map { caseName, rawValue in
124+
.init(
125+
kind: .case(.dot(caseName)),
126+
body: [
127+
.expression(
128+
.return(.literal(rawValue))
129+
)
130+
]
131+
)
132+
}
133+
let unknownCase = SwitchCaseDescription(
134+
kind: .case(
135+
.valueBinding(
136+
kind: .let,
137+
value: .init(
138+
calledExpression: .dot(
139+
unknownCaseName
140+
),
141+
arguments: [
142+
.identifier("string")
143+
]
144+
)
145+
)
146+
),
147+
body: [
148+
.expression(
149+
.return(.identifier("string"))
150+
)
151+
]
152+
)
153+
154+
let variableDescription = VariableDescription(
155+
accessModifier: config.access,
156+
kind: .var,
157+
left: "rawValue",
158+
type: "String",
159+
body: [
160+
.expression(
161+
.switch(
162+
switchedExpression: .identifier("self"),
163+
cases: [unknownCase] + knownCases
164+
)
165+
)
166+
]
167+
)
168+
169+
rawValueGetter = .variable(
170+
variableDescription
171+
)
172+
}
173+
174+
let allCasesGetter: Declaration
175+
do {
176+
let caseExpressions: [Expression] = cases.map { caseName, _ in
177+
.memberAccess(.init(right: caseName))
178+
}
179+
allCasesGetter = .variable(
180+
.init(
181+
accessModifier: config.access,
182+
isStatic: true,
183+
kind: .var,
184+
left: "allCases",
185+
type: "[Self]",
186+
body: [
187+
.expression(.literal(.array(caseExpressions)))
188+
]
189+
)
190+
)
191+
}
192+
otherMembers = [
193+
undocumentedCase,
194+
rawRepresentableInitializer,
195+
rawValueGetter,
196+
allCasesGetter,
197+
]
198+
} else {
199+
otherMembers = []
200+
}
201+
202+
let enumDescription = EnumDescription(
203+
isFrozen: true,
204+
accessModifier: config.access,
205+
name: typeName.shortSwiftName,
206+
conformances: conformances,
207+
members: knownCases + otherMembers
208+
)
209+
let comment: Comment? =
210+
typeName
211+
.docCommentWithUserDescription(userDescription)
212+
return .commentable(
213+
comment,
214+
.enum(enumDescription)
215+
)
216+
}
217+
}

0 commit comments

Comments
 (0)