Skip to content

Commit ed61e74

Browse files
authored
Allow more types of subschemas in anyOf/allOf (#179)
1 parent e2f476d commit ed61e74

File tree

4 files changed

+62
-30
lines changed

4 files changed

+62
-30
lines changed

Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,15 @@ extension FileTranslator {
141141
schema: schema
142142
)
143143
}
144-
return try areObjectishSchemasAndSupported(schemas)
144+
return try areSchemasSupported(schemas)
145145
case .any(of: let schemas, _):
146146
guard !schemas.isEmpty else {
147147
return .unsupported(
148148
reason: .noSubschemas,
149149
schema: schema
150150
)
151151
}
152-
return try areObjectishSchemasAndSupported(schemas)
152+
return try areSchemasSupported(schemas)
153153
case .one(of: let schemas, let context):
154154
guard !schemas.isEmpty else {
155155
return .unsupported(
@@ -206,20 +206,6 @@ extension FileTranslator {
206206
return .supported
207207
}
208208

209-
/// Returns a result indicating whether the provided schemas
210-
/// are reference, object, or allOf schemas and supported.
211-
/// - Parameter schemas: Schemas to check.
212-
/// - Returns: `.supported` if all schemas match; `.unsupported` otherwise.
213-
func areObjectishSchemasAndSupported(_ schemas: [JSONSchema]) throws -> IsSchemaSupportedResult {
214-
for schema in schemas {
215-
let result = try isObjectishSchemaAndSupported(schema)
216-
guard result == .supported else {
217-
return result
218-
}
219-
}
220-
return .supported
221-
}
222-
223209
/// Returns a result indicating whether the provided schema
224210
/// is an reference, object, or allOf (object-ish) schema and is supported.
225211
/// - Parameter schema: A schemas to check.
@@ -228,7 +214,7 @@ extension FileTranslator {
228214
case .object, .reference:
229215
return try isSchemaSupported(schema)
230216
case .all(of: let schemas, _):
231-
return try areObjectishSchemasAndSupported(schemas)
217+
return try areSchemasSupported(schemas)
232218
default:
233219
return .unsupported(
234220
reason: .notObjectish,
@@ -256,8 +242,9 @@ extension FileTranslator {
256242
/// - Parameter schema: A schema to check.
257243
func isRefToObjectishSchemaAndSupported(_ schema: JSONSchema) throws -> IsSchemaSupportedResult {
258244
switch schema.value {
259-
case .reference:
260-
return try isObjectishSchemaAndSupported(schema)
245+
case let .reference(ref, _):
246+
let referencedSchema = try components.lookup(ref)
247+
return try isObjectishSchemaAndSupported(referencedSchema)
261248
default:
262249
return .unsupported(
263250
reason: .notRef,

Sources/swift-openapi-generator/Documentation.docc/Articles/Supported-OpenAPI-features.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,12 @@ Supported features are always provided on _both_ client and server.
142142
- [x] enum
143143
- [x] type
144144
- [x] allOf
145-
- a wrapper struct is generated and children must be object schemas
145+
- a wrapper struct is generated, children can be any schema
146146
- [x] oneOf
147-
- if a discriminator is specified (recommended), children must be object schemas
147+
- if a discriminator is specified, each child must be a reference to an object schema
148148
- if no discriminator is specified, children can be any schema
149149
- [x] anyOf
150-
- children must be object schemas
150+
- a wrapper struct is generated, children can be any schema
151151
- [ ] not
152152
- [x] items
153153
- [x] properties

Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_isSchemaSupported.swift

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Test_isSchemaSupported: XCTestCase {
2424
components: .init(schemas: [
2525
"Foo": .string,
2626
"MyObj": .object,
27+
"MyObj2": .object,
2728
])
2829
)
2930
}
@@ -60,28 +61,41 @@ class Test_isSchemaSupported: XCTestCase {
6061
)
6162
),
6263

63-
// allOf with two schemas
64+
// allOf with many schemas
6465
.all(of: [
6566
.object(properties: [
6667
"Foo": .string
6768
]),
6869
.reference(.component(named: "MyObj")),
70+
.string,
71+
.array(items: .string),
6972
]),
7073

71-
// oneOf with two schemas
74+
// oneOf with a discriminator with two objectish schemas
75+
.one(
76+
of: .reference(.component(named: "MyObj")),
77+
.reference(.component(named: "MyObj2")),
78+
discriminator: .init(propertyName: "foo")
79+
),
80+
81+
// oneOf without a discriminator with various schemas
7282
.one(of: [
7383
.object(properties: [
7484
"Foo": .string
7585
]),
7686
.reference(.component(named: "MyObj")),
87+
.string,
88+
.array(items: .string),
7789
]),
7890

79-
// anyOf with two schemas
91+
// anyOf with various schemas
8092
.any(of: [
8193
.object(properties: [
8294
"Foo": .string
8395
]),
8496
.reference(.component(named: "MyObj")),
97+
.string,
98+
.array(items: .string),
8599
]),
86100
]
87101
func testSupportedTypes() throws {
@@ -101,8 +115,11 @@ class Test_isSchemaSupported: XCTestCase {
101115
// an allOf without any subschemas
102116
(.all(of: []), .noSubschemas),
103117

104-
// an allOf with non-object-ish schemas
105-
(.all(of: [.string, .integer]), .notObjectish),
118+
// a oneOf with a discriminator with non-object-ish schemas
119+
(
120+
.one(of: .reference(.internal(.component(name: "Foo"))), discriminator: .init(propertyName: "foo")),
121+
.notObjectish
122+
),
106123

107124
// a oneOf with a discriminator with an inline subschema
108125
(.one(of: .object, discriminator: .init(propertyName: "foo")), .notRef),

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ final class SnippetBasedReferenceTests: XCTestCase {
224224
allOf:
225225
- $ref: '#/components/schemas/A'
226226
- $ref: '#/components/schemas/B'
227+
- type: string
228+
- type: array
229+
items:
230+
type: integer
227231
""",
228232
"""
229233
public enum Schemas {
@@ -232,20 +236,30 @@ final class SnippetBasedReferenceTests: XCTestCase {
232236
public struct MyAllOf: Codable, Equatable, Hashable, Sendable {
233237
public var value1: Components.Schemas.A
234238
public var value2: Components.Schemas.B
239+
public var value3: Swift.String
240+
public var value4: [Swift.Int]
235241
public init(
236242
value1: Components.Schemas.A,
237-
value2: Components.Schemas.B
243+
value2: Components.Schemas.B,
244+
value3: Swift.String,
245+
value4: [Swift.Int]
238246
) {
239247
self.value1 = value1
240248
self.value2 = value2
249+
self.value3 = value3
250+
self.value4 = value4
241251
}
242252
public init(from decoder: any Decoder) throws {
243253
value1 = try .init(from: decoder)
244254
value2 = try .init(from: decoder)
255+
value3 = try .init(from: decoder)
256+
value4 = try .init(from: decoder)
245257
}
246258
public func encode(to encoder: any Encoder) throws {
247259
try value1.encode(to: encoder)
248260
try value2.encode(to: encoder)
261+
try value3.encode(to: encoder)
262+
try value4.encode(to: encoder)
249263
}
250264
}
251265
}
@@ -263,6 +277,10 @@ final class SnippetBasedReferenceTests: XCTestCase {
263277
anyOf:
264278
- $ref: '#/components/schemas/A'
265279
- $ref: '#/components/schemas/B'
280+
- type: string
281+
- type: array
282+
items:
283+
type: integer
266284
""",
267285
"""
268286
public enum Schemas {
@@ -271,25 +289,35 @@ final class SnippetBasedReferenceTests: XCTestCase {
271289
public struct MyAnyOf: Codable, Equatable, Hashable, Sendable {
272290
public var value1: Components.Schemas.A?
273291
public var value2: Components.Schemas.B?
292+
public var value3: Swift.String?
293+
public var value4: [Swift.Int]?
274294
public init(
275295
value1: Components.Schemas.A? = nil,
276-
value2: Components.Schemas.B? = nil
296+
value2: Components.Schemas.B? = nil,
297+
value3: Swift.String? = nil,
298+
value4: [Swift.Int]? = nil
277299
) {
278300
self.value1 = value1
279301
self.value2 = value2
302+
self.value3 = value3
303+
self.value4 = value4
280304
}
281305
public init(from decoder: any Decoder) throws {
282306
value1 = try? .init(from: decoder)
283307
value2 = try? .init(from: decoder)
308+
value3 = try? .init(from: decoder)
309+
value4 = try? .init(from: decoder)
284310
try DecodingError.verifyAtLeastOneSchemaIsNotNil(
285-
[value1, value2],
311+
[value1, value2, value3, value4],
286312
type: Self.self,
287313
codingPath: decoder.codingPath
288314
)
289315
}
290316
public func encode(to encoder: any Encoder) throws {
291317
try value1?.encode(to: encoder)
292318
try value2?.encode(to: encoder)
319+
try value3?.encode(to: encoder)
320+
try value4?.encode(to: encoder)
293321
}
294322
}
295323
}

0 commit comments

Comments
 (0)