Skip to content

Commit cf8e437

Browse files
authored
Allow non-unique values in oneOf.discriminator.mapping (#225)
Allow non-unique values in oneOf.discriminator.mapping ### Motivation Fixes #223, see the issue for details. ### Modifications Changed the logic for deriving the enum cases to take both the mapping and the list of subschemas into account. Documented it more in code. ### Result Now adopter can actually have different keys pointing to the same types in their mapping - originally reported by an adopter, so it's a real issue. ### Test Plan Adapted 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. #225
1 parent a0df460 commit cf8e437

File tree

10 files changed

+361
-200
lines changed

10 files changed

+361
-200
lines changed

Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,11 @@ enum SwitchCaseKind: Equatable, Codable {
636636
/// For example: `case let foo(bar):`.
637637
case `case`(Expression, [String])
638638

639+
/// A case with multiple comma-separated expressions.
640+
///
641+
/// For example: `case "foo", "bar":`.
642+
case multiCase([Expression])
643+
639644
/// A default. Spelled as `default:`.
640645
case `default`
641646
}

Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ struct TextBasedRenderer: RendererProtocol {
152152
maybeLet = ""
153153
}
154154
return "case \(maybeLet)\(renderedExpression(expression))\(associatedValues)"
155+
case .multiCase(let expressions):
156+
let expressions = expressions.map(renderedExpression).joined(separator: ", ")
157+
return "case \(expressions)"
155158
case .`default`:
156159
return "default"
157160
}

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -124,55 +124,71 @@ extension FileTranslator {
124124
discriminator: OpenAPI.Discriminator?,
125125
schemas: [JSONSchema]
126126
) throws -> Declaration {
127-
// > When using the discriminator, inline schemas will not be considered.
128-
// > — https://spec.openapis.org/oas/v3.0.3#discriminator-object
129-
let includedSchemas: [JSONSchema]
130-
if discriminator != nil {
131-
includedSchemas = schemas.filter(\.isReference)
132-
} else {
133-
includedSchemas = schemas
134-
}
135-
136-
let cases: [(String, Comment?, TypeUsage, [Declaration])] =
137-
try includedSchemas
138-
.enumerated()
139-
.map { index, schema in
140-
let key = "case\(index+1)"
141-
let childType = try typeAssigner.typeUsage(
142-
forAllOrAnyOrOneOfChildSchemaNamed: key,
143-
withSchema: schema,
144-
inParent: typeName
145-
)
146-
let caseName: String
147-
// Only use the type name for the case for references,
148-
// as inline schemas have nothing that guarantees uniqueness.
149-
if schema.isReference {
150-
// Use the type name.
151-
caseName = childType.typeName.shortSwiftName
152-
} else {
153-
// Use a position-based key.
154-
caseName = key
127+
let cases: [(String, [String]?, Comment?, TypeUsage, [Declaration])]
128+
if let discriminator {
129+
// > When using the discriminator, inline schemas will not be considered.
130+
// > — https://spec.openapis.org/oas/v3.0.3#discriminator-object
131+
let includedSchemas: [JSONReference<JSONSchema>] =
132+
schemas
133+
.compactMap { schema in
134+
guard case let .reference(ref, _) = schema.value else {
135+
return nil
136+
}
137+
return ref
155138
}
139+
let mappedTypes = try discriminator.allTypes(
140+
schemas: includedSchemas,
141+
typeAssigner: typeAssigner
142+
)
143+
cases = mappedTypes.map { mappedType in
156144
let comment: Comment? = .child(
157-
originalName: key,
158-
userDescription: schema.description,
145+
originalName: mappedType.typeName.shortSwiftName,
146+
userDescription: nil,
159147
parent: typeName
160148
)
161-
let associatedDeclarations: [Declaration]
162-
if TypeMatcher.isInlinable(schema) {
163-
associatedDeclarations = try translateSchema(
164-
typeName: childType.typeName,
165-
schema: schema,
166-
overrides: .none
149+
let caseName = safeSwiftNameForOneOfMappedType(mappedType)
150+
return (caseName, mappedType.rawNames, comment, mappedType.typeName.asUsage, [])
151+
}
152+
} else {
153+
cases = try schemas.enumerated()
154+
.map { index, schema in
155+
let key = "case\(index+1)"
156+
let childType = try typeAssigner.typeUsage(
157+
forAllOrAnyOrOneOfChildSchemaNamed: key,
158+
withSchema: schema,
159+
inParent: typeName
167160
)
168-
} else {
169-
associatedDeclarations = []
161+
let caseName: String
162+
// Only use the type name for the case for references,
163+
// as inline schemas have nothing that guarantees uniqueness.
164+
if schema.isReference {
165+
// Use the type name.
166+
caseName = childType.typeName.shortSwiftName
167+
} else {
168+
// Use a position-based key.
169+
caseName = key
170+
}
171+
let comment: Comment? = .child(
172+
originalName: key,
173+
userDescription: schema.description,
174+
parent: typeName
175+
)
176+
let associatedDeclarations: [Declaration]
177+
if TypeMatcher.isInlinable(schema) {
178+
associatedDeclarations = try translateSchema(
179+
typeName: childType.typeName,
180+
schema: schema,
181+
overrides: .none
182+
)
183+
} else {
184+
associatedDeclarations = []
185+
}
186+
return (caseName, nil, comment, childType, associatedDeclarations)
170187
}
171-
return (caseName, comment, childType, associatedDeclarations)
172-
}
188+
}
173189

174190
let caseDecls: [Declaration] = cases.flatMap { caseInfo in
175-
let (caseName, comment, childType, associatedDeclarations) = caseInfo
191+
let (caseName, _, comment, childType, associatedDeclarations) = caseInfo
176192
return associatedDeclarations + [
177193
.commentable(
178194
comment,
@@ -211,12 +227,9 @@ extension FileTranslator {
211227
]
212228
)
213229
]
214-
215-
let allTypes = cases.map { $0.2.typeName }
216-
let mappedTypes = try discriminator.mappedTypes(allTypes)
217230
decoder = translateOneOfWithDiscriminatorDecoder(
218231
discriminatorName: swiftName,
219-
cases: mappedTypes
232+
cases: cases.map { ($0.0, $0.1!) }
220233
)
221234
} else {
222235
undocumentedType = .valueContainer

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateCodable.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -420,23 +420,20 @@ extension FileTranslator {
420420
}
421421

422422
/// Returns a declaration of a oneOf with a discriminator decoder implementation.
423-
/// - Parameters:
424-
/// - caseNames: The cases to decode, first element is the raw string to check for, the second
425-
/// element is the case name (without the leading dot).
426423
func translateOneOfWithDiscriminatorDecoder(
427424
discriminatorName: String,
428-
cases: [OneOfMappedType]
425+
cases: [(caseName: String, rawNames: [String])]
429426
) -> Declaration {
430427
let cases: [SwitchCaseDescription] =
431428
cases
432-
.map { caseInfo in
429+
.map { caseName, rawNames in
433430
.init(
434-
kind: .case(.literal(caseInfo.rawName)),
431+
kind: .multiCase(rawNames.map { .literal($0) }),
435432
body: [
436433
.expression(
437434
.assignment(
438435
left: .identifier("self"),
439-
right: .dot(caseInfo.caseName)
436+
right: .dot(caseName)
440437
.call([
441438
.init(
442439
label: nil,

0 commit comments

Comments
 (0)