-
Notifications
You must be signed in to change notification settings - Fork 161
Expand file tree
/
Copy pathGeneratorPipeline.swift
More file actions
153 lines (140 loc) · 6.82 KB
/
GeneratorPipeline.swift
File metadata and controls
153 lines (140 loc) · 6.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import OpenAPIKit
import Foundation
import Yams
/// A sequence of steps that combined represents the full end-to-end
/// functionality of the generator.
///
/// The input is an in-memory OpenAPI document, and the output is an
/// in-memory generated Swift file. Which file is generated (types, client,
/// or server) is controlled by the generator configuration, in the translation
/// stage.
///
/// The pipeline goes through several stages, similar to a compiler:
/// 1. Parsing: RawInput -> ParsedInput, which converts a raw OpenAPI file
/// into type-safe OpenAPIKit types.
/// 2. Translation: ParsedInput -> TranslatedOutput, which converts an OpenAPI
/// document into a structure Swift representation of the requested generated
/// file, for one of: types, client, or server. This stage contains most of the
/// OpenAPI to Swift generation code.
/// 3. Rendering: TranslatedOutput -> RenderedOutput, which converts a
/// structured Swift representation into a raw Swift file.
struct GeneratorPipeline {
// Note: Until we have variadic generics we need to have a fixed number
// of type parameters, but this is fine because have all the concrete
// types for the stage boundaries already defined.
/// A raw input into the parsing stage, usually read from disk.
typealias RawInput = InMemoryInputFile
/// An output of the parsing stage and an input into the translation stage.
typealias ParsedInput = ParsedOpenAPIRepresentation
/// An output of the translation stage and an input of the rendering stage.
typealias TranslatedOutput = StructuredSwiftRepresentation
/// An output of the rendering phase, usually written to disk.
typealias RenderedOutput = RenderedSwiftRepresentation
/// The parsing stage.
var parseOpenAPIFileStage: GeneratorPipelineStage<RawInput, ParsedInput>
/// The translation phase.
var translateOpenAPIToStructuredSwiftStage: GeneratorPipelineStage<ParsedInput, TranslatedOutput>
/// The rendering phase.
var renderSwiftFilesStage: GeneratorPipelineStage<TranslatedOutput, RenderedOutput>
/// Runs the full pipeline.
///
/// Throws an error when encountering a non-recoverable issue.
///
/// When possible, use ``DiagnosticCollector`` instead to emit
/// recoverable diagnostics, such as unsupported features.
/// - Parameter input: The input of the parsing stage.
/// - Returns: The output of the rendering stage.
/// - Throws: An error if a non-recoverable issue occurs during pipeline execution.
func run(_ input: RawInput) throws -> RenderedOutput {
try renderSwiftFilesStage.run(translateOpenAPIToStructuredSwiftStage.run(parseOpenAPIFileStage.run(input)))
}
}
/// Runs the generator logic with the specified inputs.
/// - Parameters:
/// - input: The raw file contents of the OpenAPI document.
/// - config: A set of configuration values for the generator.
/// - diagnostics: A collector to which the generator emits diagnostics.
/// - Throws: When encountering a non-recoverable error. For recoverable
/// issues, emits issues into the diagnostics collector.
/// - Returns: The raw contents of the generated Swift file.
public func runGenerator(input: InMemoryInputFile, config: Config, diagnostics: any DiagnosticCollector) throws
-> InMemoryOutputFile
{ try makeGeneratorPipeline(config: config, diagnostics: diagnostics).run(input) }
/// Runs the generator and returns multiple output files when sharding is configured.
/// Falls back to a single-element array when sharding is not configured.
public func runShardedGenerator(
input: InMemoryInputFile,
config: Config,
diagnostics: any DiagnosticCollector
) throws -> [InMemoryOutputFile] {
let pipeline = makeGeneratorPipeline(config: config, diagnostics: diagnostics)
let parsed = try pipeline.parseOpenAPIFileStage.run(input)
let translated = try pipeline.translateOpenAPIToStructuredSwiftStage.run(parsed)
return translated.files.map { namedFile in
let renderer = TextBasedRenderer.default
renderer.renderFile(namedFile.contents)
let string = renderer.renderedContents()
return InMemoryOutputFile(baseName: namedFile.name, contents: Data(string.utf8))
}
}
/// Creates a new pipeline instance.
/// - Parameters:
/// - parser: An OpenAPI document parser.
/// - validator: A validator for parsed OpenAPI documents.
/// - translator: A translator from OpenAPI to Swift.
/// - renderer: A Swift code renderer.
/// - config: A set of configuration values for the generator.
/// - diagnostics: A collector to which the generator emits diagnostics.
/// - Returns: A configured generator pipeline that can be executed with
/// ``GeneratorPipeline/run(_:)``.
func makeGeneratorPipeline(
parser: any ParserProtocol = YamsParser(),
validator: @escaping (ParsedOpenAPIRepresentation, Config) throws -> [Diagnostic] = validateDoc,
translator: any TranslatorProtocol = MultiplexTranslator(),
renderer: any RendererProtocol = TextBasedRenderer.default,
config: Config,
diagnostics: any DiagnosticCollector
) -> GeneratorPipeline {
let filterDoc = { (doc: OpenAPI.Document) -> OpenAPI.Document in
guard let documentFilter = config.filter else { return doc }
let filteredDoc: OpenAPI.Document = try documentFilter.filter(doc)
return filteredDoc
}
let validateDoc = { (doc: OpenAPI.Document) -> OpenAPI.Document in
let validationDiagnostics = try validator(doc, config)
for diagnostic in validationDiagnostics { try diagnostics.emit(diagnostic) }
return doc
}
return .init(
parseOpenAPIFileStage: .init(
preTransitionHooks: [],
transition: { input in try parser.parseOpenAPI(input, config: config, diagnostics: diagnostics) },
postTransitionHooks: [filterDoc, validateDoc]
),
translateOpenAPIToStructuredSwiftStage: .init(
preTransitionHooks: [],
transition: { input in
try translator.translate(parsedOpenAPI: input, config: config, diagnostics: diagnostics)
},
postTransitionHooks: []
),
renderSwiftFilesStage: .init(
preTransitionHooks: [],
transition: { input in try renderer.render(structured: input, config: config, diagnostics: diagnostics) },
postTransitionHooks: []
)
)
}