Skip to content

Commit 7ddef95

Browse files
authored
[Generator] Integrate the new URI and String coders (#226)
[Generator] Integrate the new URI and String coders ### Motivation Depends on runtime changes from apple/swift-openapi-runtime#45. Up until now, we relied on a series of marker and helper protocols `_StringConvertible` and `_AutoLosslessStringConvertible` to handle converting between various types and their string representation. This has been very manual and required a non-trivial amount of work to support any extra type, especially `Date` and generated string enums. Well, turns out this was an unnecessarily difficult way to approach the problem - we had a better solution available for a long time - `Codable`. Since all the generated types and all the built-in types we reference are already `Codable`, there is no need to reinvent a way to serialize and deserialize types, and we should just embrace it. While a JSON encoder and decoder already exists in Foundation, we didn't have one handy for encoding to and from URIs (used by headers, query and path parameters), and raw string representation (using `LosslessStringConvertible`). We created those in the runtime library in PRs apple/swift-openapi-runtime#44 and apple/swift-openapi-runtime#41, and integrated them into our helper functions (which got significantly simplified this way) in apple/swift-openapi-runtime#45. Out of scope of this PR, but this also opens the door to supporting URL form encoded bodies (#182), multipart (#36), and base64 (#11). While this should be mostly invisible to our adopters, this refactoring creates space for implementing more complex features and overall simplifies our serialization story. ### Modifications - Updated the generator to use the new helper functions. - Updated the article about serialization, shows how we reduced the number of helper functions by moving to `Codable`. - Set the `lineLength` to 120 on the formatter configuration, it was inconsistent with our `.swift-format` file, and lead to the soundness script trying to update the reference files, but then the reference tests were failing. Since we're planning to sync these in #40, this is a step closer to it, but it means that it's probably best to review this PR's diff with whitespace ignored. ### Result Now the generated code uses the new helper functions, allowing us to delete all the deprecated helpers in 0.2.0. ### Test Plan Updated file-based reference, snippet, and unit tests. Reviewed by: 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. #226
1 parent cf8e437 commit 7ddef95

File tree

22 files changed

+360
-784
lines changed

22 files changed

+360
-784
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ let package = Package(
8080
// Tests-only: Runtime library linked by generated code, and also
8181
// helps keep the runtime library new enough to work with the generated
8282
// code.
83-
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.10")),
83+
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.11")),
8484

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

Sources/_OpenAPIGeneratorCore/Extensions/SwiftFormat.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ extension String {
3232
configuration.lineBreakAroundMultilineExpressionChainComponents = true
3333
configuration.indentConditionalCompilationBlocks = false
3434
configuration.maximumBlankLines = 0
35+
configuration.lineLength = 120
3536
let formatter = SwiftFormatter(configuration: configuration)
3637
try formatter.format(
3738
source: self,

Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ extension ClientFileTranslator {
3030
left: "path",
3131
right: .try(
3232
.identifier("converter")
33-
.dot("renderedRequestPath")
33+
.dot("renderedPath")
3434
.call([
3535
.init(label: "template", expression: .literal(pathTemplate)),
3636
.init(label: "parameters", expression: pathParamsArrayExpr),

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ extension FileTranslator {
4848
let generateUnknownCases = shouldGenerateUndocumentedCaseForEnumsAndOneOfs
4949
let baseConformance =
5050
generateUnknownCases ? Constants.StringEnum.baseConformanceOpen : Constants.StringEnum.baseConformanceClosed
51+
let conformances =
52+
generateUnknownCases ? Constants.StringEnum.conformancesOpen : Constants.StringEnum.conformancesClosed
5153
let unknownCaseName = generateUnknownCases ? Constants.StringEnum.undocumentedCaseName : nil
5254
return try translateRawRepresentableEnum(
5355
typeName: typeName,
54-
conformances: [baseConformance] + Constants.StringEnum.conformances,
56+
conformances: [baseConformance] + conformances,
5557
userDescription: userDescription,
5658
cases: cases,
5759
unknownCaseName: unknownCaseName,

Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,19 @@ enum Constants {
151151
static let baseConformanceClosed: String = "String"
152152

153153
/// The types that every enum conforms to.
154-
static let conformances: [String] = [
154+
static let conformancesOpen: [String] = [
155155
"Codable",
156156
"Hashable",
157157
"Sendable",
158-
"_AutoLosslessStringConvertible",
159158
"CaseIterable",
160159
]
160+
161+
/// The types that every enum conforms to.
162+
static let conformancesClosed: [String] = [
163+
"Codable",
164+
"Hashable",
165+
"Sendable",
166+
]
161167
}
162168

163169
/// Constants related to generated oneOf enums.
@@ -370,8 +376,11 @@ enum Constants {
370376
/// The substring used in method names for the JSON coding strategy.
371377
static let json: String = "JSON"
372378

373-
/// The substring used in method names for the text coding strategy.
374-
static let text: String = "Text"
379+
/// The substring used in method names for the URI coding strategy.
380+
static let uri: String = "URI"
381+
382+
/// The substring used in method names for the string coding strategy.
383+
static let string: String = "String"
375384

376385
/// The substring used in method names for the binary coding strategy.
377386
static let binary: String = "Binary"

Sources/_OpenAPIGeneratorCore/Translator/Content/CodingStrategy.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ enum CodingStrategy: String, Hashable, Sendable {
1818
/// A strategy using JSONEncoder/JSONDecoder.
1919
case json
2020

21-
/// A strategy using LosslessStringConvertible.
22-
case text
21+
/// A strategy using URIEncoder/URIDecoder.
22+
case uri
23+
24+
/// A strategy using StringEncoder/StringDecoder.
25+
case string
2326

2427
/// A strategy that passes through the data unmodified.
2528
case binary
@@ -29,8 +32,10 @@ enum CodingStrategy: String, Hashable, Sendable {
2932
switch self {
3033
case .json:
3134
return Constants.CodingStrategy.json
32-
case .text:
33-
return Constants.CodingStrategy.text
35+
case .uri:
36+
return Constants.CodingStrategy.uri
37+
case .string:
38+
return Constants.CodingStrategy.string
3439
case .binary:
3540
return Constants.CodingStrategy.binary
3641
}

Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ extension FileTranslator {
203203
{
204204
diagnostics.emitUnsupportedIfNotNil(
205205
chosenContent.1.encoding,
206-
"Custom encoding for JSON content",
206+
"Custom encoding for multipart/formEncoded content",
207207
foundIn: "\(foundIn), content \(contentType.originallyCasedTypeAndSubtype)"
208208
)
209209
}
@@ -234,11 +234,15 @@ extension FileTranslator {
234234
) -> SchemaContent? {
235235
if contentKey.isJSON {
236236
let contentType = ContentType(contentKey.typeAndSubtype)
237-
diagnostics.emitUnsupportedIfNotNil(
238-
contentValue.encoding,
239-
"Custom encoding for JSON content",
240-
foundIn: "\(foundIn), content \(contentKey.rawValue)"
241-
)
237+
if contentType.lowercasedType == "multipart"
238+
|| contentType.lowercasedTypeAndSubtype.contains("application/x-www-form-urlencoded")
239+
{
240+
diagnostics.emitUnsupportedIfNotNil(
241+
contentValue.encoding,
242+
"Custom encoding for multipart/formEncoded content",
243+
foundIn: "\(foundIn), content \(contentType.originallyCasedTypeAndSubtype)"
244+
)
245+
}
242246
return .init(
243247
contentType: contentType,
244248
schema: contentValue.schema

Sources/_OpenAPIGeneratorCore/Translator/Content/ContentType.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ struct ContentType: Hashable {
7070
case .json:
7171
return .json
7272
case .text:
73-
return .text
73+
return .string
7474
case .binary:
7575
return .binary
7676
}

Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,23 +158,23 @@ extension FileTranslator {
158158
schema = schemaContext.schema
159159
style = schemaContext.style
160160
explode = schemaContext.explode
161-
codingStrategy = .text
161+
codingStrategy = .uri
162162

163163
// Check supported exploded/style types
164164
let location = parameter.location
165165
switch location {
166166
case .query:
167-
guard case .form = schemaContext.style else {
167+
guard case .form = style else {
168168
diagnostics.emitUnsupported(
169-
"Non-form style query params",
169+
"Query params of style \(style.rawValue), explode: \(explode)",
170170
foundIn: foundIn
171171
)
172172
return nil
173173
}
174174
case .header, .path:
175-
guard case .simple = schemaContext.style else {
175+
guard case .simple = style else {
176176
diagnostics.emitUnsupported(
177-
"Non-simple style \(location.rawValue) params",
177+
"\(location.rawValue) params of style \(style.rawValue), explode: \(explode)",
178178
foundIn: foundIn
179179
)
180180
return nil

Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ extension ServerFileTranslator {
207207
convertExpr = .try(
208208
.identifier("converter").dot(methodName("QueryItem"))
209209
.call([
210-
.init(label: "in", expression: .identifier("metadata").dot("queryParameters")),
210+
.init(label: "in", expression: .identifier("request").dot("query")),
211211
.init(label: "style", expression: .dot(typedParameter.style.runtimeName)),
212212
.init(label: "explode", expression: .literal(.bool(typedParameter.explode))),
213213
.init(label: "name", expression: .literal(parameter.name)),

Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ extension FileTranslator {
113113
switch header.schemaOrContent {
114114
case let .a(schemaContext):
115115
schema = schemaContext.schema
116-
codingStrategy = .text
116+
codingStrategy = .uri
117117
case let .b(contentMap):
118118
guard
119119
let typedContent = try bestSingleTypedContent(

Sources/swift-openapi-generator/Documentation.docc/Articles/Supported-OpenAPI-features.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,21 +210,22 @@ Supported features are always provided on _both_ client and server.
210210

211211
- [ ] matrix (in path)
212212
- [ ] label (in path)
213-
- [ ] form (in query)
214-
- [x] primitive
215-
- [x] array
216-
- [ ] object
213+
- [x] form (in query)
217214
- [ ] form (in cookie)
218215
- [x] simple (in path)
219216
- [x] simple (in header)
220217
- [ ] spaceDelimited (in query)
221218
- [ ] pipeDelimited (in query)
222219
- [ ] deepObject (in query)
223220

224-
Supported location + styles + exploded combinations:
225-
- path + simple + false
226-
- query + form + true/false
227-
- header + simple + false
221+
#### Supported combinations
222+
223+
| Location | Style | Explode |
224+
| -------- | ----- | ------- |
225+
| path | `simple` | `false` |
226+
| query | `form` | `true` |
227+
| query | `form` | `false` |
228+
| header | `simple` | `false` |
228229

229230
#### Reference Object
230231

0 commit comments

Comments
 (0)