Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional api protocol conformances #635

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/_OpenAPIGeneratorCore/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public struct Config: Sendable {
/// Additional imports to add to each generated file.
public var additionalImports: [String]

/// Additional protocol conformances to add to the APIProtocol type
public var additionalAPIProtocols: [String]

/// Filter to apply to the OpenAPI document before generation.
public var filter: DocumentFilter?

Expand All @@ -49,12 +52,14 @@ public struct Config: Sendable {
mode: GeneratorMode,
access: AccessModifier,
additionalImports: [String] = [],
additionalAPIProtocols: [String] = [],
filter: DocumentFilter? = nil,
featureFlags: FeatureFlags = []
) {
self.mode = mode
self.access = access
self.additionalImports = additionalImports
self.additionalAPIProtocols = additionalAPIProtocols
self.filter = filter
self.featureFlags = featureFlags
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension TypesFileTranslator {
let protocolDescription = ProtocolDescription(
accessModifier: config.access,
name: Constants.APIProtocol.typeName,
conformances: Constants.APIProtocol.conformances,
conformances: Constants.APIProtocol.conformances + config.additionalAPIProtocols,
members: functionDecls
)
let protocolComment: Comment = .doc("A type that performs HTTP operations defined by the OpenAPI document.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The configuration file has the following keys:
- `package`: Generated API is accessible from other modules within the same package or project.
- `internal` (default): Generated API is accessible from the containing module only.
- `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.
- `additionalAPIProtocols` (optional): array of strings. Each string value is the name of a protocol the resulting API should conform to. These protocols must be available in the scope that the API is generated.
- `filter`: (optional): Filters to apply to the OpenAPI document before generation.
- `operations`: Operations with these operation IDs will be included in the filter.
- `tags`: Operations tagged with these tags will be included in the filter.
Expand Down Expand Up @@ -86,6 +87,17 @@ additionalImports:
accessModifier: public
```

To use together with a mocking library, it is possible to add conformance to a custom protocol:

```yaml
generate:
- client
additionalImports:
- APITypes
additionalAPIProtocols:
- AutoMockable
```

### Document filtering

The generator supports filtering the OpenAPI document prior to generation, which can be useful when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ extension _GenerateOptions {
let sortedModes = try resolvedModes(config)
let resolvedAccessModifier = resolvedAccessModifier(config) ?? Config.defaultAccessModifier
let resolvedAdditionalImports = resolvedAdditionalImports(config)
let resolvedAdditionalAPIProtocols = resolvedAdditionalAPIProtocols(config)
let resolvedFeatureFlags = resolvedFeatureFlags(config)
let configs: [Config] = sortedModes.map {
.init(
mode: $0,
access: resolvedAccessModifier,
additionalImports: resolvedAdditionalImports,
additionalAPIProtocols: resolvedAdditionalAPIProtocols,
filter: config?.filter,
featureFlags: resolvedFeatureFlags
)
Expand Down
10 changes: 10 additions & 0 deletions Sources/swift-openapi-generator/GenerateOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ struct _GenerateOptions: ParsableArguments {

@Option(help: "Additional import to add to all generated files.") var additionalImport: [String] = []

@Option(help: "Additional protocol conformances to add to the APIProtocol type") var additionalAPIProtocols: [String] = []

@Option(help: "Pre-release feature to enable. Options: \(FeatureFlag.prettyListing).") var featureFlag:
[FeatureFlag] = []

Expand Down Expand Up @@ -83,6 +85,14 @@ extension _GenerateOptions {
return []
}

func resolvedAdditionalAPIProtocols(_ config: _UserConfig?) -> [String] {
if !additionalAPIProtocols.isEmpty { return additionalAPIProtocols }
if let additionalAPIProcotols = config?.additionalAPIProtocols, !additionalAPIProcotols.isEmpty {
return additionalAPIProcotols
}
return []
}

/// Returns a list of the feature flags requested by the user.
/// - Parameter config: The configuration specified by the user.
/// - Returns: A set of feature flags requested by the user.
Expand Down
4 changes: 4 additions & 0 deletions Sources/swift-openapi-generator/UserConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ struct _UserConfig: Codable {
/// generated Swift file.
var additionalImports: [String]?

/// A list of additional protocol conformances to add to the APIProtocol
/// that is generated.
var additionalAPIProtocols: [String]?

/// Filter to apply to the OpenAPI document before generation.
var filter: DocumentFilter?

Expand Down
5 changes: 4 additions & 1 deletion Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,12 @@ final class Test_YamsParser: Test_Core {

additionalImports:
- Foundation

additionalAPIProtocols:
- ExampleProtocol
"""
let keys = try? YamsParser.extractTopLevelKeys(fromYAMLString: yaml)
XCTAssertEqual(keys, ["generate", "featureFlags", "additionalImports"])
XCTAssertEqual(keys, ["generate", "featureFlags", "additionalImports", "additionalAPIProtocols"])
}

func testExtractTopLevelKeysWithInvalidYAML() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ struct TestConfig: Encodable {
var docFilePath: String
var mode: GeneratorMode
var additionalImports: [String]?
var additionalAPIProtocols: [String]?
var featureFlags: FeatureFlags?
var referenceOutputDirectory: String
}

extension TestConfig {
var asConfig: Config {
.init(mode: mode, access: .public, additionalImports: additionalImports ?? [], featureFlags: featureFlags ?? [])
.init(
mode: mode,
access: .public,
additionalImports: additionalImports ?? [],
additionalAPIProtocols: additionalAPIProtocols ?? [],
featureFlags: featureFlags ?? []
)
}
}

Expand Down Expand Up @@ -126,6 +133,7 @@ final class FileBasedReferenceTests: XCTestCase {
docFilePath: "Docs/\(project.openAPIDocFileName)",
mode: mode,
additionalImports: [],
additionalAPIProtocols: [],
featureFlags: featureFlags,
referenceOutputDirectory: "ReferenceSources/\(project.fixtureCodeDirectoryName)"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2330,6 +2330,13 @@ final class SnippetBasedReferenceTests: XCTestCase {
)
}

func testAPIProtocolConformance() throws {
try self.assertProtocolConformanceTranslation(
additionalProtocolConformances: ["ExampleProtocol"],
"public protocol APIProtocol: Sendable, ExampleProtocol {}"
)
}

func testServerRegisterHandlers_oneOperation() throws {
try self.assertServerRegisterHandlers(
"""
Expand Down Expand Up @@ -5195,13 +5202,19 @@ extension SnippetBasedReferenceTests {

func makeTypesTranslator(
accessModifier: AccessModifier = .public,
additionalProtocolConformances: [String] = [],
featureFlags: FeatureFlags = [],
ignoredDiagnosticMessages: Set<String> = [],
componentsYAML: String
) throws -> TypesFileTranslator {
let components = try YAMLDecoder().decode(OpenAPI.Components.self, from: componentsYAML)
return TypesFileTranslator(
config: Config(mode: .types, access: accessModifier, featureFlags: featureFlags),
config: Config(
mode: .types,
access: accessModifier,
additionalAPIProtocols: additionalProtocolConformances,
featureFlags: featureFlags
),
diagnostics: XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages),
components: components
)
Expand Down Expand Up @@ -5444,6 +5457,21 @@ extension SnippetBasedReferenceTests {
try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line)
}

func assertProtocolConformanceTranslation(
additionalProtocolConformances: [String],
_ expectedSwift: String,
file: StaticString = #filePath,
line: UInt = #line
) throws {
let translator = try makeTypesTranslator(
additionalProtocolConformances: additionalProtocolConformances,
componentsYAML: "{}"
)
let paths: OpenAPI.PathItem.Map = .init()
let translation = try translator.translateAPIProtocol(paths)
try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line)
}

func assertPathsTranslationExtension(
_ pathsYAML: String,
componentsYAML: String = "{}",
Expand Down