Skip to content

Commit

Permalink
Ignore inline schemas in oneOf with discriminator (#189)
Browse files Browse the repository at this point in the history
### 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]>
  • Loading branch information
simonjbeaumont authored Aug 11, 2023
1 parent 8f64727 commit 155bd4c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,17 @@ extension FileTranslator {
discriminator: OpenAPI.Discriminator?,
schemas: [JSONSchema]
) throws -> Declaration {
// > When using the discriminator, inline schemas will not be considered.
// > — https://spec.openapis.org/oas/v3.0.3#discriminator-object
let includedSchemas: [JSONSchema]
if discriminator != nil {
includedSchemas = schemas.filter(\.isReference)
} else {
includedSchemas = schemas
}

let cases: [(String, Comment?, TypeUsage, [Declaration])] =
try schemas
try includedSchemas
.enumerated()
.map { index, schema in
let key = "case\(index+1)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,12 @@ extension FileTranslator {
schema: schema
)
}
// If a discriminator is provided, only refs to object/allOf of
// object schemas are allowed.
// Otherwise, any schema is allowed.
guard context.discriminator != nil else {
return try areSchemasSupported(schemas)
}
return try areRefsToObjectishSchemaAndSupported(schemas)
// > When using the discriminator, inline schemas will not be considered.
// > — https://spec.openapis.org/oas/v3.0.3#discriminator-object
return try areRefsToObjectishSchemaAndSupported(schemas.filter(\.isReference))
case .not:
return .unsupported(
reason: .schemaType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ class Test_isSchemaSupported: XCTestCase {
.array(items: .string),
]),

// oneOf with a discriminator with two objectish schemas
// oneOf with a discriminator with two objectish schemas and two (ignored) inline schemas
.one(
of: .reference(.component(named: "MyObj")),
.reference(.component(named: "MyObj2")),
.object,
.boolean,
discriminator: .init(propertyName: "foo")
),

Expand Down Expand Up @@ -120,9 +122,6 @@ class Test_isSchemaSupported: XCTestCase {
.one(of: .reference(.internal(.component(name: "Foo"))), discriminator: .init(propertyName: "foo")),
.notObjectish
),

// a oneOf with a discriminator with an inline subschema
(.one(of: .object, discriminator: .init(propertyName: "foo")), .notRef),
]
func testUnsupportedTypes() throws {
let translator = self.translator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,77 @@ final class SnippetBasedReferenceTests: XCTestCase {
)
}

func testComponentsSchemasOneOfWithDiscriminator() throws {
try self.assertSchemasTranslation(
"""
schemas:
A:
type: object
properties:
which:
type: string
B:
type: object
properties:
which:
type: string
MyOneOf:
oneOf:
- $ref: '#/components/schemas/A'
- $ref: '#/components/schemas/B'
- type: string
- type: object
properties:
p:
type: integer
discriminator:
propertyName: which
mapping:
a: '#/components/schemas/A'
b: '#/components/schemas/B'
""",
"""
public enum Schemas {
public struct A: Codable, Equatable, Hashable, Sendable {
public var which: Swift.String?
public init(which: Swift.String? = nil) { self.which = which }
public enum CodingKeys: String, CodingKey { case which }
}
public struct B: Codable, Equatable, Hashable, Sendable {
public var which: Swift.String?
public init(which: Swift.String? = nil) { self.which = which }
public enum CodingKeys: String, CodingKey { case which }
}
@frozen public enum MyOneOf: Codable, Equatable, Hashable, Sendable {
case A(Components.Schemas.A)
case B(Components.Schemas.B)
case undocumented(OpenAPIRuntime.OpenAPIObjectContainer)
public enum CodingKeys: String, CodingKey { case which }
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(String.self, forKey: .which)
switch discriminator {
case "a": self = .A(try .init(from: decoder))
case "b": self = .B(try .init(from: decoder))
default:
let container = try decoder.singleValueContainer()
let value = try container.decode(OpenAPIRuntime.OpenAPIObjectContainer.self)
self = .undocumented(value)
}
}
public func encode(to encoder: any Encoder) throws {
switch self {
case let .A(value): try value.encode(to: encoder)
case let .B(value): try value.encode(to: encoder)
case let .undocumented(value): try value.encode(to: encoder)
}
}
}
}
"""
)
}

func testComponentsSchemasAllOfOneStringRef() throws {
try self.assertSchemasTranslation(
"""
Expand Down

0 comments on commit 155bd4c

Please sign in to comment.