Skip to content

Commit 155bd4c

Browse files
Ignore inline schemas in oneOf with discriminator (#189)
### Motivation Currently, when provided with a `oneOf` with a `discriminator`, the generator will fail if any of the schemas are not references. However, the OpenAPI specification states that inline schemas should be ignored when using a discriminator: > When using the discriminator, inline schemas will not be considered. > — https://spec.openapis.org/oas/v3.0.3#discriminator-object The generator shouldn't fail when presented with a spec-compliant OpenAPI document, however misguided it might be. ### Modifications Filter out inline schemas during schema validation and translation. ### Result Can handle documents with `oneOf` types with a discriminator, containing inline schemas. ### Test Plan - Updated unit tests for supported/unsupported schemas. - Updated snippet test with an example, which failed before this patch. ### Resolves - Fixes #181 Signed-off-by: Si Beaumont <[email protected]>
1 parent 8f64727 commit 155bd4c

File tree

4 files changed

+86
-9
lines changed

4 files changed

+86
-9
lines changed

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,17 @@ 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+
}
127135

128136
let cases: [(String, Comment?, TypeUsage, [Declaration])] =
129-
try schemas
137+
try includedSchemas
130138
.enumerated()
131139
.map { index, schema in
132140
let key = "case\(index+1)"

Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,12 @@ extension FileTranslator {
157157
schema: schema
158158
)
159159
}
160-
// If a discriminator is provided, only refs to object/allOf of
161-
// object schemas are allowed.
162-
// Otherwise, any schema is allowed.
163160
guard context.discriminator != nil else {
164161
return try areSchemasSupported(schemas)
165162
}
166-
return try areRefsToObjectishSchemaAndSupported(schemas)
163+
// > When using the discriminator, inline schemas will not be considered.
164+
// > — https://spec.openapis.org/oas/v3.0.3#discriminator-object
165+
return try areRefsToObjectishSchemaAndSupported(schemas.filter(\.isReference))
167166
case .not:
168167
return .unsupported(
169168
reason: .schemaType,

Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_isSchemaSupported.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,12 @@ class Test_isSchemaSupported: XCTestCase {
7171
.array(items: .string),
7272
]),
7373

74-
// oneOf with a discriminator with two objectish schemas
74+
// oneOf with a discriminator with two objectish schemas and two (ignored) inline schemas
7575
.one(
7676
of: .reference(.component(named: "MyObj")),
7777
.reference(.component(named: "MyObj2")),
78+
.object,
79+
.boolean,
7880
discriminator: .init(propertyName: "foo")
7981
),
8082

@@ -120,9 +122,6 @@ class Test_isSchemaSupported: XCTestCase {
120122
.one(of: .reference(.internal(.component(name: "Foo"))), discriminator: .init(propertyName: "foo")),
121123
.notObjectish
122124
),
123-
124-
// a oneOf with a discriminator with an inline subschema
125-
(.one(of: .object, discriminator: .init(propertyName: "foo")), .notRef),
126125
]
127126
func testUnsupportedTypes() throws {
128127
let translator = self.translator

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,77 @@ final class SnippetBasedReferenceTests: XCTestCase {
375375
)
376376
}
377377

378+
func testComponentsSchemasOneOfWithDiscriminator() throws {
379+
try self.assertSchemasTranslation(
380+
"""
381+
schemas:
382+
A:
383+
type: object
384+
properties:
385+
which:
386+
type: string
387+
B:
388+
type: object
389+
properties:
390+
which:
391+
type: string
392+
MyOneOf:
393+
oneOf:
394+
- $ref: '#/components/schemas/A'
395+
- $ref: '#/components/schemas/B'
396+
- type: string
397+
- type: object
398+
properties:
399+
p:
400+
type: integer
401+
discriminator:
402+
propertyName: which
403+
mapping:
404+
a: '#/components/schemas/A'
405+
b: '#/components/schemas/B'
406+
""",
407+
"""
408+
public enum Schemas {
409+
public struct A: Codable, Equatable, Hashable, Sendable {
410+
public var which: Swift.String?
411+
public init(which: Swift.String? = nil) { self.which = which }
412+
public enum CodingKeys: String, CodingKey { case which }
413+
}
414+
public struct B: Codable, Equatable, Hashable, Sendable {
415+
public var which: Swift.String?
416+
public init(which: Swift.String? = nil) { self.which = which }
417+
public enum CodingKeys: String, CodingKey { case which }
418+
}
419+
@frozen public enum MyOneOf: Codable, Equatable, Hashable, Sendable {
420+
case A(Components.Schemas.A)
421+
case B(Components.Schemas.B)
422+
case undocumented(OpenAPIRuntime.OpenAPIObjectContainer)
423+
public enum CodingKeys: String, CodingKey { case which }
424+
public init(from decoder: any Decoder) throws {
425+
let container = try decoder.container(keyedBy: CodingKeys.self)
426+
let discriminator = try container.decode(String.self, forKey: .which)
427+
switch discriminator {
428+
case "a": self = .A(try .init(from: decoder))
429+
case "b": self = .B(try .init(from: decoder))
430+
default:
431+
let container = try decoder.singleValueContainer()
432+
let value = try container.decode(OpenAPIRuntime.OpenAPIObjectContainer.self)
433+
self = .undocumented(value)
434+
}
435+
}
436+
public func encode(to encoder: any Encoder) throws {
437+
switch self {
438+
case let .A(value): try value.encode(to: encoder)
439+
case let .B(value): try value.encode(to: encoder)
440+
case let .undocumented(value): try value.encode(to: encoder)
441+
}
442+
}
443+
}
444+
}
445+
"""
446+
)
447+
}
448+
378449
func testComponentsSchemasAllOfOneStringRef() throws {
379450
try self.assertSchemasTranslation(
380451
"""

0 commit comments

Comments
 (0)