Skip to content

Commit 269b31d

Browse files
authored
[Generator] Stop generating an undocumented case for enums/oneOfs (#205)
### Motivation Fixes #204. Depends on apple/swift-openapi-runtime#40. ### Modifications This introduces the feature flag `closedEnumsAndOneOfs` which stops generating the `undocumented` case for enums and oneOfs (no other places are affected, such as responses, and so on.). The default behavior in 0.1.x stays the same, but in 0.2.0 we'll stop generating `undocumented` cases unconditionally. This fixes our divergence from the OpenAPI specification, as we treated enums and oneOfs as open, but they're closed by default. I also added docs on this pattern to make it easier for adopters to know how to create open enums and oneOfs on purpose. ### Result We get closer to being spec-compliant. ### Test Plan Created new snippet tests to verify the new functionality, will adapt file-based tests when we're preparing 0.2.0.
1 parent b59b8a3 commit 269b31d

File tree

13 files changed

+642
-347
lines changed

13 files changed

+642
-347
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ let package = Package(
7878
),
7979

8080
// Tests-only: Runtime library linked by generated code
81-
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.8")),
81+
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.9")),
8282

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

Sources/_OpenAPIGeneratorCore/FeatureFlags.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public enum FeatureFlag: String, Hashable, Equatable, Codable, CaseIterable {
4444
///
4545
/// Check for structural issues and detect cycles proactively.
4646
case strictOpenAPIValidation
47+
48+
/// Removed the generation of an undocumented case in enums/oneOfs.
49+
///
50+
/// Tracking issue:
51+
/// - https://github.com/apple/swift-openapi-generator/issues/204
52+
case closedEnumsAndOneOfs
4753
}
4854

4955
/// A set of enabled feature flags.

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,15 +226,26 @@ extension FileTranslator {
226226
)
227227
}
228228

229-
let undocumentedCase: Declaration = .commentable(
230-
.doc("Parsed a case that was not defined in the OpenAPI document."),
231-
.enumCase(
232-
name: Constants.OneOf.undocumentedCaseName,
233-
kind: .nameWithAssociatedValues([
234-
.init(type: undocumentedType.fullyQualifiedSwiftName)
235-
])
229+
let generateUndocumentedCase = shouldGenerateUndocumentedCaseForEnumsAndOneOfs
230+
231+
let otherCases: [Declaration]
232+
if generateUndocumentedCase {
233+
let undocumentedCase: Declaration = .commentable(
234+
.doc("Parsed a case that was not defined in the OpenAPI document."),
235+
.enumCase(
236+
name: Constants.OneOf.undocumentedCaseName,
237+
kind: .nameWithAssociatedValues([
238+
.init(type: undocumentedType.fullyQualifiedSwiftName)
239+
])
240+
)
236241
)
237-
)
242+
otherCases = [
243+
undocumentedCase
244+
]
245+
} else {
246+
otherCases = []
247+
}
248+
238249
let encoder = translateOneOfEncoder(caseNames: caseNames)
239250

240251
let comment: Comment? =
@@ -245,9 +256,7 @@ extension FileTranslator {
245256
accessModifier: config.access,
246257
name: typeName.shortSwiftName,
247258
conformances: Constants.ObjectStruct.conformances,
248-
members: caseDecls + [
249-
undocumentedCase
250-
] + codingKeysDecls + [
259+
members: caseDecls + otherCases + codingKeysDecls + [
251260
decoder,
252261
encoder,
253262
]

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateCodable.swift

Lines changed: 130 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -338,52 +338,84 @@ extension FileTranslator {
338338
)
339339
)
340340
}
341-
let decodeUndocumentedExprs: [CodeBlock] = [
342-
.declaration(
343-
.variable(
344-
kind: .let,
345-
left: "container",
346-
right: .try(
347-
.identifier("decoder")
348-
.dot("singleValueContainer")
349-
.call([])
341+
342+
let generateUndocumentedCase = shouldGenerateUndocumentedCaseForEnumsAndOneOfs
343+
let otherExprs: [CodeBlock]
344+
if generateUndocumentedCase {
345+
otherExprs = [
346+
.declaration(
347+
.variable(
348+
kind: .let,
349+
left: "container",
350+
right: .try(
351+
.identifier("decoder")
352+
.dot("singleValueContainer")
353+
.call([])
354+
)
350355
)
351-
)
352-
),
353-
.declaration(
354-
.variable(
355-
kind: .let,
356-
left: "value",
357-
right: .try(
358-
.identifier("container")
359-
.dot("decode")
356+
),
357+
.declaration(
358+
.variable(
359+
kind: .let,
360+
left: "value",
361+
right: .try(
362+
.identifier("container")
363+
.dot("decode")
364+
.call([
365+
.init(
366+
label: nil,
367+
expression:
368+
.identifier(
369+
TypeName
370+
.valueContainer
371+
.fullyQualifiedSwiftName
372+
)
373+
.dot("self")
374+
)
375+
])
376+
)
377+
)
378+
),
379+
.expression(
380+
.assignment(
381+
left: .identifier("self"),
382+
right: .dot(Constants.OneOf.undocumentedCaseName)
360383
.call([
361-
.init(
362-
label: nil,
363-
expression:
364-
.identifier(
365-
TypeName
366-
.valueContainer
367-
.fullyQualifiedSwiftName
368-
)
369-
.dot("self")
370-
)
384+
.init(label: nil, expression: .identifier("value"))
371385
])
372386
)
387+
),
388+
]
389+
} else {
390+
otherExprs = [
391+
.expression(
392+
translateOneOfDecoderThrowOnUnknownExpr()
373393
)
374-
),
375-
.expression(
376-
.assignment(
377-
left: .identifier("self"),
378-
right: .dot(Constants.OneOf.undocumentedCaseName)
379-
.call([
380-
.init(label: nil, expression: .identifier("value"))
381-
])
382-
)
383-
),
384-
]
394+
]
395+
}
396+
385397
return decoderInitializer(
386-
body: (assignExprs).map { .expression($0) } + decodeUndocumentedExprs
398+
body: (assignExprs).map { .expression($0) } + otherExprs
399+
)
400+
}
401+
402+
/// Returns an expression that throws an error when a oneOf failed
403+
/// to match any documented cases.
404+
func translateOneOfDecoderThrowOnUnknownExpr() -> Expression {
405+
.unaryKeyword(
406+
kind: .throw,
407+
expression: .identifier("DecodingError")
408+
.dot("failedToDecodeOneOfSchema")
409+
.call([
410+
.init(
411+
label: "type",
412+
expression: .identifier("Self").dot("self")
413+
),
414+
.init(
415+
label: "codingPath",
416+
expression: .identifier("decoder").dot("codingPath")
417+
),
418+
])
387419
)
388420
}
389421

@@ -418,50 +450,60 @@ extension FileTranslator {
418450
]
419451
)
420452
}
421-
let decodeUndocumentedBody: [CodeBlock] = [
422-
.declaration(
423-
.variable(
424-
kind: .let,
425-
left: "container",
426-
right: .try(
427-
.identifier("decoder")
428-
.dot("singleValueContainer")
429-
.call([])
453+
let generateUndocumentedCase = shouldGenerateUndocumentedCaseForEnumsAndOneOfs
454+
let otherExprs: [CodeBlock]
455+
if generateUndocumentedCase {
456+
otherExprs = [
457+
.declaration(
458+
.variable(
459+
kind: .let,
460+
left: "container",
461+
right: .try(
462+
.identifier("decoder")
463+
.dot("singleValueContainer")
464+
.call([])
465+
)
430466
)
431-
)
432-
),
433-
.declaration(
434-
.variable(
435-
kind: .let,
436-
left: "value",
437-
right: .try(
438-
.identifier("container")
439-
.dot("decode")
467+
),
468+
.declaration(
469+
.variable(
470+
kind: .let,
471+
left: "value",
472+
right: .try(
473+
.identifier("container")
474+
.dot("decode")
475+
.call([
476+
.init(
477+
label: nil,
478+
expression:
479+
.identifier(
480+
TypeName
481+
.objectContainer
482+
.fullyQualifiedSwiftName
483+
)
484+
.dot("self")
485+
)
486+
])
487+
)
488+
)
489+
),
490+
.expression(
491+
.assignment(
492+
left: .identifier("self"),
493+
right: .dot(Constants.OneOf.undocumentedCaseName)
440494
.call([
441-
.init(
442-
label: nil,
443-
expression:
444-
.identifier(
445-
TypeName
446-
.objectContainer
447-
.fullyQualifiedSwiftName
448-
)
449-
.dot("self")
450-
)
495+
.init(label: nil, expression: .identifier("value"))
451496
])
452497
)
498+
),
499+
]
500+
} else {
501+
otherExprs = [
502+
.expression(
503+
translateOneOfDecoderThrowOnUnknownExpr()
453504
)
454-
),
455-
.expression(
456-
.assignment(
457-
left: .identifier("self"),
458-
right: .dot(Constants.OneOf.undocumentedCaseName)
459-
.call([
460-
.init(label: nil, expression: .identifier("value"))
461-
])
462-
)
463-
),
464-
]
505+
]
506+
}
465507
let body: [CodeBlock] = [
466508
.declaration(.decoderContainerOfKeysVar()),
467509
.declaration(
@@ -490,7 +532,7 @@ extension FileTranslator {
490532
cases: cases + [
491533
.init(
492534
kind: .default,
493-
body: decodeUndocumentedBody
535+
body: otherExprs
494536
)
495537
]
496538
)
@@ -507,9 +549,16 @@ extension FileTranslator {
507549
func translateOneOfEncoder(
508550
caseNames: [String]
509551
) -> Declaration {
552+
let generateUndocumentedCase = shouldGenerateUndocumentedCaseForEnumsAndOneOfs
553+
let otherCaseNames: [String]
554+
if generateUndocumentedCase {
555+
otherCaseNames = [Constants.OneOf.undocumentedCaseName]
556+
} else {
557+
otherCaseNames = []
558+
}
510559
let switchExpr: Expression = .switch(
511560
switchedExpression: .identifier("self"),
512-
cases: (caseNames + [Constants.OneOf.undocumentedCaseName])
561+
cases: (caseNames + otherCaseNames)
513562
.map { caseName in
514563
.init(
515564
kind: .case(.dot(caseName), ["value"]),

0 commit comments

Comments
 (0)