Skip to content

Commit 6e6bdeb

Browse files
authored
Generate static service metadata (#1322)
Motivation: Sometimes it's useful to know information about a service, including its name, methods it offers and so on. An example where this is useful is service discovery (#1183). However, we currently only provide the service name and this isn't available statically. For service discovery this is problematic as it requires users to create a client in order to get the service name so that a server can be dialled. For the async/await code, since it is not yet final, we can add requirements to generated protocols to provide the service metadata statically. Modifications: - Add service and method descriptors to `GRPC` - Generate service descriptors for client and server separately (it's feasible that a user may generate client code into one module and server code into separate modules) - Update the generated code for async/await and NIO based APIs to use the descriptors directly rather than generating literals in places where they are required. - Add test for the generated echo service metadata - Regenerate other services Result: Adopters can get static information about services.
1 parent b0ee726 commit 6e6bdeb

File tree

17 files changed

+1225
-322
lines changed

17 files changed

+1225
-322
lines changed

Sources/Examples/Echo/Model/echo.grpc.swift

Lines changed: 145 additions & 62 deletions
Large diffs are not rendered by default.

Sources/Examples/HelloWorld/Model/helloworld.grpc.swift

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,14 @@ extension Helloworld_GreeterClientProtocol {
5454
callOptions: CallOptions? = nil
5555
) -> UnaryCall<Helloworld_HelloRequest, Helloworld_HelloReply> {
5656
return self.makeUnaryCall(
57-
path: "/helloworld.Greeter/SayHello",
57+
path: Helloworld_GreeterClientMetadata.Methods.sayHello.path,
5858
request: request,
5959
callOptions: callOptions ?? self.defaultCallOptions,
6060
interceptors: self.interceptors?.makeSayHelloInterceptors() ?? []
6161
)
6262
}
6363
}
6464

65-
public protocol Helloworld_GreeterClientInterceptorFactoryProtocol {
66-
67-
/// - Returns: Interceptors to use when invoking 'sayHello'.
68-
func makeSayHelloInterceptors() -> [ClientInterceptor<Helloworld_HelloRequest, Helloworld_HelloReply>]
69-
}
70-
7165
public final class Helloworld_GreeterClient: Helloworld_GreeterClientProtocol {
7266
public let channel: GRPCChannel
7367
public var defaultCallOptions: CallOptions
@@ -90,6 +84,30 @@ public final class Helloworld_GreeterClient: Helloworld_GreeterClientProtocol {
9084
}
9185
}
9286

87+
public protocol Helloworld_GreeterClientInterceptorFactoryProtocol {
88+
89+
/// - Returns: Interceptors to use when invoking 'sayHello'.
90+
func makeSayHelloInterceptors() -> [ClientInterceptor<Helloworld_HelloRequest, Helloworld_HelloReply>]
91+
}
92+
93+
public enum Helloworld_GreeterClientMetadata {
94+
public static let serviceDescriptor = GRPCServiceDescriptor(
95+
name: "Greeter",
96+
fullName: "helloworld.Greeter",
97+
methods: [
98+
Helloworld_GreeterClientMetadata.Methods.sayHello,
99+
]
100+
)
101+
102+
public enum Methods {
103+
public static let sayHello = GRPCMethodDescriptor(
104+
name: "SayHello",
105+
path: "/helloworld.Greeter/SayHello",
106+
type: GRPCCallType.unary
107+
)
108+
}
109+
}
110+
93111
/// The greeting service definition.
94112
///
95113
/// To build a server, implement a class that conforms to this protocol.
@@ -101,7 +119,9 @@ public protocol Helloworld_GreeterProvider: CallHandlerProvider {
101119
}
102120

103121
extension Helloworld_GreeterProvider {
104-
public var serviceName: Substring { return "helloworld.Greeter" }
122+
public var serviceName: Substring {
123+
return Helloworld_GreeterServerMetadata.serviceDescriptor.fullName[...]
124+
}
105125

106126
/// Determines, calls and returns the appropriate request handler, depending on the request's method.
107127
/// Returns nil for methods not handled by this service.
@@ -132,3 +152,20 @@ public protocol Helloworld_GreeterServerInterceptorFactoryProtocol {
132152
func makeSayHelloInterceptors() -> [ServerInterceptor<Helloworld_HelloRequest, Helloworld_HelloReply>]
133153
}
134154

155+
public enum Helloworld_GreeterServerMetadata {
156+
public static let serviceDescriptor = GRPCServiceDescriptor(
157+
name: "Greeter",
158+
fullName: "helloworld.Greeter",
159+
methods: [
160+
Helloworld_GreeterServerMetadata.Methods.sayHello,
161+
]
162+
)
163+
164+
public enum Methods {
165+
public static let sayHello = GRPCMethodDescriptor(
166+
name: "SayHello",
167+
path: "/helloworld.Greeter/SayHello",
168+
type: GRPCCallType.unary
169+
)
170+
}
171+
}

Sources/Examples/RouteGuide/Model/route_guide.grpc.swift

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ extension Routeguide_RouteGuideClientProtocol {
7474
callOptions: CallOptions? = nil
7575
) -> UnaryCall<Routeguide_Point, Routeguide_Feature> {
7676
return self.makeUnaryCall(
77-
path: "/routeguide.RouteGuide/GetFeature",
77+
path: Routeguide_RouteGuideClientMetadata.Methods.getFeature.path,
7878
request: request,
7979
callOptions: callOptions ?? self.defaultCallOptions,
8080
interceptors: self.interceptors?.makeGetFeatureInterceptors() ?? []
@@ -99,7 +99,7 @@ extension Routeguide_RouteGuideClientProtocol {
9999
handler: @escaping (Routeguide_Feature) -> Void
100100
) -> ServerStreamingCall<Routeguide_Rectangle, Routeguide_Feature> {
101101
return self.makeServerStreamingCall(
102-
path: "/routeguide.RouteGuide/ListFeatures",
102+
path: Routeguide_RouteGuideClientMetadata.Methods.listFeatures.path,
103103
request: request,
104104
callOptions: callOptions ?? self.defaultCallOptions,
105105
interceptors: self.interceptors?.makeListFeaturesInterceptors() ?? [],
@@ -122,7 +122,7 @@ extension Routeguide_RouteGuideClientProtocol {
122122
callOptions: CallOptions? = nil
123123
) -> ClientStreamingCall<Routeguide_Point, Routeguide_RouteSummary> {
124124
return self.makeClientStreamingCall(
125-
path: "/routeguide.RouteGuide/RecordRoute",
125+
path: Routeguide_RouteGuideClientMetadata.Methods.recordRoute.path,
126126
callOptions: callOptions ?? self.defaultCallOptions,
127127
interceptors: self.interceptors?.makeRecordRouteInterceptors() ?? []
128128
)
@@ -145,29 +145,14 @@ extension Routeguide_RouteGuideClientProtocol {
145145
handler: @escaping (Routeguide_RouteNote) -> Void
146146
) -> BidirectionalStreamingCall<Routeguide_RouteNote, Routeguide_RouteNote> {
147147
return self.makeBidirectionalStreamingCall(
148-
path: "/routeguide.RouteGuide/RouteChat",
148+
path: Routeguide_RouteGuideClientMetadata.Methods.routeChat.path,
149149
callOptions: callOptions ?? self.defaultCallOptions,
150150
interceptors: self.interceptors?.makeRouteChatInterceptors() ?? [],
151151
handler: handler
152152
)
153153
}
154154
}
155155

156-
public protocol Routeguide_RouteGuideClientInterceptorFactoryProtocol {
157-
158-
/// - Returns: Interceptors to use when invoking 'getFeature'.
159-
func makeGetFeatureInterceptors() -> [ClientInterceptor<Routeguide_Point, Routeguide_Feature>]
160-
161-
/// - Returns: Interceptors to use when invoking 'listFeatures'.
162-
func makeListFeaturesInterceptors() -> [ClientInterceptor<Routeguide_Rectangle, Routeguide_Feature>]
163-
164-
/// - Returns: Interceptors to use when invoking 'recordRoute'.
165-
func makeRecordRouteInterceptors() -> [ClientInterceptor<Routeguide_Point, Routeguide_RouteSummary>]
166-
167-
/// - Returns: Interceptors to use when invoking 'routeChat'.
168-
func makeRouteChatInterceptors() -> [ClientInterceptor<Routeguide_RouteNote, Routeguide_RouteNote>]
169-
}
170-
171156
public final class Routeguide_RouteGuideClient: Routeguide_RouteGuideClientProtocol {
172157
public let channel: GRPCChannel
173158
public var defaultCallOptions: CallOptions
@@ -190,6 +175,60 @@ public final class Routeguide_RouteGuideClient: Routeguide_RouteGuideClientProto
190175
}
191176
}
192177

178+
public protocol Routeguide_RouteGuideClientInterceptorFactoryProtocol {
179+
180+
/// - Returns: Interceptors to use when invoking 'getFeature'.
181+
func makeGetFeatureInterceptors() -> [ClientInterceptor<Routeguide_Point, Routeguide_Feature>]
182+
183+
/// - Returns: Interceptors to use when invoking 'listFeatures'.
184+
func makeListFeaturesInterceptors() -> [ClientInterceptor<Routeguide_Rectangle, Routeguide_Feature>]
185+
186+
/// - Returns: Interceptors to use when invoking 'recordRoute'.
187+
func makeRecordRouteInterceptors() -> [ClientInterceptor<Routeguide_Point, Routeguide_RouteSummary>]
188+
189+
/// - Returns: Interceptors to use when invoking 'routeChat'.
190+
func makeRouteChatInterceptors() -> [ClientInterceptor<Routeguide_RouteNote, Routeguide_RouteNote>]
191+
}
192+
193+
public enum Routeguide_RouteGuideClientMetadata {
194+
public static let serviceDescriptor = GRPCServiceDescriptor(
195+
name: "RouteGuide",
196+
fullName: "routeguide.RouteGuide",
197+
methods: [
198+
Routeguide_RouteGuideClientMetadata.Methods.getFeature,
199+
Routeguide_RouteGuideClientMetadata.Methods.listFeatures,
200+
Routeguide_RouteGuideClientMetadata.Methods.recordRoute,
201+
Routeguide_RouteGuideClientMetadata.Methods.routeChat,
202+
]
203+
)
204+
205+
public enum Methods {
206+
public static let getFeature = GRPCMethodDescriptor(
207+
name: "GetFeature",
208+
path: "/routeguide.RouteGuide/GetFeature",
209+
type: GRPCCallType.unary
210+
)
211+
212+
public static let listFeatures = GRPCMethodDescriptor(
213+
name: "ListFeatures",
214+
path: "/routeguide.RouteGuide/ListFeatures",
215+
type: GRPCCallType.serverStreaming
216+
)
217+
218+
public static let recordRoute = GRPCMethodDescriptor(
219+
name: "RecordRoute",
220+
path: "/routeguide.RouteGuide/RecordRoute",
221+
type: GRPCCallType.clientStreaming
222+
)
223+
224+
public static let routeChat = GRPCMethodDescriptor(
225+
name: "RouteChat",
226+
path: "/routeguide.RouteGuide/RouteChat",
227+
type: GRPCCallType.bidirectionalStreaming
228+
)
229+
}
230+
}
231+
193232
/// Interface exported by the server.
194233
///
195234
/// To build a server, implement a class that conforms to this protocol.
@@ -226,7 +265,9 @@ public protocol Routeguide_RouteGuideProvider: CallHandlerProvider {
226265
}
227266

228267
extension Routeguide_RouteGuideProvider {
229-
public var serviceName: Substring { return "routeguide.RouteGuide" }
268+
public var serviceName: Substring {
269+
return Routeguide_RouteGuideServerMetadata.serviceDescriptor.fullName[...]
270+
}
230271

231272
/// Determines, calls and returns the appropriate request handler, depending on the request's method.
232273
/// Returns nil for methods not handled by this service.
@@ -296,3 +337,41 @@ public protocol Routeguide_RouteGuideServerInterceptorFactoryProtocol {
296337
func makeRouteChatInterceptors() -> [ServerInterceptor<Routeguide_RouteNote, Routeguide_RouteNote>]
297338
}
298339

340+
public enum Routeguide_RouteGuideServerMetadata {
341+
public static let serviceDescriptor = GRPCServiceDescriptor(
342+
name: "RouteGuide",
343+
fullName: "routeguide.RouteGuide",
344+
methods: [
345+
Routeguide_RouteGuideServerMetadata.Methods.getFeature,
346+
Routeguide_RouteGuideServerMetadata.Methods.listFeatures,
347+
Routeguide_RouteGuideServerMetadata.Methods.recordRoute,
348+
Routeguide_RouteGuideServerMetadata.Methods.routeChat,
349+
]
350+
)
351+
352+
public enum Methods {
353+
public static let getFeature = GRPCMethodDescriptor(
354+
name: "GetFeature",
355+
path: "/routeguide.RouteGuide/GetFeature",
356+
type: GRPCCallType.unary
357+
)
358+
359+
public static let listFeatures = GRPCMethodDescriptor(
360+
name: "ListFeatures",
361+
path: "/routeguide.RouteGuide/ListFeatures",
362+
type: GRPCCallType.serverStreaming
363+
)
364+
365+
public static let recordRoute = GRPCMethodDescriptor(
366+
name: "RecordRoute",
367+
path: "/routeguide.RouteGuide/RecordRoute",
368+
type: GRPCCallType.clientStreaming
369+
)
370+
371+
public static let routeChat = GRPCMethodDescriptor(
372+
name: "RouteChat",
373+
path: "/routeguide.RouteGuide/RouteChat",
374+
type: GRPCCallType.bidirectionalStreaming
375+
)
376+
}
377+
}

Sources/Examples/RouteGuide/Model/route_guide.pb.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,16 @@ extension Routeguide_Rectangle: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
231231
}
232232

233233
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
234-
if let v = self._lo {
234+
// The use of inline closures is to circumvent an issue where the compiler
235+
// allocates stack space for every if/case branch local when no optimizations
236+
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
237+
// https://github.com/apple/swift-protobuf/issues/1182
238+
try { if let v = self._lo {
235239
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
236-
}
237-
if let v = self._hi {
240+
} }()
241+
try { if let v = self._hi {
238242
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
239-
}
243+
} }()
240244
try unknownFields.traverse(visitor: &visitor)
241245
}
242246

@@ -269,12 +273,16 @@ extension Routeguide_Feature: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
269273
}
270274

271275
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
276+
// The use of inline closures is to circumvent an issue where the compiler
277+
// allocates stack space for every if/case branch local when no optimizations
278+
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
279+
// https://github.com/apple/swift-protobuf/issues/1182
272280
if !self.name.isEmpty {
273281
try visitor.visitSingularStringField(value: self.name, fieldNumber: 1)
274282
}
275-
if let v = self._location {
283+
try { if let v = self._location {
276284
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
277-
}
285+
} }()
278286
try unknownFields.traverse(visitor: &visitor)
279287
}
280288

@@ -307,9 +315,13 @@ extension Routeguide_RouteNote: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
307315
}
308316

309317
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
310-
if let v = self._location {
318+
// The use of inline closures is to circumvent an issue where the compiler
319+
// allocates stack space for every if/case branch local when no optimizations
320+
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
321+
// https://github.com/apple/swift-protobuf/issues/1182
322+
try { if let v = self._location {
311323
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
312-
}
324+
} }()
313325
if !self.message.isEmpty {
314326
try visitor.visitSingularStringField(value: self.message, fieldNumber: 2)
315327
}

Sources/GRPC/GRPCClientChannelHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ extension _GRPCRequestHead {
226226
}
227227

228228
/// The type of gRPC call.
229-
public enum GRPCCallType {
229+
public enum GRPCCallType: Hashable {
230230
/// Unary: a single request and a single response.
231231
case unary
232232

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2021, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
public struct GRPCServiceDescriptor: Hashable {
18+
/// The name of the service excluding the package, e.g. 'Echo'.
19+
public var name: String
20+
21+
/// The full name of the service including the package, e.g. 'echo.Echo'
22+
public var fullName: String
23+
24+
/// Methods defined on the service.
25+
public var methods: [GRPCMethodDescriptor]
26+
27+
public init(name: String, fullName: String, methods: [GRPCMethodDescriptor]) {
28+
self.name = name
29+
self.fullName = fullName
30+
self.methods = methods
31+
}
32+
}
33+
34+
public struct GRPCMethodDescriptor: Hashable {
35+
/// The name of the method, e.g. 'Get'.
36+
public var name: String
37+
38+
/// The full name of the method include the fully qualified name of the service in the
39+
/// format 'package.Service/Method', for example 'echo.Echo/Get'.
40+
///
41+
/// This differs from the ``path`` only in that the leading '/' is removed.
42+
public var fullName: String {
43+
assert(self.path.utf8.first == UInt8(ascii: "/"))
44+
return String(self.path.dropFirst())
45+
}
46+
47+
/// The path of the method in the format '/package.Service/method', for example '/echo.Echo/Get'.
48+
public var path: String
49+
50+
/// The type of call.
51+
public var type: GRPCCallType
52+
53+
public init(name: String, path: String, type: GRPCCallType) {
54+
self.name = name
55+
self.path = path
56+
self.type = type
57+
}
58+
}

0 commit comments

Comments
 (0)