Skip to content

Commit e4f69cf

Browse files
authored
Simplify code gen lib interface (grpc#2169)
Motivation: When reviewing APIs I noticed a few inconsistencies and some abstractions which are more complicated than they need to be. We should simplify this before we commit to a stable API. Modifications: - Replace the 'Name' type with a more concrete 'ServiceName' and 'MethodName'. They carry approximately equivalent information but the information is expressed in terms of what the names are used for (i.e. a function vs. what the expected format of the name is, e.g. lowercase). This makes it easier for users to understand how the names will be used and leaves room for more customisation in the future. - The service descriptor used two names: a namespace name and a service name. These have now been compressed into a single object (not all namespace name values were used). The namespace name was never used in isolation so this was adding unnecessary complexity. - The top-level 'SourceGenerator' type has been renamed 'CodeGenerator'. This is consistent with being a 'CodeGen' module and having a 'CodeGenerationRequest'. - The closures for returning code snippets to create a serializer/deserializer were renamed to make their user clearer. - Accidental public API was removed. Result: Slightly smaller and more consistent API.
1 parent 9639fe9 commit e4f69cf

19 files changed

+365
-244
lines changed

Sources/GRPCCodeGen/CodeGenerationRequest.swift

+171-39
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ public struct CodeGenerationRequest {
4444
///
4545
/// For example, to serialize Protobuf messages you could specify a serializer as:
4646
/// ```swift
47-
/// request.lookupSerializer = { messageType in
47+
/// request.makeSerializerCodeSnippet = { messageType in
4848
/// "ProtobufSerializer<\(messageType)>()"
4949
/// }
5050
/// ```
51-
public var lookupSerializer: (_ messageType: String) -> String
51+
public var makeSerializerCodeSnippet: (_ messageType: String) -> String
5252

5353
/// Closure that receives a message type as a `String` and returns a code snippet to
5454
/// initialize a `MessageDeserializer` for that type as a `String`.
@@ -58,26 +58,64 @@ public struct CodeGenerationRequest {
5858
///
5959
/// For example, to serialize Protobuf messages you could specify a serializer as:
6060
/// ```swift
61-
/// request.lookupDeserializer = { messageType in
61+
/// request.makeDeserializerCodeSnippet = { messageType in
6262
/// "ProtobufDeserializer<\(messageType)>()"
6363
/// }
6464
/// ```
65-
public var lookupDeserializer: (_ messageType: String) -> String
65+
public var makeDeserializerCodeSnippet: (_ messageType: String) -> String
6666

6767
public init(
6868
fileName: String,
6969
leadingTrivia: String,
7070
dependencies: [Dependency],
7171
services: [ServiceDescriptor],
72-
lookupSerializer: @escaping (String) -> String,
73-
lookupDeserializer: @escaping (String) -> String
72+
makeSerializerCodeSnippet: @escaping (_ messageType: String) -> String,
73+
makeDeserializerCodeSnippet: @escaping (_ messageType: String) -> String
7474
) {
7575
self.fileName = fileName
7676
self.leadingTrivia = leadingTrivia
7777
self.dependencies = dependencies
7878
self.services = services
79-
self.lookupSerializer = lookupSerializer
80-
self.lookupDeserializer = lookupDeserializer
79+
self.makeSerializerCodeSnippet = makeSerializerCodeSnippet
80+
self.makeDeserializerCodeSnippet = makeDeserializerCodeSnippet
81+
}
82+
}
83+
84+
extension CodeGenerationRequest {
85+
@available(*, deprecated, renamed: "makeSerializerSnippet")
86+
public var lookupSerializer: (_ messageType: String) -> String {
87+
get { self.makeSerializerCodeSnippet }
88+
set { self.makeSerializerCodeSnippet = newValue }
89+
}
90+
91+
@available(*, deprecated, renamed: "makeDeserializerSnippet")
92+
public var lookupDeserializer: (_ messageType: String) -> String {
93+
get { self.makeDeserializerCodeSnippet }
94+
set { self.makeDeserializerCodeSnippet = newValue }
95+
}
96+
97+
@available(
98+
*,
99+
deprecated,
100+
renamed:
101+
"init(fileName:leadingTrivia:dependencies:services:lookupSerializer:lookupDeserializer:)"
102+
)
103+
public init(
104+
fileName: String,
105+
leadingTrivia: String,
106+
dependencies: [Dependency],
107+
services: [ServiceDescriptor],
108+
lookupSerializer: @escaping (String) -> String,
109+
lookupDeserializer: @escaping (String) -> String
110+
) {
111+
self.init(
112+
fileName: fileName,
113+
leadingTrivia: leadingTrivia,
114+
dependencies: dependencies,
115+
services: services,
116+
makeSerializerCodeSnippet: lookupSerializer,
117+
makeDeserializerCodeSnippet: lookupDeserializer
118+
)
81119
}
82120
}
83121

@@ -88,7 +126,7 @@ public struct Dependency: Equatable {
88126
public var item: Item?
89127

90128
/// The access level to be included in imports of this dependency.
91-
public var accessLevel: SourceGenerator.Config.AccessLevel
129+
public var accessLevel: CodeGenerator.Config.AccessLevel
92130

93131
/// The name of the imported module or of the module an item is imported from.
94132
public var module: String
@@ -107,7 +145,7 @@ public struct Dependency: Equatable {
107145
module: String,
108146
spi: String? = nil,
109147
preconcurrency: PreconcurrencyRequirement = .notRequired,
110-
accessLevel: SourceGenerator.Config.AccessLevel
148+
accessLevel: CodeGenerator.Config.AccessLevel
111149
) {
112150
self.item = item
113151
self.module = module
@@ -228,33 +266,53 @@ public struct ServiceDescriptor: Hashable {
228266
/// It is already formatted, meaning it contains "///" and new lines.
229267
public var documentation: String
230268

231-
/// The service name in different formats.
232-
///
233-
/// All properties of this object must be unique for each service from within a namespace.
234-
public var name: Name
235-
236-
/// The service namespace in different formats.
237-
///
238-
/// All different services from within the same namespace must have
239-
/// the same ``Name`` object as this property.
240-
/// For `.proto` files the base name of this object is the package name.
241-
public var namespace: Name
269+
/// The name of the service.
270+
public var name: ServiceName
242271

243272
/// A description of each method of a service.
244273
///
245274
/// - SeeAlso: ``MethodDescriptor``.
246275
public var methods: [MethodDescriptor]
247276

277+
public init(
278+
documentation: String,
279+
name: ServiceName,
280+
methods: [MethodDescriptor]
281+
) {
282+
self.documentation = documentation
283+
self.name = name
284+
self.methods = methods
285+
}
286+
}
287+
288+
extension ServiceDescriptor {
289+
@available(*, deprecated, renamed: "init(documentation:name:methods:)")
248290
public init(
249291
documentation: String,
250292
name: Name,
251293
namespace: Name,
252294
methods: [MethodDescriptor]
253295
) {
254296
self.documentation = documentation
255-
self.name = name
256-
self.namespace = namespace
257297
self.methods = methods
298+
299+
let identifier = namespace.base.isEmpty ? name.base : namespace.base + "." + name.base
300+
301+
let typeName =
302+
namespace.generatedUpperCase.isEmpty
303+
? name.generatedUpperCase
304+
: namespace.generatedUpperCase + "_" + name.generatedUpperCase
305+
306+
let propertyName =
307+
namespace.generatedLowerCase.isEmpty
308+
? name.generatedUpperCase
309+
: namespace.generatedLowerCase + "_" + name.generatedUpperCase
310+
311+
self.name = ServiceName(
312+
identifyingName: identifier,
313+
typeName: typeName,
314+
propertyName: propertyName
315+
)
258316
}
259317
}
260318

@@ -268,7 +326,7 @@ public struct MethodDescriptor: Hashable {
268326
///
269327
/// All properties of this object must be unique for each method
270328
/// from within a service.
271-
public var name: Name
329+
public var name: MethodName
272330

273331
/// Identifies if the method is input streaming.
274332
public var isInputStreaming: Bool
@@ -284,7 +342,7 @@ public struct MethodDescriptor: Hashable {
284342

285343
public init(
286344
documentation: String,
287-
name: Name,
345+
name: MethodName,
288346
isInputStreaming: Bool,
289347
isOutputStreaming: Bool,
290348
inputType: String,
@@ -299,7 +357,94 @@ public struct MethodDescriptor: Hashable {
299357
}
300358
}
301359

360+
extension MethodDescriptor {
361+
@available(*, deprecated, message: "Use MethodName instead of Name")
362+
public init(
363+
documentation: String,
364+
name: Name,
365+
isInputStreaming: Bool,
366+
isOutputStreaming: Bool,
367+
inputType: String,
368+
outputType: String
369+
) {
370+
self.documentation = documentation
371+
self.name = MethodName(
372+
identifyingName: name.base,
373+
typeName: name.generatedUpperCase,
374+
functionName: name.generatedLowerCase
375+
)
376+
self.isInputStreaming = isInputStreaming
377+
self.isOutputStreaming = isOutputStreaming
378+
self.inputType = inputType
379+
self.outputType = outputType
380+
}
381+
}
382+
383+
public struct ServiceName: Hashable {
384+
/// The identifying name as used in the service/method descriptors including any namespace.
385+
///
386+
/// This value is also used to identify the service to the remote peer, usually as part of the
387+
/// ":path" pseudoheader if doing gRPC over HTTP/2.
388+
///
389+
/// If the service is declared in package "foo.bar" and the service is called "Baz" then this
390+
/// value should be "foo.bar.Baz".
391+
public var identifyingName: String
392+
393+
/// The name as used on types including any namespace.
394+
///
395+
/// This is used to generate a namespace for each service which contains a number of client and
396+
/// server protocols and concrete types.
397+
///
398+
/// If the service is declared in package "foo.bar" and the service is called "Baz" then this
399+
/// value should be "Foo\_Bar\_Baz".
400+
public var typeName: String
401+
402+
/// The name as used as a property.
403+
///
404+
/// This is used to provide a convenience getter for a descriptor of the service.
405+
///
406+
/// If the service is declared in package "foo.bar" and the service is called "Baz" then this
407+
/// value should be "foo\_bar\_Baz".
408+
public var propertyName: String
409+
410+
public init(identifyingName: String, typeName: String, propertyName: String) {
411+
self.identifyingName = identifyingName
412+
self.typeName = typeName
413+
self.propertyName = propertyName
414+
}
415+
}
416+
417+
public struct MethodName: Hashable {
418+
/// The identifying name as used in the service/method descriptors.
419+
///
420+
/// This value is also used to identify the method to the remote peer, usually as part of the
421+
/// ":path" pseudoheader if doing gRPC over HTTP/2.
422+
///
423+
/// This value typically starts with an uppercase character, for example "Get".
424+
public var identifyingName: String
425+
426+
/// The name as used on types including any namespace.
427+
///
428+
/// This is used to generate a namespace for each method which contains information about
429+
/// the method.
430+
///
431+
/// This value typically starts with an uppercase character, for example "Get".
432+
public var typeName: String
433+
434+
/// The name as used as a property.
435+
///
436+
/// This value typically starts with an lowercase character, for example "get".
437+
public var functionName: String
438+
439+
public init(identifyingName: String, typeName: String, functionName: String) {
440+
self.identifyingName = identifyingName
441+
self.typeName = typeName
442+
self.functionName = functionName
443+
}
444+
}
445+
302446
/// Represents the name associated with a namespace, service or a method, in three different formats.
447+
@available(*, deprecated, message: "Use ServiceName/MethodName instead.")
303448
public struct Name: Hashable {
304449
/// The base name is the name used for the namespace/service/method in the IDL file, so it should follow
305450
/// the specific casing of the IDL.
@@ -327,6 +472,7 @@ public struct Name: Hashable {
327472
}
328473
}
329474

475+
@available(*, deprecated, message: "Use ServiceName/MethodName instead.")
330476
extension Name {
331477
/// The base name replacing occurrences of "." with "_".
332478
///
@@ -335,17 +481,3 @@ extension Name {
335481
return self.base.replacing(".", with: "_")
336482
}
337483
}
338-
339-
extension ServiceDescriptor {
340-
var namespacedServicePropertyName: String {
341-
let prefix: String
342-
343-
if self.namespace.normalizedBase.isEmpty {
344-
prefix = ""
345-
} else {
346-
prefix = self.namespace.normalizedBase + "_"
347-
}
348-
349-
return prefix + self.name.normalizedBase
350-
}
351-
}

Sources/GRPCCodeGen/SourceGenerator.swift renamed to Sources/GRPCCodeGen/CodeGenerator.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
/// Creates a ``SourceFile`` containing the generated code for the RPCs represented in a ``CodeGenerationRequest`` object.
18-
public struct SourceGenerator: Sendable {
17+
@available(*, deprecated, renamed: "CodeGenerator")
18+
public typealias SourceGenerator = CodeGenerator
19+
20+
/// Generates ``SourceFile`` objects containing generated code for the RPCs represented
21+
/// in a ``CodeGenerationRequest`` object.
22+
public struct CodeGenerator: Sendable {
1923
/// The options regarding the access level, indentation for the generated code
2024
/// and whether to generate server and client code.
2125
public var config: Config
@@ -79,8 +83,8 @@ public struct SourceGenerator: Sendable {
7983
}
8084
}
8185

82-
/// The function that transforms a ``CodeGenerationRequest`` object into a ``SourceFile`` object containing
83-
/// the generated code, in accordance to the configurations set by the user for the ``SourceGenerator``.
86+
/// Transforms a ``CodeGenerationRequest`` object into a ``SourceFile`` object containing
87+
/// the generated code.
8488
public func generate(
8589
_ request: CodeGenerationRequest
8690
) throws -> SourceFile {

Sources/GRPCCodeGen/Internal/StructuredSwift+Client.swift

+7-7
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ extension ProtocolDescription {
213213
.preFormatted(docs(for: method)),
214214
.function(
215215
signature: .clientMethod(
216-
name: method.name.generatedLowerCase,
216+
name: method.name.functionName,
217217
input: method.inputType,
218218
output: method.outputType,
219219
streamingInput: method.isInputStreaming,
@@ -256,7 +256,7 @@ extension ExtensionDescription {
256256
.function(
257257
.clientMethodWithDefaults(
258258
accessLevel: accessLevel,
259-
name: method.name.generatedLowerCase,
259+
name: method.name.functionName,
260260
input: method.inputType,
261261
output: method.outputType,
262262
streamingInput: method.isInputStreaming,
@@ -506,7 +506,7 @@ extension ExtensionDescription {
506506
.function(
507507
.clientMethodExploded(
508508
accessLevel: accessLevel,
509-
name: method.name.generatedLowerCase,
509+
name: method.name.functionName,
510510
input: method.inputType,
511511
output: method.outputType,
512512
streamingInput: method.isInputStreaming,
@@ -716,11 +716,11 @@ extension StructDescription {
716716
.function(
717717
.clientMethod(
718718
accessLevel: accessLevel,
719-
name: method.name.generatedLowerCase,
719+
name: method.name.functionName,
720720
input: method.inputType,
721721
output: method.outputType,
722722
serviceEnum: serviceEnum,
723-
methodEnum: method.name.generatedUpperCase,
723+
methodEnum: method.name.typeName,
724724
streamingInput: method.isInputStreaming,
725725
streamingOutput: method.isOutputStreaming
726726
)
@@ -735,7 +735,7 @@ private func docs(
735735
for method: MethodDescriptor,
736736
serializers includeSerializers: Bool = true
737737
) -> String {
738-
let summary = "/// Call the \"\(method.name.base)\" method."
738+
let summary = "/// Call the \"\(method.name.identifyingName)\" method."
739739

740740
let request: String
741741
if method.isInputStreaming {
@@ -773,7 +773,7 @@ private func docs(
773773
}
774774

775775
private func explodedDocs(for method: MethodDescriptor) -> String {
776-
let summary = "/// Call the \"\(method.name.base)\" method."
776+
let summary = "/// Call the \"\(method.name.identifyingName)\" method."
777777
var parameters = """
778778
/// - Parameters:
779779
"""

0 commit comments

Comments
 (0)