diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift index f6c2d48a..63670507 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift @@ -28,7 +28,7 @@ extension FileTranslator { let typealiasDescription = TypealiasDescription( accessModifier: config.access, name: typeName.shortSwiftName, - existingType: .init(existingTypeUsage.withOptional(false)) + existingType: .init(existingTypeUsage) ) let typealiasComment: Comment? = typeName.docCommentWithUserDescription(userDescription) return .commentable(typealiasComment, .typealias(typealiasDescription)) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift index c422bec0..dfc40f98 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift @@ -70,7 +70,7 @@ extension TypesFileTranslator { let decl = try translateSchema( typeName: typeName, schema: parameter.schema, - overrides: .init(isOptional: !parameter.required, userDescription: parameter.parameter.description) + overrides: .init(userDescription: parameter.parameter.description) ) return decl } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift index 229d575a..707761d6 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift @@ -114,7 +114,7 @@ extension TypesFileTranslator { let decl = try translateSchema( typeName: typeName, schema: header.schema, - overrides: .init(isOptional: header.isOptional, userDescription: header.header.description) + overrides: .init(userDescription: header.header.description) ) return decl } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 72e18f8a..5fc3a8ec 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -345,7 +345,7 @@ struct TypeAssigner { swiftComponent: asSwiftSafeName(originalName) + suffix, jsonComponent: jsonReferenceComponentOverride ?? originalName ) - .asUsage.withOptional(try typeMatcher.isOptional(schema, components: components)) + .asUsage.withOptional(try typeMatcher.isOptionalRoot(schema, components: components)) } /// Returns a type name for a reusable component. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index 18707774..0671774a 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -78,7 +78,7 @@ struct TypeMatcher { }, genericArrayHandler: { TypeName.arrayContainer.asUsage } )? - .withOptional(isOptional(schema, components: components)) + .withOptional(isOptionalRoot(schema, components: components)) } /// Returns a Boolean value that indicates whether the schema @@ -331,6 +331,25 @@ struct TypeMatcher { return result } + /// Returns a Boolean value indicating whether the schema is optional at the root of any references. + /// - Parameters: + /// - schema: The reference to check. + /// - components: The OpenAPI components for looking up references. + /// - Throws: An error if there's an issue while checking the schema. + /// - Returns: `true` if the schema is an optional root, `false` otherwise. + func isOptionalRoot(_ schema: JSONSchema, components: OpenAPI.Components) throws -> Bool { + let directlyOptional = schema.nullable || !schema.required + switch schema.value { + case .null(_): + return true + case .reference(let ref, _): + let indirectlyOptional = try isOptional(ref, components: components) + return directlyOptional && !indirectlyOptional + default: + return directlyOptional + } + } + // MARK: - Private /// Returns the type name of a built-in type that matches the specified diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 4b308362..da4af56d 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -237,7 +237,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """, """ public enum Schemas { - public typealias Null = OpenAPIRuntime.OpenAPIValueContainer + public typealias Null = OpenAPIRuntime.OpenAPIValueContainer? public typealias NullArray = [Components.Schemas.Null] } """) @@ -520,17 +520,17 @@ final class SnippetBasedReferenceTests: XCTestCase { """ public enum Schemas { public typealias MyRequiredString = Swift.String - public typealias MyNullableString = Swift.String + public typealias MyNullableString = Swift.String? public struct MyObject: Codable, Hashable, Sendable { public var id: Swift.Int64 public var alias: Swift.String? public var requiredString: Components.Schemas.MyRequiredString - public var nullableString: Components.Schemas.MyNullableString? + public var nullableString: Components.Schemas.MyNullableString public init( id: Swift.Int64, alias: Swift.String? = nil, requiredString: Components.Schemas.MyRequiredString, - nullableString: Components.Schemas.MyNullableString? = nil + nullableString: Components.Schemas.MyNullableString ) { self.id = id self.alias = alias