Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 10 additions & 2 deletions Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(_:))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
25 changes: 25 additions & 0 deletions Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down