Skip to content

Commit d7dcae0

Browse files
committed
Add feature flag for UUID format
1 parent b4a1a4b commit d7dcae0

File tree

15 files changed

+113
-38
lines changed

15 files changed

+113
-38
lines changed

Sources/_OpenAPIGeneratorCore/FeatureFlags.swift

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
public enum FeatureFlag: String, Hashable, Codable, CaseIterable, Sendable {
2929
// needs to be here for the enum to compile
3030
case empty
31+
32+
/// UUID support
33+
///
34+
/// Enable interpretation of `type: string, format: uuid` as `Foundation.UUID` typed data.
35+
case uuidSupport
3136
}
3237

3338
/// A set of enabled feature flags.

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ extension TypesFileTranslator {
6464
parent: typeName
6565
)
6666
let associatedDeclarations: [Declaration]
67-
if TypeMatcher.isInlinable(schema) {
67+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) {
6868
associatedDeclarations = try translateSchema(
6969
typeName: propertyType.typeName,
7070
schema: schema,
@@ -173,7 +173,7 @@ extension TypesFileTranslator {
173173
parent: typeName
174174
)
175175
let associatedDeclarations: [Declaration]
176-
if TypeMatcher.isInlinable(schema) {
176+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) {
177177
associatedDeclarations = try translateSchema(
178178
typeName: childType.typeName,
179179
schema: schema,

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ extension TypesFileTranslator {
8686
parent: typeName
8787
)
8888
let associatedDeclarations: [Declaration]
89-
if TypeMatcher.isInlinable(value) {
89+
if TypeMatcher.isInlinable(value, enableUUIDSupport: supportUUIDFormat) {
9090
associatedDeclarations = try translateSchema(
9191
typeName: propertyType.typeName,
9292
schema: value,
@@ -154,7 +154,7 @@ extension TypesFileTranslator {
154154
components: components,
155155
inParent: parent
156156
)
157-
if TypeMatcher.isInlinable(schema) {
157+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) {
158158
associatedDeclarations = try translateSchema(
159159
typeName: valueTypeUsage.typeName,
160160
schema: schema,

Sources/_OpenAPIGeneratorCore/Translator/FileTranslator+FeatureFlags.swift

+5
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ import OpenAPIKit
1515

1616
extension FileTranslator {
1717
// Add helpers for reading feature flags below.
18+
19+
/// A boolean value indicating whether the `uuid` format on schemas should be followed.
20+
var supportUUIDFormat: Bool {
21+
config.featureFlags.contains(.uuidSupport)
22+
}
1823
}

Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ extension TypesFileTranslator {
7676
inParent: typeName.appending(swiftComponent: nil, jsonComponent: "content")
7777
)
7878
let associatedDeclarations: [Declaration]
79-
if TypeMatcher.isInlinable(schema) {
79+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) {
8080
associatedDeclarations = try translateSchema(
8181
typeName: bodyTypeUsage.typeName,
8282
schema: schema,
@@ -117,7 +117,7 @@ extension TypesFileTranslator {
117117
schema: JSONSchema
118118
) throws -> [Declaration] {
119119
let associatedDeclarations: [Declaration]
120-
if TypeMatcher.isInlinable(schema) {
120+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) {
121121
associatedDeclarations = try translateSchema(typeName: typeName, schema: schema, overrides: .none)
122122
} else {
123123
associatedDeclarations = []

Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ struct TypedParameter {
3737
/// A converted function from user-provided strings to strings
3838
/// safe to be used as a Swift identifier.
3939
var asSwiftSafeName: (String) -> String
40+
41+
/// A boolean value indicating whether the `uuid` format on schemas should be followed.
42+
var supportUUIDFormat: Bool
4043
}
4144

4245
extension TypedParameter: CustomStringConvertible {
@@ -61,19 +64,19 @@ extension TypedParameter {
6164
/// A schema to be inlined.
6265
///
6366
/// - Returns: Nil when schema is referenceable.
64-
var inlineableSchema: JSONSchema? { schema.inlineableSchema }
67+
var inlineableSchema: JSONSchema? { schema.inlineableSchema(enableUUIDSupport: supportUUIDFormat) }
6568
}
6669

6770
extension UnresolvedSchema {
6871

6972
/// A schema to be inlined.
7073
///
7174
/// - Returns: Nil when schema is referenceable.
72-
var inlineableSchema: JSONSchema? {
75+
func inlineableSchema(enableUUIDSupport: Bool) -> JSONSchema? {
7376
switch self {
7477
case .a: return nil
7578
case let .b(schema):
76-
if TypeMatcher.isInlinable(schema) { return schema }
79+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: enableUUIDSupport) { return schema }
7780
return nil
7881
}
7982
}
@@ -208,7 +211,8 @@ extension FileTranslator {
208211
explode: explode,
209212
typeUsage: usage,
210213
codingStrategy: codingStrategy,
211-
asSwiftSafeName: swiftSafeName
214+
asSwiftSafeName: swiftSafeName,
215+
supportUUIDFormat: supportUUIDFormat
212216
)
213217
}
214218
}

Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ extension TypesFileTranslator {
4646
let contentTypeName = typeName.appending(jsonComponent: "content")
4747
let contents = requestBody.contents
4848
for content in contents {
49-
if TypeMatcher.isInlinable(content.content.schema) || content.content.isReferenceableMultipart {
49+
if TypeMatcher.isInlinable(content.content.schema, enableUUIDSupport: supportUUIDFormat) || content.content.isReferenceableMultipart {
5050
let inlineTypeDecls = try translateRequestBodyContentInTypes(content)
5151
bodyMembers.append(contentsOf: inlineTypeDecls)
5252
}

Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ extension TypesFileTranslator {
145145
let associatedType = typedContent.resolvedTypeUsage
146146
let content = typedContent.content
147147
let schema = content.schema
148-
if TypeMatcher.isInlinable(schema) || content.isReferenceableMultipart {
148+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) || content.isReferenceableMultipart {
149149
let decls: [Declaration]
150150
if contentType.isMultipart {
151151
decls = try translateMultipartBody(typedContent)

Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extension TypesFileTranslator {
6666
let schema = header.schema
6767
let typeUsage = header.typeUsage
6868
let associatedDeclarations: [Declaration]
69-
if TypeMatcher.isInlinable(schema) {
69+
if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) {
7070
associatedDeclarations = try translateSchema(typeName: typeUsage.typeName, schema: schema, overrides: .none)
7171
} else {
7272
associatedDeclarations = []

Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift

+10-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ struct TypeAssigner {
4444
/// A converted function from user-provided strings to strings
4545
/// safe to be used as a Swift identifier.
4646
var asSwiftSafeName: (String) -> String
47+
48+
/// A boolean value indicating whether the `uuid` format on schemas should be followed.
49+
var enableUUIDSupport: Bool
4750

4851
/// Returns a type name for an OpenAPI-named component type.
4952
///
@@ -328,7 +331,7 @@ struct TypeAssigner {
328331
inParent parent: TypeName,
329332
subtype: SubtypeNamingMethod
330333
) throws -> TypeUsage {
331-
let typeMatcher = TypeMatcher(asSwiftSafeName: asSwiftSafeName)
334+
let typeMatcher = TypeMatcher(asSwiftSafeName: asSwiftSafeName, enableUUIDSupport: enableUUIDSupport)
332335
// Check if this type can be simply referenced without
333336
// creating a new inline type.
334337
if let referenceableType = try typeMatcher.tryMatchReferenceableType(for: schema, components: components) {
@@ -545,10 +548,14 @@ struct TypeAssigner {
545548
extension FileTranslator {
546549
547550
/// A configured type assigner.
548-
var typeAssigner: TypeAssigner { TypeAssigner(asSwiftSafeName: swiftSafeName) }
551+
var typeAssigner: TypeAssigner {
552+
TypeAssigner(asSwiftSafeName: swiftSafeName, enableUUIDSupport: supportUUIDFormat)
553+
}
549554
550555
/// A configured type matcher.
551-
var typeMatcher: TypeMatcher { TypeMatcher(asSwiftSafeName: swiftSafeName) }
556+
var typeMatcher: TypeMatcher {
557+
TypeMatcher(asSwiftSafeName: swiftSafeName, enableUUIDSupport: supportUUIDFormat)
558+
}
552559
}
553560
554561
/// An error used during the parsing of JSON references specified in an

Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift

+39-16
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ struct TypeMatcher {
1919
/// A converted function from user-provided strings to strings
2020
/// safe to be used as a Swift identifier.
2121
var asSwiftSafeName: (String) -> String
22+
23+
/// Indication whether the uuid format should be respected.
24+
var enableUUIDSupport: Bool
2225

2326
/// Returns the type name of a built-in type that matches the specified
2427
/// schema.
@@ -43,7 +46,7 @@ struct TypeMatcher {
4346
func tryMatchBuiltinType(for schema: JSONSchema.Schema) -> TypeUsage? {
4447
Self._tryMatchRecursive(
4548
for: schema,
46-
test: { schema in Self._tryMatchBuiltinNonRecursive(for: schema) },
49+
test: { schema in Self._tryMatchBuiltinNonRecursive(for: schema, enableUUIDSupport: enableUUIDSupport) },
4750
matchedArrayHandler: { elementType, nullableItems in
4851
nullableItems ? elementType.asOptional.asArray : elementType.asArray
4952
},
@@ -69,9 +72,12 @@ struct TypeMatcher {
6972
try Self._tryMatchRecursive(
7073
for: schema.value,
7174
test: { (schema) -> TypeUsage? in
72-
if let builtinType = Self._tryMatchBuiltinNonRecursive(for: schema) { return builtinType }
75+
if let builtinType = Self._tryMatchBuiltinNonRecursive(for: schema, enableUUIDSupport: enableUUIDSupport) {
76+
return builtinType
77+
}
7378
guard case let .reference(ref, _) = schema else { return nil }
74-
return try TypeAssigner(asSwiftSafeName: asSwiftSafeName).typeName(for: ref).asUsage
79+
return try TypeAssigner(asSwiftSafeName: asSwiftSafeName, enableUUIDSupport: enableUUIDSupport)
80+
.typeName(for: ref).asUsage
7581
},
7682
matchedArrayHandler: { elementType, nullableItems in
7783
nullableItems ? elementType.asOptional.asArray : elementType.asArray
@@ -87,14 +93,16 @@ struct TypeMatcher {
8793
/// A referenceable schema is one of:
8894
/// - A builtin type
8995
/// - A reference
90-
/// - Parameter schema: The schema to match a referenceable type for.
96+
/// - Parameters:
97+
/// - schema: The schema to match a referenceable type for.
98+
/// - enableUUIDSupport: Indication whether the uuid format should be respected.
9199
/// - Returns: `true` if the schema is referenceable; `false` otherwise.
92-
static func isReferenceable(_ schema: JSONSchema) -> Bool {
100+
static func isReferenceable(_ schema: JSONSchema, enableUUIDSupport: Bool) -> Bool {
93101
// This logic should be kept in sync with `tryMatchReferenceableType`.
94102
_tryMatchRecursive(
95103
for: schema.value,
96104
test: { schema in
97-
if _tryMatchBuiltinNonRecursive(for: schema) != nil { return true }
105+
if _tryMatchBuiltinNonRecursive(for: schema, enableUUIDSupport: enableUUIDSupport) != nil { return true }
98106
guard case .reference = schema else { return false }
99107
return true
100108
},
@@ -109,9 +117,11 @@ struct TypeMatcher {
109117
/// A referenceable schema is one of:
110118
/// - A builtin type
111119
/// - A reference
112-
/// - Parameter schema: The schema to match a referenceable type for.
120+
/// - Parameters:
121+
/// - schema: The schema to match a referenceable type for.
122+
/// - enableUUIDSupport: Indication whether the uuid format should be respected.
113123
/// - Returns: `true` if the schema is referenceable; `false` otherwise.
114-
static func isReferenceable(_ schema: UnresolvedSchema?) -> Bool {
124+
static func isReferenceable(_ schema: UnresolvedSchema?, enableUUIDSupport: Bool) -> Bool {
115125
guard let schema else {
116126
// fragment type is referenceable
117127
return true
@@ -120,7 +130,7 @@ struct TypeMatcher {
120130
case .a:
121131
// is a reference
122132
return true
123-
case let .b(schema): return isReferenceable(schema)
133+
case let .b(schema): return isReferenceable(schema, enableUUIDSupport: enableUUIDSupport)
124134
}
125135
}
126136

@@ -131,9 +141,13 @@ struct TypeMatcher {
131141
///
132142
/// In other words, a type is inlinable if and only if it is not
133143
/// referenceable.
134-
/// - Parameter schema: The schema to match a referenceable type for.
144+
/// - Parameters:
145+
/// - schema: The schema to match a referenceable type for.
146+
/// - enableUUIDSupport: Indication whether the uuid format should be respected.
135147
/// - Returns: `true` if the schema is inlinable; `false` otherwise.
136-
static func isInlinable(_ schema: JSONSchema) -> Bool { !isReferenceable(schema) }
148+
static func isInlinable(_ schema: JSONSchema, enableUUIDSupport: Bool) -> Bool {
149+
!isReferenceable(schema, enableUUIDSupport: enableUUIDSupport)
150+
}
137151

138152
/// Returns a Boolean value that indicates whether the schema
139153
/// needs to be defined inline.
@@ -142,9 +156,13 @@ struct TypeMatcher {
142156
///
143157
/// In other words, a type is inlinable if and only if it is not
144158
/// referenceable.
145-
/// - Parameter schema: The schema to match a referenceable type for.
159+
/// - Parameters:
160+
/// - schema: The schema to match a referenceable type for.
161+
/// - enableUUIDSupport: Indication whether the uuid format should be respected.
146162
/// - Returns: `true` if the schema is inlinable; `false` otherwise.
147-
static func isInlinable(_ schema: UnresolvedSchema?) -> Bool { !isReferenceable(schema) }
163+
static func isInlinable(_ schema: UnresolvedSchema?, enableUUIDSupport: Bool) -> Bool {
164+
!isReferenceable(schema, enableUUIDSupport: enableUUIDSupport)
165+
}
148166

149167
/// Return a reference to a multipart element type if the provided schema is referenceable.
150168
/// - Parameters:
@@ -283,10 +301,15 @@ struct TypeMatcher {
283301
/// - Important: Optionality from the `JSONSchema` is not applied, since
284302
/// this function takes `JSONSchema.Schema`, and optionality is defined
285303
/// at the `JSONSchema` level.
286-
/// - Parameter schema: The schema to match a referenceable type for.
304+
/// - Parameters:
305+
/// - schema: The schema to match a referenceable type for.
306+
/// - enableUUIDSupport: Indication whether the uuid format should be respected.
287307
/// - Returns: A type usage for the schema if the schema is built-in.
288308
/// Otherwise, returns nil.
289-
private static func _tryMatchBuiltinNonRecursive(for schema: JSONSchema.Schema) -> TypeUsage? {
309+
private static func _tryMatchBuiltinNonRecursive(
310+
for schema: JSONSchema.Schema,
311+
enableUUIDSupport: Bool
312+
) -> TypeUsage? {
290313
let typeName: TypeName
291314
switch schema {
292315
case .boolean(_): typeName = .swift("Bool")
@@ -316,7 +339,7 @@ struct TypeMatcher {
316339
default:
317340
switch core.format {
318341
case .dateTime: typeName = .date
319-
case .uuid: typeName = .uuid
342+
case .uuid where enableUUIDSupport: typeName = .uuid
320343
default: typeName = .string
321344
}
322345
}

Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class Test_Core: XCTestCase {
5757

5858
var typeAssigner: TypeAssigner { makeTranslator().typeAssigner }
5959

60-
var typeMatcher: TypeMatcher { makeTranslator().typeMatcher }
60+
var typeMatcher: TypeMatcher { makeTranslator(featureFlags: [.uuidSupport]).typeMatcher }
6161

6262
var asSwiftSafeName: (String) -> String { makeTranslator().swiftSafeName }
6363

Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ final class Test_TypeMatcher: Test_Core {
128128
.fullyQualifiedSwiftName,
129129
name
130130
)
131-
XCTAssertTrue(TypeMatcher.isReferenceable(schema))
132-
XCTAssertFalse(TypeMatcher.isInlinable(schema))
131+
XCTAssertTrue(TypeMatcher.isReferenceable(schema, enableUUIDSupport: false))
132+
XCTAssertFalse(TypeMatcher.isInlinable(schema, enableUUIDSupport: false))
133133
}
134134
}
135135

@@ -146,8 +146,8 @@ final class Test_TypeMatcher: Test_Core {
146146
typeMatcher.tryMatchBuiltinType(for: schema.value),
147147
"Type is expected to not match a builtin type: \(schema)"
148148
)
149-
XCTAssertFalse(TypeMatcher.isReferenceable(schema), "Expected schema not to be referenceable: \(schema)")
150-
XCTAssertTrue(TypeMatcher.isInlinable(schema), "Expected schema to be inlinable: \(schema)")
149+
XCTAssertFalse(TypeMatcher.isReferenceable(schema, enableUUIDSupport: false), "Expected schema not to be referenceable: \(schema)")
150+
XCTAssertTrue(TypeMatcher.isInlinable(schema, enableUUIDSupport: false), "Expected schema to be inlinable: \(schema)")
151151
}
152152
}
153153

Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class FileBasedReferenceTests: XCTestCase {
4242
#endif
4343
}
4444

45-
func testPetstore() throws { try _test(referenceProject: .init(name: .petstore)) }
45+
func testPetstore() throws { try _test(referenceProject: .init(name: .petstore), featureFlags: [.uuidSupport]) }
4646

4747
// MARK: - Private
4848

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

+31
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,37 @@ final class SnippetBasedReferenceTests: XCTestCase {
14621462
"""
14631463
)
14641464
}
1465+
1466+
func testComponentsSchemasUUID() throws {
1467+
try self.assertSchemasTranslation(
1468+
featureFlags: [.uuidSupport],
1469+
"""
1470+
schemas:
1471+
MyUUID:
1472+
type: string
1473+
format: uuid
1474+
""",
1475+
"""
1476+
public enum Schemas {
1477+
public typealias MyUUID = Foundation.UUID
1478+
}
1479+
"""
1480+
)
1481+
// Without UUID support, the schema will be translated as a string
1482+
try self.assertSchemasTranslation(
1483+
"""
1484+
schemas:
1485+
MyUUID:
1486+
type: string
1487+
format: uuid
1488+
""",
1489+
"""
1490+
public enum Schemas {
1491+
public typealias MyUUID = Swift.String
1492+
}
1493+
"""
1494+
)
1495+
}
14651496

14661497
func testComponentsSchemasBase64() throws {
14671498
try self.assertSchemasTranslation(

0 commit comments

Comments
 (0)