Skip to content

Commit 82853e6

Browse files
committed
Support {oneOf: [{type: 'null'}, ...]} as optional
Fixes #513
1 parent 86c16d8 commit 82853e6

File tree

4 files changed

+56
-11
lines changed

4 files changed

+56
-11
lines changed

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

+13-5
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ extension TypesFileTranslator {
3838
/// - Throws: An error if there is an issue during translation.
3939
/// - Returns: A declaration representing the translated allOf/anyOf structure.
4040
func translateAllOrAnyOf(typeName: TypeName, openAPIDescription: String?, type: AllOrAnyOf, schemas: [JSONSchema])
41-
throws -> Declaration
41+
throws -> [Declaration]
4242
{
4343
let properties: [(property: PropertyBlueprint, isKeyValuePair: Bool)] = try schemas.enumerated()
4444
.map { index, schema in
@@ -107,7 +107,7 @@ extension TypesFileTranslator {
107107
properties: propertyValues
108108
)
109109
)
110-
return structDecl
110+
return [structDecl]
111111
}
112112

113113
/// Returns a declaration for a oneOf schema.
@@ -128,7 +128,7 @@ extension TypesFileTranslator {
128128
openAPIDescription: String?,
129129
discriminator: OpenAPI.Discriminator?,
130130
schemas: [JSONSchema]
131-
) throws -> Declaration {
131+
) throws -> [Declaration] {
132132
let cases: [(String, [String]?, Bool, Comment?, TypeUsage, [Declaration])]
133133
if let discriminator {
134134
// > When using the discriminator, inline schemas will not be considered.
@@ -148,7 +148,15 @@ extension TypesFileTranslator {
148148
return (caseName, mappedType.rawNames, true, comment, mappedType.typeName.asUsage, [])
149149
}
150150
} else {
151-
cases = try schemas.enumerated()
151+
let (schemas, nullSchemas) = schemas.partitioned(by: { typeMatcher.isNull($0) })
152+
if schemas.count == 1, nullSchemas.count > 0, let schema = schemas.first {
153+
return try translateSchema(
154+
typeName: typeName,
155+
schema: schema,
156+
overrides: .init(isOptional: true))
157+
}
158+
cases = try schemas
159+
.enumerated()
152160
.map { index, schema in
153161
let key = "case\(index+1)"
154162
let childType = try typeAssigner.typeUsage(
@@ -242,6 +250,6 @@ extension TypesFileTranslator {
242250
conformances: Constants.ObjectStruct.conformances,
243251
members: caseDecls + codingKeysDecls + [decoder, encoder]
244252
)
245-
return .commentable(comment, enumDecl)
253+
return [.commentable(comment, enumDecl)]
246254
}
247255
}

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateSchema.swift

+3-6
Original file line numberDiff line numberDiff line change
@@ -141,29 +141,26 @@ extension TypesFileTranslator {
141141
arrayContext: arrayContext
142142
)
143143
case let .all(of: schemas, core: coreContext):
144-
let allOfDecl = try translateAllOrAnyOf(
144+
return try translateAllOrAnyOf(
145145
typeName: typeName,
146146
openAPIDescription: overrides.userDescription ?? coreContext.description,
147147
type: .allOf,
148148
schemas: schemas
149149
)
150-
return [allOfDecl]
151150
case let .any(of: schemas, core: coreContext):
152-
let anyOfDecl = try translateAllOrAnyOf(
151+
return try translateAllOrAnyOf(
153152
typeName: typeName,
154153
openAPIDescription: overrides.userDescription ?? coreContext.description,
155154
type: .anyOf,
156155
schemas: schemas
157156
)
158-
return [anyOfDecl]
159157
case let .one(of: schemas, core: coreContext):
160-
let oneOfDecl = try translateOneOf(
158+
return try translateOneOf(
161159
typeName: typeName,
162160
openAPIDescription: overrides.userDescription ?? coreContext.description,
163161
discriminator: coreContext.discriminator,
164162
schemas: schemas
165163
)
166-
return [oneOfDecl]
167164
default: return []
168165
}
169166
}

Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift

+15
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,21 @@ struct TypeMatcher {
318318
}
319319
}
320320

321+
/// Returns a Boolean value indicating whether the schema admits only explicit null values.
322+
/// - Parameters:
323+
/// - schema: The schema to check.
324+
/// - Returns: `true` if the schema admits only explicit null values, `false` otherwise.
325+
func isNull(_ schema: JSONSchema) -> Bool {
326+
switch schema.value {
327+
case .null(_):
328+
return true
329+
case let .fragment(core):
330+
return core.format.jsonType == .null
331+
default:
332+
return false
333+
}
334+
}
335+
321336
// MARK: - Private
322337

323338
/// Returns the type name of a built-in type that matches the specified

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

+25
Original file line numberDiff line numberDiff line change
@@ -1761,6 +1761,31 @@ final class SnippetBasedReferenceTests: XCTestCase {
17611761
)
17621762
}
17631763

1764+
func testOneOfRefOrNull() throws {
1765+
try self.assertSchemasTranslation(
1766+
"""
1767+
schemas:
1768+
SomeString:
1769+
type: string
1770+
NullableRef:
1771+
oneOf:
1772+
- $ref: '#/components/schemas/SomeString'
1773+
- type: 'null'
1774+
ArrayOfNullableRefs:
1775+
type: array
1776+
items:
1777+
$ref: '#/components/schemas/NullableRef'
1778+
""",
1779+
"""
1780+
public enum Schemas {
1781+
public typealias SomeString = Swift.String
1782+
public typealias NullableRef = Components.Schemas.SomeString?
1783+
public typealias ArrayOfNullableRefs = [Components.Schemas.NullableRef]
1784+
}
1785+
"""
1786+
)
1787+
}
1788+
17641789
func testComponentsResponsesResponseNoBody() throws {
17651790
try self.assertResponsesTranslation(
17661791
"""

0 commit comments

Comments
 (0)