Skip to content

Commit 06b0a72

Browse files
authored
Use the package access modifier for generated code (#393)
### Motivation Fixes #270. The `package` access modifier seems like the right default access modifier for generated code, as it forces adopters to explicitly opt into generated APIs leaking outside of their package. `internal` was too limiting, as often you want to isolate generated code into a separate module. Fortunately, Swift 5.9 provided that feature and since 1.0 will ship with 5.9 as minimum Swift version, we can switch to `package` as default. ### Modifications - (Re)added the ability to customize the access modifier of the generated code. You can do so from the config file and with a CLI option. - The default has changed from `public` to `package` (a breaking change). - Updated docs (some drive-by docs improvements as well for which OpenAPI features are supported.) ### Result Generated code uses the `package` access modifier by default, but the user can change it to `public` or `internal`, if needed. ### Test Plan Manually tested the CLI path, the config file path, updated tests, but didn't actually change our fixtures, as there's no need. We just explicitly override to `public`. I also tried this branch on a real-world document using the generator plugin, and tried all 3 access modifiers and verified all 3 worked (the project built successfully).
1 parent 972adfd commit 06b0a72

17 files changed

+160
-31
lines changed

Sources/_OpenAPIGeneratorCore/Config.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ public struct Config: Sendable {
2323
/// The generator mode to use.
2424
public var mode: GeneratorMode
2525

26+
/// The access modifier to use for generated declarations.
27+
public var access: AccessModifier
28+
29+
/// The default access modifier.
30+
public static let defaultAccessModifier: AccessModifier = .package
31+
2632
/// Additional imports to add to each generated file.
2733
public var additionalImports: [String]
2834

@@ -35,23 +41,21 @@ public struct Config: Sendable {
3541
/// Creates a configuration with the specified generator mode and imports.
3642
/// - Parameters:
3743
/// - mode: The mode to use for generation.
44+
/// - access: The access modifier to use for generated declarations.
3845
/// - additionalImports: Additional imports to add to each generated file.
3946
/// - filter: Filter to apply to the OpenAPI document before generation.
4047
/// - featureFlags: Additional pre-release features to enable.
4148
public init(
4249
mode: GeneratorMode,
50+
access: AccessModifier,
4351
additionalImports: [String] = [],
4452
filter: DocumentFilter? = nil,
4553
featureFlags: FeatureFlags = []
4654
) {
4755
self.mode = mode
56+
self.access = access
4857
self.additionalImports = additionalImports
4958
self.filter = filter
5059
self.featureFlags = featureFlags
5160
}
5261
}
53-
54-
extension Config {
55-
/// Returns the access modifier to use for generated declarations.
56-
var access: AccessModifier? { .public }
57-
}

Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ struct ImportDescription: Equatable, Codable {
5050
/// A description of an access modifier.
5151
///
5252
/// For example: `public`.
53-
enum AccessModifier: String, Equatable, Codable {
53+
public enum AccessModifier: String, Sendable, Equatable, Codable {
5454

5555
/// A declaration accessible outside of the module.
5656
case `public`
5757

58+
/// A declaration accessible outside of the module but only inside the containing package or project.
59+
case `package`
60+
5861
/// A declaration only accessible inside of the module.
5962
case `internal`
6063

Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ struct TextBasedRenderer: RendererProtocol {
175175
func renderedAccessModifier(_ accessModifier: AccessModifier) -> String {
176176
switch accessModifier {
177177
case .public: return "public"
178+
case .package: return "package"
178179
case .internal: return "internal"
179180
case .fileprivate: return "fileprivate"
180181
case .private: return "private"

Sources/swift-openapi-generator/Documentation.docc/Articles/Configuring-the-generator.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ The configuration file has the following keys:
3030
- `types`: Common types and abstractions used by generated client and server code.
3131
- `client`: Client code that can be used with any client transport (depends on code from `types`).
3232
- `server`: Server code that can be used with any server transport (depends on code from `types`).
33+
- `accessModifier` (optional): a string. Customizes the visibility of the API of the generated code.
34+
- `public`: Generated API is accessible from other modules and other packages (if included in a product).
35+
- `package` (default): Generated API is accessible from other modules within the same package or project.
36+
- `internal`: Generated API is accessible from the containing module only.
3337
- `additionalImports` (optional): array of strings. Each string value is a Swift module name. An import statement will be added to the generated source files for each module.
3438
- `filter`: (optional): Filters to apply to the OpenAPI document before generation.
3539
- `operations`: Operations with these operation IDs will be included in the filter.
@@ -38,7 +42,6 @@ The configuration file has the following keys:
3842
- `schemas`: These (additional) schemas will be included in the filter.
3943
- `featureFlags` (optional): array of strings. Each string must be a valid feature flag to enable. For a list of currently supported feature flags, check out [FeatureFlags.swift](https://github.com/apple/swift-openapi-generator/blob/main/Sources/_OpenAPIGeneratorCore/FeatureFlags.swift).
4044

41-
4245
### Example config files
4346

4447
To generate client code in a single target:
@@ -73,6 +76,16 @@ additionalImports:
7376
- APITypes
7477
```
7578

79+
To use the generated code from other packages, also customize the access modifier:
80+
81+
```yaml
82+
generate:
83+
- client
84+
additionalImports:
85+
- APITypes
86+
accessModifier: public
87+
```
88+
7689
### Document filtering
7790

7891
The generator supports filtering the OpenAPI document prior to generation, which can be useful when

Sources/swift-openapi-generator/Documentation.docc/Articles/Manually-invoking-the-generator-CLI.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ To see the usage documentation for the generator CLI, use the following command:
4040
% swift run swift-openapi-generator --help
4141
```
4242

43+
> Note: By default, the code is generated with the `package` access modifier, to make the generated code visible from other packages, add `--access-modifier public` to the invocation.
44+
4345
### Use the Swift package command
4446

4547
As an alternative to invoking the CLI manually, you can also use the package command, which works similarly to the build plugin.

Sources/swift-openapi-generator/Documentation.docc/Articles/Supported-OpenAPI-features.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ For any other formats, the payload is provided as raw bytes, leaving it up to th
2222
- when content type is `application/json` or ends with `+json`
2323
- [x] URL-encoded form request bodies
2424
- when content type is `application/x-www-form-urlencoded`
25-
- [ ] multipart
26-
- tracked by [#36](https://github.com/apple/swift-openapi-generator/issues/36)
25+
- [x] multipart
26+
- for details, see [SOAR-0009](https://swiftpackageindex.com/apple/swift-openapi-generator/main/documentation/swift-openapi-generator/soar-0009)
2727
- [ ] XML
2828

2929
### OpenAPI specification features
@@ -63,21 +63,21 @@ For any other formats, the payload is provided as raw bytes, leaving it up to th
6363

6464
- [x] url
6565
- [x] description
66-
- [ ] variables
66+
- [x] variables
6767

6868
#### Server Variable Object
6969

70-
- [ ] enum
71-
- [ ] default
72-
- [ ] description
70+
- [x] enum
71+
- [x] default
72+
- [x] description
7373

7474
#### Paths Object
7575

7676
- [x] map from pattern to Path Item Object
7777

7878
#### Path Item Object
7979

80-
- [ ] $ref
80+
- [x] $ref
8181
- [x] summary
8282
- [x] description
8383
- [x] get/put/post/delete/options/head/patch/trace
@@ -110,7 +110,7 @@ For any other formats, the payload is provided as raw bytes, leaving it up to th
110110
- [x] schema
111111
- [ ] example
112112
- [ ] examples
113-
- [ ] encoding
113+
- [x] encoding (in multipart only)
114114

115115
#### Security Requirement Object
116116

@@ -198,8 +198,8 @@ For any other formats, the payload is provided as raw bytes, leaving it up to th
198198

199199
#### Encoding Object
200200

201-
- [ ] contentType
202-
- [ ] headers
201+
- [x] contentType
202+
- [x] headers
203203
- [ ] style
204204
- [ ] explode
205205
- [ ] allowReserved

Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ extension _GenerateOptions {
3030
func runGenerator(outputDirectory: URL, pluginSource: PluginSource?, isDryRun: Bool) async throws {
3131
let config = try loadedConfig()
3232
let sortedModes = try resolvedModes(config)
33+
let resolvedAccessModifier = resolvedAccessModifier(config)
3334
let resolvedAdditionalImports = resolvedAdditionalImports(config)
3435
let resolvedFeatureFlags = resolvedFeatureFlags(config)
3536
let configs: [Config] = sortedModes.map {
3637
.init(
3738
mode: $0,
39+
access: resolvedAccessModifier ?? Config.defaultAccessModifier,
3840
additionalImports: resolvedAdditionalImports,
3941
filter: config?.filter,
4042
featureFlags: resolvedFeatureFlags

Sources/swift-openapi-generator/GenerateOptions.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ struct _GenerateOptions: ParsableArguments {
2828
"The Swift files to generate. Options: \(GeneratorMode.prettyListing). Note that '\(GeneratorMode.client.rawValue)' and '\(GeneratorMode.server.rawValue)' depend on declarations in '\(GeneratorMode.types.rawValue)'."
2929
) var mode: [GeneratorMode] = []
3030

31+
@Option(
32+
help:
33+
"The access modifier to use for the API of generated code. Default: \(Config.defaultAccessModifier.rawValue)"
34+
) var accessModifier: AccessModifier?
35+
3136
@Option(help: "Additional import to add to all generated files.") var additionalImport: [String] = []
3237

3338
@Option(help: "Pre-release feature to enable. Options: \(FeatureFlag.prettyListing).") var featureFlag:
@@ -38,6 +43,8 @@ struct _GenerateOptions: ParsableArguments {
3843
) var diagnosticsOutputPath: URL?
3944
}
4045

46+
extension AccessModifier: ExpressibleByArgument {}
47+
4148
extension _GenerateOptions {
4249

4350
/// The user-provided user config, not yet resolved with defaults.
@@ -58,6 +65,15 @@ extension _GenerateOptions {
5865
return Set(config.generate).sorted()
5966
}
6067

68+
/// Returns the access modifier requested by the user.
69+
/// - Parameter config: The configuration specified by the user.
70+
/// - Returns: The access modifier requested by the user, or nil if the default should be used.
71+
func resolvedAccessModifier(_ config: _UserConfig?) -> AccessModifier? {
72+
if let accessModifier { return accessModifier }
73+
if let accessModifier = config?.accessModifier { return accessModifier }
74+
return nil
75+
}
76+
6177
/// Returns a list of additional imports requested by the user.
6278
/// - Parameter config: The configuration specified by the user.
6379
/// - Returns: A list of additional import statements requested by the user.

Sources/swift-openapi-generator/UserConfig.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ struct _UserConfig: Codable {
2323
/// A list of modes to use, in other words, which Swift files to generate.
2424
var generate: [GeneratorMode]
2525

26+
/// The access modifier used for the API of generated code.
27+
var accessModifier: AccessModifier?
28+
2629
/// A list of names of additional imports that are added to every
2730
/// generated Swift file.
2831
var additionalImports: [String]?
@@ -38,6 +41,7 @@ struct _UserConfig: Codable {
3841

3942
enum CodingKeys: String, CaseIterable, CodingKey {
4043
case generate
44+
case accessModifier
4145
case additionalImports
4246
case filter
4347
case featureFlags

Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ final class Test_YamsParser: Test_Core {
163163
try YamsParser()
164164
.parseOpenAPI(
165165
.init(absolutePath: URL(fileURLWithPath: "/foo.yaml"), contents: Data(yaml.utf8)),
166-
config: .init(mode: .types),
166+
config: .init(mode: .types, access: Config.defaultAccessModifier),
167167
diagnostics: PrintingDiagnosticCollector()
168168
)
169169
}

Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ final class Test_validateDoc: Test_Core {
3131
paths: [:],
3232
components: .init(schemas: ["myImperfectSchema": schemaWithWarnings])
3333
)
34-
let diagnostics = try validateDoc(doc, config: .init(mode: .types))
34+
let diagnostics = try validateDoc(doc, config: .init(mode: .types, access: Config.defaultAccessModifier))
3535
XCTAssertEqual(diagnostics.count, 1)
3636
}
3737

@@ -53,7 +53,7 @@ final class Test_validateDoc: Test_Core {
5353
],
5454
components: .noComponents
5555
)
56-
XCTAssertThrowsError(try validateDoc(doc, config: .init(mode: .types)))
56+
XCTAssertThrowsError(try validateDoc(doc, config: .init(mode: .types, access: Config.defaultAccessModifier)))
5757
}
5858

5959
}

Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ class Test_Core: XCTestCase {
4545
)
4646
}
4747

48-
func makeConfig(featureFlags: FeatureFlags = []) -> Config { .init(mode: .types, featureFlags: featureFlags) }
48+
func makeConfig(featureFlags: FeatureFlags = []) -> Config {
49+
.init(mode: .types, access: Config.defaultAccessModifier, featureFlags: featureFlags)
50+
}
4951

5052
func loadSchemaFromYAML(_ yamlString: String) throws -> JSONSchema {
5153
try YAMLDecoder().decode(JSONSchema.self, from: yamlString)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import XCTest
15+
import OpenAPIKit
16+
@testable import _OpenAPIGeneratorCore
17+
18+
final class Test_Config: Test_Core {
19+
func testDefaultAccessModifier() { XCTAssertEqual(Config.defaultAccessModifier, .package) }
20+
}

Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_isSchemaSupported.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Test_isSchemaSupported: XCTestCase {
1919

2020
var translator: any FileTranslator {
2121
TypesFileTranslator(
22-
config: .init(mode: .types),
22+
config: .init(mode: .types, access: Config.defaultAccessModifier),
2323
diagnostics: PrintingDiagnosticCollector(),
2424
components: .init(schemas: [
2525
"Foo": .string, "MyObj": .object, "MyObj2": .object,

Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ fileprivate extension CompatibilityTest {
229229
for mode in GeneratorMode.allCases {
230230
group.addTask {
231231
let generator = makeGeneratorPipeline(
232-
config: Config(mode: mode),
232+
config: Config(mode: mode, access: .public),
233233
diagnostics: diagnosticsCollector
234234
)
235235
return try assertNoThrowWithValue(generator.run(input))
@@ -239,7 +239,10 @@ fileprivate extension CompatibilityTest {
239239
}
240240
} else {
241241
outputs = try GeneratorMode.allCases.map { mode in
242-
let generator = makeGeneratorPipeline(config: Config(mode: mode), diagnostics: diagnosticsCollector)
242+
let generator = makeGeneratorPipeline(
243+
config: Config(mode: mode, access: .public),
244+
diagnostics: diagnosticsCollector
245+
)
243246
return try assertNoThrowWithValue(generator.run(input))
244247
}
245248
}

Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ struct TestConfig: Encodable {
2626

2727
extension TestConfig {
2828
var asConfig: Config {
29-
.init(mode: mode, additionalImports: additionalImports ?? [], featureFlags: featureFlags ?? [])
29+
.init(mode: mode, access: .public, additionalImports: additionalImports ?? [], featureFlags: featureFlags ?? [])
3030
}
3131
}
3232

0 commit comments

Comments
 (0)