diff --git a/Sources/_OpenAPIGeneratorCore/FeatureFlags.swift b/Sources/_OpenAPIGeneratorCore/FeatureFlags.swift index ca3d5682..5e793e72 100644 --- a/Sources/_OpenAPIGeneratorCore/FeatureFlags.swift +++ b/Sources/_OpenAPIGeneratorCore/FeatureFlags.swift @@ -28,6 +28,11 @@ public enum FeatureFlag: String, Hashable, Codable, CaseIterable, Sendable { // needs to be here for the enum to compile case empty + + /// UUID support + /// + /// Enable interpretation of `type: string, format: uuid` as `Foundation.UUID` typed data. + case uuidSupport } /// A set of enabled feature flags. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index ad7a1b63..2fa99e62 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift @@ -64,7 +64,7 @@ extension TypesFileTranslator { parent: typeName ) let associatedDeclarations: [Declaration] - if TypeMatcher.isInlinable(schema) { + if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) { associatedDeclarations = try translateSchema( typeName: propertyType.typeName, schema: schema, @@ -173,7 +173,7 @@ extension TypesFileTranslator { parent: typeName ) let associatedDeclarations: [Declaration] - if TypeMatcher.isInlinable(schema) { + if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) { associatedDeclarations = try translateSchema( typeName: childType.typeName, schema: schema, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift index a55517b4..4d922672 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift @@ -86,7 +86,7 @@ extension TypesFileTranslator { parent: typeName ) let associatedDeclarations: [Declaration] - if TypeMatcher.isInlinable(value) { + if TypeMatcher.isInlinable(value, enableUUIDSupport: supportUUIDFormat) { associatedDeclarations = try translateSchema( typeName: propertyType.typeName, schema: value, @@ -154,7 +154,7 @@ extension TypesFileTranslator { components: components, inParent: parent ) - if TypeMatcher.isInlinable(schema) { + if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) { associatedDeclarations = try translateSchema( typeName: valueTypeUsage.typeName, schema: schema, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator+FeatureFlags.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator+FeatureFlags.swift index 4527bbe9..e509bf35 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator+FeatureFlags.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator+FeatureFlags.swift @@ -15,4 +15,9 @@ import OpenAPIKit extension FileTranslator { // Add helpers for reading feature flags below. + + /// A boolean value indicating whether the `uuid` format on schemas should be followed. + var supportUUIDFormat: Bool { + config.featureFlags.contains(.uuidSupport) + } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift index 186cfda8..c0a97059 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift @@ -76,7 +76,7 @@ extension TypesFileTranslator { inParent: typeName.appending(swiftComponent: nil, jsonComponent: "content") ) let associatedDeclarations: [Declaration] - if TypeMatcher.isInlinable(schema) { + if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) { associatedDeclarations = try translateSchema( typeName: bodyTypeUsage.typeName, schema: schema, @@ -117,7 +117,7 @@ extension TypesFileTranslator { schema: JSONSchema ) throws -> [Declaration] { let associatedDeclarations: [Declaration] - if TypeMatcher.isInlinable(schema) { + if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) { associatedDeclarations = try translateSchema(typeName: typeName, schema: schema, overrides: .none) } else { associatedDeclarations = [] diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift index d84f9d3d..e8e7b3bf 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -37,6 +37,9 @@ struct TypedParameter { /// A converted function from user-provided strings to strings /// safe to be used as a Swift identifier. var asSwiftSafeName: (String) -> String + + /// A boolean value indicating whether the `uuid` format on schemas should be followed. + var supportUUIDFormat: Bool } extension TypedParameter: CustomStringConvertible { @@ -61,7 +64,7 @@ extension TypedParameter { /// A schema to be inlined. /// /// - Returns: Nil when schema is referenceable. - var inlineableSchema: JSONSchema? { schema.inlineableSchema } + var inlineableSchema: JSONSchema? { schema.inlineableSchema(enableUUIDSupport: supportUUIDFormat) } } extension UnresolvedSchema { @@ -69,11 +72,11 @@ extension UnresolvedSchema { /// A schema to be inlined. /// /// - Returns: Nil when schema is referenceable. - var inlineableSchema: JSONSchema? { + func inlineableSchema(enableUUIDSupport: Bool) -> JSONSchema? { switch self { case .a: return nil case let .b(schema): - if TypeMatcher.isInlinable(schema) { return schema } + if TypeMatcher.isInlinable(schema, enableUUIDSupport: enableUUIDSupport) { return schema } return nil } } @@ -208,7 +211,8 @@ extension FileTranslator { explode: explode, typeUsage: usage, codingStrategy: codingStrategy, - asSwiftSafeName: swiftSafeName + asSwiftSafeName: swiftSafeName, + supportUUIDFormat: supportUUIDFormat ) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift index 76c0c6f7..9ae3cdc2 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift @@ -46,7 +46,7 @@ extension TypesFileTranslator { let contentTypeName = typeName.appending(jsonComponent: "content") let contents = requestBody.contents for content in contents { - if TypeMatcher.isInlinable(content.content.schema) || content.content.isReferenceableMultipart { + if TypeMatcher.isInlinable(content.content.schema, enableUUIDSupport: supportUUIDFormat) || content.content.isReferenceableMultipart { let inlineTypeDecls = try translateRequestBodyContentInTypes(content) bodyMembers.append(contentsOf: inlineTypeDecls) } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift index 13a44064..cc53596c 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift @@ -145,7 +145,7 @@ extension TypesFileTranslator { let associatedType = typedContent.resolvedTypeUsage let content = typedContent.content let schema = content.schema - if TypeMatcher.isInlinable(schema) || content.isReferenceableMultipart { + if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) || content.isReferenceableMultipart { let decls: [Declaration] if contentType.isMultipart { decls = try translateMultipartBody(typedContent) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift index 229d575a..a57a2bc1 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift @@ -66,7 +66,7 @@ extension TypesFileTranslator { let schema = header.schema let typeUsage = header.typeUsage let associatedDeclarations: [Declaration] - if TypeMatcher.isInlinable(schema) { + if TypeMatcher.isInlinable(schema, enableUUIDSupport: supportUUIDFormat) { associatedDeclarations = try translateSchema(typeName: typeUsage.typeName, schema: schema, overrides: .none) } else { associatedDeclarations = [] diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 72e18f8a..c3ea57f5 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -44,6 +44,9 @@ struct TypeAssigner { /// A converted function from user-provided strings to strings /// safe to be used as a Swift identifier. var asSwiftSafeName: (String) -> String + + /// A boolean value indicating whether the `uuid` format on schemas should be followed. + var enableUUIDSupport: Bool /// Returns a type name for an OpenAPI-named component type. /// @@ -328,7 +331,7 @@ struct TypeAssigner { inParent parent: TypeName, subtype: SubtypeNamingMethod ) throws -> TypeUsage { - let typeMatcher = TypeMatcher(asSwiftSafeName: asSwiftSafeName) + let typeMatcher = TypeMatcher(asSwiftSafeName: asSwiftSafeName, enableUUIDSupport: enableUUIDSupport) // Check if this type can be simply referenced without // creating a new inline type. if let referenceableType = try typeMatcher.tryMatchReferenceableType(for: schema, components: components) { @@ -545,10 +548,14 @@ struct TypeAssigner { extension FileTranslator { /// A configured type assigner. - var typeAssigner: TypeAssigner { TypeAssigner(asSwiftSafeName: swiftSafeName) } + var typeAssigner: TypeAssigner { + TypeAssigner(asSwiftSafeName: swiftSafeName, enableUUIDSupport: supportUUIDFormat) + } /// A configured type matcher. - var typeMatcher: TypeMatcher { TypeMatcher(asSwiftSafeName: swiftSafeName) } + var typeMatcher: TypeMatcher { + TypeMatcher(asSwiftSafeName: swiftSafeName, enableUUIDSupport: supportUUIDFormat) + } } /// An error used during the parsing of JSON references specified in an diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index c7fc363e..7f05f0f4 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -19,6 +19,9 @@ struct TypeMatcher { /// A converted function from user-provided strings to strings /// safe to be used as a Swift identifier. var asSwiftSafeName: (String) -> String + + /// Indication whether the uuid format should be respected. + var enableUUIDSupport: Bool /// Returns the type name of a built-in type that matches the specified /// schema. @@ -43,7 +46,7 @@ struct TypeMatcher { func tryMatchBuiltinType(for schema: JSONSchema.Schema) -> TypeUsage? { Self._tryMatchRecursive( for: schema, - test: { schema in Self._tryMatchBuiltinNonRecursive(for: schema) }, + test: { schema in Self._tryMatchBuiltinNonRecursive(for: schema, enableUUIDSupport: enableUUIDSupport) }, matchedArrayHandler: { elementType, nullableItems in nullableItems ? elementType.asOptional.asArray : elementType.asArray }, @@ -69,9 +72,12 @@ struct TypeMatcher { try Self._tryMatchRecursive( for: schema.value, test: { (schema) -> TypeUsage? in - if let builtinType = Self._tryMatchBuiltinNonRecursive(for: schema) { return builtinType } + if let builtinType = Self._tryMatchBuiltinNonRecursive(for: schema, enableUUIDSupport: enableUUIDSupport) { + return builtinType + } guard case let .reference(ref, _) = schema else { return nil } - return try TypeAssigner(asSwiftSafeName: asSwiftSafeName).typeName(for: ref).asUsage + return try TypeAssigner(asSwiftSafeName: asSwiftSafeName, enableUUIDSupport: enableUUIDSupport) + .typeName(for: ref).asUsage }, matchedArrayHandler: { elementType, nullableItems in nullableItems ? elementType.asOptional.asArray : elementType.asArray @@ -87,14 +93,16 @@ struct TypeMatcher { /// A referenceable schema is one of: /// - A builtin type /// - A reference - /// - Parameter schema: The schema to match a referenceable type for. + /// - Parameters: + /// - schema: The schema to match a referenceable type for. + /// - enableUUIDSupport: Indication whether the uuid format should be respected. /// - Returns: `true` if the schema is referenceable; `false` otherwise. - static func isReferenceable(_ schema: JSONSchema) -> Bool { + static func isReferenceable(_ schema: JSONSchema, enableUUIDSupport: Bool) -> Bool { // This logic should be kept in sync with `tryMatchReferenceableType`. _tryMatchRecursive( for: schema.value, test: { schema in - if _tryMatchBuiltinNonRecursive(for: schema) != nil { return true } + if _tryMatchBuiltinNonRecursive(for: schema, enableUUIDSupport: enableUUIDSupport) != nil { return true } guard case .reference = schema else { return false } return true }, @@ -109,9 +117,11 @@ struct TypeMatcher { /// A referenceable schema is one of: /// - A builtin type /// - A reference - /// - Parameter schema: The schema to match a referenceable type for. + /// - Parameters: + /// - schema: The schema to match a referenceable type for. + /// - enableUUIDSupport: Indication whether the uuid format should be respected. /// - Returns: `true` if the schema is referenceable; `false` otherwise. - static func isReferenceable(_ schema: UnresolvedSchema?) -> Bool { + static func isReferenceable(_ schema: UnresolvedSchema?, enableUUIDSupport: Bool) -> Bool { guard let schema else { // fragment type is referenceable return true @@ -120,7 +130,7 @@ struct TypeMatcher { case .a: // is a reference return true - case let .b(schema): return isReferenceable(schema) + case let .b(schema): return isReferenceable(schema, enableUUIDSupport: enableUUIDSupport) } } @@ -131,9 +141,13 @@ struct TypeMatcher { /// /// In other words, a type is inlinable if and only if it is not /// referenceable. - /// - Parameter schema: The schema to match a referenceable type for. + /// - Parameters: + /// - schema: The schema to match a referenceable type for. + /// - enableUUIDSupport: Indication whether the uuid format should be respected. /// - Returns: `true` if the schema is inlinable; `false` otherwise. - static func isInlinable(_ schema: JSONSchema) -> Bool { !isReferenceable(schema) } + static func isInlinable(_ schema: JSONSchema, enableUUIDSupport: Bool) -> Bool { + !isReferenceable(schema, enableUUIDSupport: enableUUIDSupport) + } /// Returns a Boolean value that indicates whether the schema /// needs to be defined inline. @@ -142,9 +156,13 @@ struct TypeMatcher { /// /// In other words, a type is inlinable if and only if it is not /// referenceable. - /// - Parameter schema: The schema to match a referenceable type for. + /// - Parameters: + /// - schema: The schema to match a referenceable type for. + /// - enableUUIDSupport: Indication whether the uuid format should be respected. /// - Returns: `true` if the schema is inlinable; `false` otherwise. - static func isInlinable(_ schema: UnresolvedSchema?) -> Bool { !isReferenceable(schema) } + static func isInlinable(_ schema: UnresolvedSchema?, enableUUIDSupport: Bool) -> Bool { + !isReferenceable(schema, enableUUIDSupport: enableUUIDSupport) + } /// Return a reference to a multipart element type if the provided schema is referenceable. /// - Parameters: @@ -283,10 +301,15 @@ struct TypeMatcher { /// - Important: Optionality from the `JSONSchema` is not applied, since /// this function takes `JSONSchema.Schema`, and optionality is defined /// at the `JSONSchema` level. - /// - Parameter schema: The schema to match a referenceable type for. + /// - Parameters: + /// - schema: The schema to match a referenceable type for. + /// - enableUUIDSupport: Indication whether the uuid format should be respected. /// - Returns: A type usage for the schema if the schema is built-in. /// Otherwise, returns nil. - private static func _tryMatchBuiltinNonRecursive(for schema: JSONSchema.Schema) -> TypeUsage? { + private static func _tryMatchBuiltinNonRecursive( + for schema: JSONSchema.Schema, + enableUUIDSupport: Bool + ) -> TypeUsage? { let typeName: TypeName switch schema { case .boolean(_): typeName = .swift("Bool") @@ -316,7 +339,7 @@ struct TypeMatcher { default: switch core.format { case .dateTime: typeName = .date - case .uuid: typeName = .uuid + case .uuid where enableUUIDSupport: typeName = .uuid default: typeName = .string } } diff --git a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift index 67d2e94a..867dce42 100644 --- a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift +++ b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift @@ -57,7 +57,7 @@ class Test_Core: XCTestCase { var typeAssigner: TypeAssigner { makeTranslator().typeAssigner } - var typeMatcher: TypeMatcher { makeTranslator().typeMatcher } + var typeMatcher: TypeMatcher { makeTranslator(featureFlags: [.uuidSupport]).typeMatcher } var asSwiftSafeName: (String) -> String { makeTranslator().swiftSafeName } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift index 434c4a3f..874163ad 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift @@ -128,8 +128,8 @@ final class Test_TypeMatcher: Test_Core { .fullyQualifiedSwiftName, name ) - XCTAssertTrue(TypeMatcher.isReferenceable(schema)) - XCTAssertFalse(TypeMatcher.isInlinable(schema)) + XCTAssertTrue(TypeMatcher.isReferenceable(schema, enableUUIDSupport: false)) + XCTAssertFalse(TypeMatcher.isInlinable(schema, enableUUIDSupport: false)) } } @@ -146,8 +146,8 @@ final class Test_TypeMatcher: Test_Core { typeMatcher.tryMatchBuiltinType(for: schema.value), "Type is expected to not match a builtin type: \(schema)" ) - XCTAssertFalse(TypeMatcher.isReferenceable(schema), "Expected schema not to be referenceable: \(schema)") - XCTAssertTrue(TypeMatcher.isInlinable(schema), "Expected schema to be inlinable: \(schema)") + XCTAssertFalse(TypeMatcher.isReferenceable(schema, enableUUIDSupport: false), "Expected schema not to be referenceable: \(schema)") + XCTAssertTrue(TypeMatcher.isInlinable(schema, enableUUIDSupport: false), "Expected schema to be inlinable: \(schema)") } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift index 1e9e6786..154b1788 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift @@ -42,7 +42,7 @@ class FileBasedReferenceTests: XCTestCase { #endif } - func testPetstore() throws { try _test(referenceProject: .init(name: .petstore)) } + func testPetstore() throws { try _test(referenceProject: .init(name: .petstore), featureFlags: [.uuidSupport]) } // MARK: - Private diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 0196c5ee..87abf3f4 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -1462,6 +1462,37 @@ final class SnippetBasedReferenceTests: XCTestCase { """ ) } + + func testComponentsSchemasUUID() throws { + try self.assertSchemasTranslation( + featureFlags: [.uuidSupport], + """ + schemas: + MyUUID: + type: string + format: uuid + """, + """ + public enum Schemas { + public typealias MyUUID = Foundation.UUID + } + """ + ) + // Without UUID support, the schema will be translated as a string + try self.assertSchemasTranslation( + """ + schemas: + MyUUID: + type: string + format: uuid + """, + """ + public enum Schemas { + public typealias MyUUID = Swift.String + } + """ + ) + } func testComponentsSchemasBase64() throws { try self.assertSchemasTranslation(