Skip to content

Commit 311486e

Browse files
authored
Add documentation to generated code (#2133)
Motivation: The generated code doesn't consistently include documentation from the source IDL. It also doesn't explain the difference between each of the generated protocols and when to use them. Modifications: Add documentation to the generated code, and where possible, also include any documentation from the source IDL. Result: Better documented generated code
1 parent 12a7366 commit 311486e

14 files changed

+809
-85
lines changed

Sources/GRPCCodeGen/Internal/StructuredSwift+Client.swift

Lines changed: 120 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ extension ProtocolDescription {
210210
conformances: ["Sendable"],
211211
members: methods.map { method in
212212
.commentable(
213-
.preFormatted(method.documentation),
213+
.preFormatted(docs(for: method)),
214214
.function(
215215
signature: .clientMethod(
216216
name: method.name.generatedLowerCase,
@@ -251,16 +251,19 @@ extension ExtensionDescription {
251251
ExtensionDescription(
252252
onType: name,
253253
declarations: methods.map { method in
254-
.function(
255-
.clientMethodWithDefaults(
256-
accessLevel: accessLevel,
257-
name: method.name.generatedLowerCase,
258-
input: method.inputType,
259-
output: method.outputType,
260-
streamingInput: method.isInputStreaming,
261-
streamingOutput: method.isOutputStreaming,
262-
serializer: .identifierPattern(serializer(method.inputType)),
263-
deserializer: .identifierPattern(deserializer(method.outputType))
254+
.commentable(
255+
.preFormatted(docs(for: method, serializers: false)),
256+
.function(
257+
.clientMethodWithDefaults(
258+
accessLevel: accessLevel,
259+
name: method.name.generatedLowerCase,
260+
input: method.inputType,
261+
output: method.outputType,
262+
streamingInput: method.isInputStreaming,
263+
streamingOutput: method.isOutputStreaming,
264+
serializer: .identifierPattern(serializer(method.inputType)),
265+
deserializer: .identifierPattern(deserializer(method.outputType))
266+
)
264267
)
265268
)
266269
}
@@ -495,11 +498,11 @@ extension ExtensionDescription {
495498
on extensionName: String,
496499
methods: [MethodDescriptor]
497500
) -> ExtensionDescription {
498-
ExtensionDescription(
501+
return ExtensionDescription(
499502
onType: extensionName,
500503
declarations: methods.map { method in
501504
.commentable(
502-
.preFormatted(method.documentation),
505+
.preFormatted(explodedDocs(for: method)),
503506
.function(
504507
.clientMethodExploded(
505508
accessLevel: accessLevel,
@@ -665,26 +668,36 @@ extension StructDescription {
665668
conformances: [clientProtocol],
666669
members: [
667670
.variable(accessModifier: .private, kind: .let, left: "client", type: .grpcClient),
668-
.function(
669-
accessModifier: accessLevel,
670-
kind: .initializer,
671-
parameters: [
672-
ParameterDescription(label: "wrapping", name: "client", type: .grpcClient)
673-
],
674-
whereClause: nil,
675-
body: [
676-
.expression(
677-
.assignment(
678-
left: .identifierPattern("self").dot("client"),
679-
right: .identifierPattern("client")
671+
.commentable(
672+
.preFormatted(
673+
"""
674+
/// Creates a new client wrapping the provided `GRPCCore.GRPCClient`.
675+
///
676+
/// - Parameters:
677+
/// - client: A `GRPCCore.GRPCClient` providing a communication channel to the service.
678+
"""
679+
),
680+
.function(
681+
accessModifier: accessLevel,
682+
kind: .initializer,
683+
parameters: [
684+
ParameterDescription(label: "wrapping", name: "client", type: .grpcClient)
685+
],
686+
whereClause: nil,
687+
body: [
688+
.expression(
689+
.assignment(
690+
left: .identifierPattern("self").dot("client"),
691+
right: .identifierPattern("client")
692+
)
680693
)
681-
)
682-
]
694+
]
695+
)
683696
),
684697
]
685698
+ methods.map { method in
686699
.commentable(
687-
.preFormatted(method.documentation),
700+
.preFormatted(docs(for: method)),
688701
.function(
689702
.clientMethod(
690703
accessLevel: accessLevel,
@@ -702,3 +715,82 @@ extension StructDescription {
702715
)
703716
}
704717
}
718+
719+
private func docs(
720+
for method: MethodDescriptor,
721+
serializers includeSerializers: Bool = true
722+
) -> String {
723+
let summary = "/// Call the \"\(method.name.base)\" method."
724+
725+
let request: String
726+
if method.isInputStreaming {
727+
request = "A streaming request producing `\(method.inputType)` messages."
728+
} else {
729+
request = "A request containing a single `\(method.inputType)` message."
730+
}
731+
732+
let parameters = """
733+
/// - Parameters:
734+
/// - request: \(request)
735+
"""
736+
737+
let serializers = """
738+
/// - serializer: A serializer for `\(method.inputType)` messages.
739+
/// - deserializer: A deserializer for `\(method.outputType)` messages.
740+
"""
741+
742+
let otherParameters = """
743+
/// - options: Options to apply to this RPC.
744+
/// - handleResponse: A closure which handles the response, the result of which is
745+
/// returned to the caller. Returning from the closure will cancel the RPC if it
746+
/// hasn't already finished.
747+
/// - Returns: The result of `handleResponse`.
748+
"""
749+
750+
let allParameters: String
751+
if includeSerializers {
752+
allParameters = parameters + "\n" + serializers + "\n" + otherParameters
753+
} else {
754+
allParameters = parameters + "\n" + otherParameters
755+
}
756+
757+
return Docs.interposeDocs(method.documentation, between: summary, and: allParameters)
758+
}
759+
760+
private func explodedDocs(for method: MethodDescriptor) -> String {
761+
let summary = "/// Call the \"\(method.name.base)\" method."
762+
var parameters = """
763+
/// - Parameters:
764+
"""
765+
766+
if !method.isInputStreaming {
767+
parameters += "\n"
768+
parameters += """
769+
/// - message: request message to send.
770+
"""
771+
}
772+
773+
parameters += "\n"
774+
parameters += """
775+
/// - metadata: Additional metadata to send, defaults to empty.
776+
/// - options: Options to apply to this RPC, defaults to `.defaults`.
777+
"""
778+
779+
if method.isInputStreaming {
780+
parameters += "\n"
781+
parameters += """
782+
/// - producer: A closure producing request messages to send to the server. The request
783+
/// stream is closed when the closure returns.
784+
"""
785+
}
786+
787+
parameters += "\n"
788+
parameters += """
789+
/// - handleResponse: A closure which handles the response, the result of which is
790+
/// returned to the caller. Returning from the closure will cancel the RPC if it
791+
/// hasn't already finished.
792+
/// - Returns: The result of `handleResponse`.
793+
"""
794+
795+
return Docs.interposeDocs(method.documentation, between: summary, and: parameters)
796+
}

Sources/GRPCCodeGen/Internal/StructuredSwift+Server.swift

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,31 @@ extension ProtocolDescription {
5656
name: String,
5757
methods: [MethodDescriptor]
5858
) -> Self {
59+
func docs(for method: MethodDescriptor) -> String {
60+
let summary = """
61+
/// Handle the "\(method.name.normalizedBase)" method.
62+
"""
63+
64+
let parameters = """
65+
/// - Parameters:
66+
/// - request: A streaming request of `\(method.inputType)` messages.
67+
/// - context: Context providing information about the RPC.
68+
/// - Throws: Any error which occurred during the processing of the request. Thrown errors
69+
/// of type `RPCError` are mapped to appropriate statuses. All other errors are converted
70+
/// to an internal error.
71+
/// - Returns: A streaming response of `\(method.outputType)` messages.
72+
"""
73+
74+
return Docs.interposeDocs(method.documentation, between: summary, and: parameters)
75+
}
76+
5977
return ProtocolDescription(
6078
accessModifier: accessLevel,
6179
name: name,
6280
conformances: ["GRPCCore.RegistrableRPCService"],
6381
members: methods.map { method in
6482
.commentable(
65-
.preFormatted(method.documentation),
83+
.preFormatted(docs(for: method)),
6684
.function(
6785
signature: .serverMethod(
6886
name: method.name.generatedLowerCase,
@@ -123,13 +141,45 @@ extension ProtocolDescription {
123141
streamingProtocol: String,
124142
methods: [MethodDescriptor]
125143
) -> Self {
144+
func docs(for method: MethodDescriptor) -> String {
145+
let summary = """
146+
/// Handle the "\(method.name.normalizedBase)" method.
147+
"""
148+
149+
let request: String
150+
if method.isInputStreaming {
151+
request = "A streaming request of `\(method.inputType)` messages."
152+
} else {
153+
request = "A request containing a single `\(method.inputType)` message."
154+
}
155+
156+
let returns: String
157+
if method.isOutputStreaming {
158+
returns = "A streaming response of `\(method.outputType)` messages."
159+
} else {
160+
returns = "A response containing a single `\(method.outputType)` message."
161+
}
162+
163+
let parameters = """
164+
/// - Parameters:
165+
/// - request: \(request)
166+
/// - context: Context providing information about the RPC.
167+
/// - Throws: Any error which occurred during the processing of the request. Thrown errors
168+
/// of type `RPCError` are mapped to appropriate statuses. All other errors are converted
169+
/// to an internal error.
170+
/// - Returns: \(returns)
171+
"""
172+
173+
return Docs.interposeDocs(method.documentation, between: summary, and: parameters)
174+
}
175+
126176
return ProtocolDescription(
127177
accessModifier: accessLevel,
128178
name: name,
129179
conformances: [streamingProtocol],
130180
members: methods.map { method in
131181
.commentable(
132-
.preFormatted(method.documentation),
182+
.preFormatted(docs(for: method)),
133183
.function(
134184
signature: .serverMethod(
135185
name: method.name.generatedLowerCase,

0 commit comments

Comments
 (0)