diff --git a/Examples/streaming-chatgpt-proxy/Sources/ChatGPT/Middleware.swift b/Examples/streaming-chatgpt-proxy/Sources/ChatGPT/Middleware.swift index e3e1bd9e1..e67d1d785 100644 --- a/Examples/streaming-chatgpt-proxy/Sources/ChatGPT/Middleware.swift +++ b/Examples/streaming-chatgpt-proxy/Sources/ChatGPT/Middleware.swift @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import Foundation +package import Foundation import HTTPTypes -import OpenAPIRuntime +package import OpenAPIRuntime package struct HeaderFieldMiddleware: ClientMiddleware { var name: HTTPField.Name diff --git a/Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift b/Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift index 4a7cacef5..b14212761 100644 --- a/Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift +++ b/Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift @@ -33,6 +33,20 @@ struct ImportDescription: Equatable, Codable { /// would be `@_spi(Secret) import Foo`. var spi: String? = nil + /// The access modifier to apply to the import statement. + /// + /// When set to `.public` or `.package`, the modifier is prepended to the + /// import statement (e.g. `public import Foo`). + var accessModifier: AccessModifier? = nil + + /// Whether the global access modifier from the generator config should be + /// applied to this import. + /// + /// Set to `false` for modules whose types don't appear in public + /// declarations (e.g. implementation-only imports), so that + /// `public import` or `package import` is not incorrectly emitted. + var setsAccessModifier: Bool = true + /// Requirements for the `@preconcurrency` attribute. var preconcurrency: PreconcurrencyRequirement = .never diff --git a/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift b/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift index b6d0a9c89..1fdb035fd 100644 --- a/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift +++ b/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift @@ -149,13 +149,21 @@ struct TextBasedRenderer: RendererProtocol { /// Renders a single import statement. func renderImport(_ description: ImportDescription) { + let accessModifierPrefix: String + switch description.accessModifier { + case .public: accessModifierPrefix = renderedAccessModifier(.public) + " " + case .package: accessModifierPrefix = renderedAccessModifier(.package) + " " + default: accessModifierPrefix = "" + } + func render(preconcurrency: Bool) { let spiPrefix = description.spi.map { "@_spi(\($0)) " } ?? "" let preconcurrencyPrefix = preconcurrency ? "@preconcurrency " : "" + let attributePrefix = "\(preconcurrencyPrefix)\(spiPrefix)" if let moduleTypes = description.moduleTypes { - for type in moduleTypes { writer.writeLine("\(preconcurrencyPrefix)\(spiPrefix)import \(type)") } + for type in moduleTypes { writer.writeLine("\(attributePrefix)\(accessModifierPrefix)import \(type)") } } else { - writer.writeLine("\(preconcurrencyPrefix)\(spiPrefix)import \(description.moduleName)") + writer.writeLine("\(attributePrefix)\(accessModifierPrefix)import \(description.moduleName)") } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift index 7e976a61b..58798e2ee 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift @@ -34,8 +34,7 @@ struct ClientFileTranslator: FileTranslator { let topComment = self.topComment - let imports = - Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) } + let imports = importDescriptions(adding: Constants.File.clientServerImports) let clientMethodDecls = try OperationDescription.all(from: doc.paths, in: components, context: context) .map(translateClientMethod(_:)) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift index 6f3d1782e..e79ab7fe9 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift @@ -45,7 +45,7 @@ enum Constants { /// The descriptions of modules imported by client and server files. static let clientServerImports: [ImportDescription] = - imports + [ImportDescription(moduleName: Constants.Import.httpTypes)] + imports + [ImportDescription(moduleName: Constants.Import.httpTypes, setsAccessModifier: false)] } /// Constants related to the OpenAPI server object. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift index 6d46f0264..4b5046c60 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -89,6 +89,31 @@ extension FileTranslator { var topComment: Comment { .inline(([Constants.File.topComment] + config.additionalFileComments).joined(separator: "\n")) } + + /// Returns the imports for the generated file, with access modifiers applied. + /// + /// The configured access modifier is propagated to the built-in imports so + /// that, under Swift 6's `InternalImportsByDefault` flag, generated + /// declarations can re-export the symbols they depend on. + /// `additionalImports` from the configuration are also given the same + /// access modifier so they are visible to consumers of the generated code. + /// - Parameter baseImports: the base set of imports for the file (e.g. + /// ``Constants/File/imports`` or ``Constants/File/clientServerImports``). + /// - Returns: An array of ``ImportDescription`` values with appropriate + /// access modifier set. + func importDescriptions(adding baseImports: [ImportDescription]) -> [ImportDescription] { + let accessModifier: AccessModifier? + switch config.access { + case .public, .package: accessModifier = config.access + default: accessModifier = nil + } + let allImports = baseImports + config.additionalImports.map { ImportDescription(moduleName: $0) } + return allImports.map { original in + var description = original + description.accessModifier = original.setsAccessModifier ? accessModifier : nil + return description + } + } } /// A set of configuration values for concrete file translators. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift index 2091a0f8c..427b25bbe 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift @@ -32,8 +32,7 @@ struct ServerFileTranslator: FileTranslator { let topComment = self.topComment - let imports = - Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) } + let imports = importDescriptions(adding: Constants.File.clientServerImports) let allOperations = try OperationDescription.all(from: doc.paths, in: components, context: context) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift index ea8e6aa9a..cdf447f7b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift @@ -34,7 +34,7 @@ struct TypesFileTranslator: FileTranslator { let topComment = self.topComment - let imports = Constants.File.imports + config.additionalImports.map { ImportDescription(moduleName: $0) } + let imports = importDescriptions(adding: Constants.File.imports) let apiProtocol = try translateAPIProtocol(doc.paths) diff --git a/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift b/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift index a8e16c7fa..b6c52da39 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift @@ -116,6 +116,76 @@ final class Test_TextBasedRenderer: XCTestCase { ) } + func testImportsWithPublicAccessModifier() throws { + try _test( + [ImportDescription(moduleName: "Foo", accessModifier: .public)], + renderedBy: TextBasedRenderer.renderImports, + rendersAs: #""" + public import Foo + """# + ) + } + + func testImportsWithPackageAccessModifier() throws { + try _test( + [ImportDescription(moduleName: "Foo", accessModifier: .package)], + renderedBy: TextBasedRenderer.renderImports, + rendersAs: #""" + package import Foo + """# + ) + } + + func testImportsWithInternalAccessModifier() throws { + try _test( + [ImportDescription(moduleName: "Foo", accessModifier: .internal)], + renderedBy: TextBasedRenderer.renderImports, + rendersAs: #""" + import Foo + """# + ) + } + + func testImportsWithAccessModifierAndAttributes() throws { + try _test( + [ImportDescription(moduleName: "Foo", spi: "Secret", accessModifier: .public, preconcurrency: .always)], + renderedBy: TextBasedRenderer.renderImports, + rendersAs: #""" + @preconcurrency @_spi(Secret) public import Foo + """# + ) + } + + func testImportsWithAccessModifierAndModuleTypes() throws { + try _test( + [ + ImportDescription( + moduleName: "Foundation", + moduleTypes: ["struct Foundation.URL"], + accessModifier: .public + ) + ], + renderedBy: TextBasedRenderer.renderImports, + rendersAs: #""" + public import struct Foundation.URL + """# + ) + } + + func testImportsWithAccessModifierAndPreconcurrencyOnOS() throws { + try _test( + [ImportDescription(moduleName: "Foo", accessModifier: .public, preconcurrency: .onOS(["Linux"]))], + renderedBy: TextBasedRenderer.renderImports, + rendersAs: #""" + #if os(Linux) + @preconcurrency public import Foo + #else + public import Foo + #endif + """# + ) + } + func testAccessModifiers() throws { try _test( .public, diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift index d9c80e62e..fbcde001b 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift @@ -1,13 +1,13 @@ // Generated by swift-openapi-generator, do not modify. -@_spi(Generated) import OpenAPIRuntime +@_spi(Generated) public import OpenAPIRuntime #if os(Linux) -@preconcurrency import struct Foundation.URL -@preconcurrency import struct Foundation.Data -@preconcurrency import struct Foundation.Date +@preconcurrency public import struct Foundation.URL +@preconcurrency public import struct Foundation.Data +@preconcurrency public import struct Foundation.Date #else -import struct Foundation.URL -import struct Foundation.Data -import struct Foundation.Date +public import struct Foundation.URL +public import struct Foundation.Data +public import struct Foundation.Date #endif import HTTPTypes /// Service for managing pet metadata. diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift index dae16f3f2..31e0375ed 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift @@ -1,13 +1,13 @@ // Generated by swift-openapi-generator, do not modify. -@_spi(Generated) import OpenAPIRuntime +@_spi(Generated) public import OpenAPIRuntime #if os(Linux) -@preconcurrency import struct Foundation.URL -@preconcurrency import struct Foundation.Data -@preconcurrency import struct Foundation.Date +@preconcurrency public import struct Foundation.URL +@preconcurrency public import struct Foundation.Data +@preconcurrency public import struct Foundation.Date #else -import struct Foundation.URL -import struct Foundation.Data -import struct Foundation.Date +public import struct Foundation.URL +public import struct Foundation.Data +public import struct Foundation.Date #endif import HTTPTypes extension APIProtocol { diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift index df2188cd7..be38b0f96 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift @@ -1,13 +1,13 @@ // Generated by swift-openapi-generator, do not modify. -@_spi(Generated) import OpenAPIRuntime +@_spi(Generated) public import OpenAPIRuntime #if os(Linux) -@preconcurrency import struct Foundation.URL -@preconcurrency import struct Foundation.Data -@preconcurrency import struct Foundation.Date +@preconcurrency public import struct Foundation.URL +@preconcurrency public import struct Foundation.Data +@preconcurrency public import struct Foundation.Date #else -import struct Foundation.URL -import struct Foundation.Data -import struct Foundation.Date +public import struct Foundation.URL +public import struct Foundation.Data +public import struct Foundation.Date #endif /// A type that performs HTTP operations defined by the OpenAPI document. public protocol APIProtocol: Sendable {