Skip to content

Commit 39dbe75

Browse files
authored
Merge branch 'main' into v2/client-code-gen
2 parents 5b67ed9 + 3b0fe70 commit 39dbe75

File tree

9 files changed

+272
-489
lines changed

9 files changed

+272
-489
lines changed

Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,25 +81,22 @@ struct TypealiasTranslator: SpecializedTranslator {
8181

8282
func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] {
8383
var codeBlocks = [CodeBlock]()
84-
let services = codeGenerationRequest.services
85-
let servicesByEnumName = Dictionary(
86-
grouping: services,
87-
by: { $0.namespacedGeneratedName }
88-
)
8984

90-
// Sorting the keys of the dictionary is necessary so that the generated enums are deterministically ordered.
91-
for (generatedEnumName, services) in servicesByEnumName.sorted(by: { $0.key < $1.key }) {
92-
for service in services {
93-
codeBlocks.append(
94-
CodeBlock(
95-
item: .declaration(try self.makeServiceEnum(from: service, named: generatedEnumName))
85+
for service in codeGenerationRequest.services {
86+
codeBlocks.append(
87+
CodeBlock(
88+
item: .declaration(
89+
try self.makeServiceEnum(
90+
from: service,
91+
named: service.namespacedGeneratedName
92+
)
9693
)
9794
)
95+
)
9896

99-
codeBlocks.append(
100-
CodeBlock(item: .declaration(self.makeServiceDescriptorExtension(for: service)))
101-
)
102-
}
97+
codeBlocks.append(
98+
CodeBlock(item: .declaration(self.makeServiceDescriptorExtension(for: service)))
99+
)
103100
}
104101

105102
return codeBlocks
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Compatibility
2+
3+
Learn which versions of Swift are supported by gRPC Swift and how this changes
4+
over time.
5+
6+
## Overview
7+
8+
### Supported Swift Versions
9+
10+
gRPC Swift supports the **three most recent Swift versions** that are **Swift 6
11+
or newer**. Within a minor Swift release only the latest patch version is supported.
12+
13+
| grpc-swift | Minimum Supported Swift version |
14+
|------------|---------------------------------|
15+
| 2.0 | 6.0 |
16+
17+
### Platforms
18+
19+
gRPC Swift aims to support the same platforms as Swift project. These are listed
20+
on [swift.org](https://www.swift.org/platform-support/).
21+
22+
The only known unsupported platform from that list is currently Windows.

Sources/GRPCCore/Documentation.docc/Documentation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ as tutorials.
5353

5454
- <doc:Generating-stubs>
5555

56+
### Project Information
57+
58+
- <doc:Compatibility>
59+
5660
### Getting involved
5761

5862
Resources for developers working on gRPC Swift:

Sources/GRPCCore/GRPCClient.swift

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -28,79 +28,25 @@ private import Synchronization
2828
///
2929
/// However, in most cases you should prefer wrapping the ``GRPCClient`` with a generated stub.
3030
///
31-
/// You can set ``ServiceConfig``s on this client to override whatever configurations have been
32-
/// set on the given transport. You can also use ``ClientInterceptor``s to implement cross-cutting
33-
/// logic which apply to all RPCs. Example uses of interceptors include authentication and logging.
31+
/// ## Creating a client
3432
///
35-
/// ## Creating and configuring a client
36-
///
37-
/// The following example demonstrates how to create and configure a client.
33+
/// You can create and run a client using ``withGRPCClient(transport:interceptors:isolation:handleClient:)``
34+
/// or ``withGRPCClient(transport:interceptorPipeline:isolation:handleClient:)`` which create, configure and
35+
/// run the client providing scoped access to it via the `handleClient` closure. The client will
36+
/// begin gracefully shutting down when the closure returns.
3837
///
3938
/// ```swift
40-
/// // Create a configuration object for the client and override the timeout for the 'Get' method on
41-
/// // the 'echo.Echo' service. This configuration takes precedence over any set by the transport.
42-
/// var configuration = GRPCClient.Configuration()
43-
/// configuration.service.override = ServiceConfig(
44-
/// methodConfig: [
45-
/// MethodConfig(
46-
/// names: [
47-
/// MethodConfig.Name(service: "echo.Echo", method: "Get")
48-
/// ],
49-
/// timeout: .seconds(5)
50-
/// )
51-
/// ]
52-
/// )
53-
///
54-
/// // Configure a fallback timeout for all RPCs (indicated by an empty service and method name) if
55-
/// // no configuration is provided in the overrides or by the transport.
56-
/// configuration.service.defaults = ServiceConfig(
57-
/// methodConfig: [
58-
/// MethodConfig(
59-
/// names: [
60-
/// MethodConfig.Name(service: "", method: "")
61-
/// ],
62-
/// timeout: .seconds(10)
63-
/// )
64-
/// ]
65-
/// )
66-
///
67-
/// // Finally create a transport and instantiate the client, adding an interceptor.
68-
/// let inProcessTransport = InProcessTransport()
69-
///
70-
/// let client = GRPCClient(
71-
/// transport: inProcessTransport.client,
72-
/// interceptors: [StatsRecordingClientInterceptor()],
73-
/// configuration: configuration
74-
/// )
39+
/// let transport: any ClientTransport = ...
40+
/// try await withGRPCClient(transport: transport) { client in
41+
/// // ...
42+
/// }
7543
/// ```
7644
///
77-
/// ## Starting and stopping the client
45+
/// ## Creating a client manually
7846
///
79-
/// Once you have configured the client, call ``run()`` to start it. Calling ``run()`` instructs the
80-
/// transport to start connecting to the server.
81-
///
82-
/// ```swift
83-
/// // Start running the client. 'run()' must be running while RPCs are execute so it's executed in
84-
/// // a task group.
85-
/// try await withThrowingTaskGroup(of: Void.self) { group in
86-
/// group.addTask {
87-
/// try await client.run()
88-
/// }
89-
///
90-
/// // Execute a request against the "echo.Echo" service.
91-
/// try await client.unary(
92-
/// request: ClientRequest<[UInt8]>(message: [72, 101, 108, 108, 111, 33]),
93-
/// descriptor: MethodDescriptor(service: "echo.Echo", method: "Get"),
94-
/// serializer: IdentitySerializer(),
95-
/// deserializer: IdentityDeserializer(),
96-
/// ) { response in
97-
/// print(response.message)
98-
/// }
99-
///
100-
/// // The RPC has completed, close the client.
101-
/// client.beginGracefulShutdown()
102-
/// }
103-
/// ```
47+
/// If the `with`-style methods for creating clients isn't suitable for your application then you
48+
/// can create and run a client manually. This requires you to call the ``run()`` method in a task
49+
/// which instructs the client to start connecting to the server.
10450
///
10551
/// The ``run()`` method won't return until the client has finished handling all requests. You can
10652
/// signal to the client that it should stop creating new request streams by calling ``beginGracefulShutdown()``.
@@ -425,3 +371,62 @@ public final class GRPCClient: Sendable {
425371
)
426372
}
427373
}
374+
375+
/// Creates and runs a new client with the given transport and interceptors.
376+
///
377+
/// - Parameters:
378+
/// - transport: The transport used to establish a communication channel with a server.
379+
/// - interceptors: A collection of ``ClientInterceptor``s providing cross-cutting functionality to each
380+
/// accepted RPC. The order in which interceptors are added reflects the order in which they
381+
/// are called. The first interceptor added will be the first interceptor to intercept each
382+
/// request. The last interceptor added will be the final interceptor to intercept each
383+
/// request before calling the appropriate handler.
384+
/// - isolation: A reference to the actor to which the enclosing code is isolated, or nil if the
385+
/// code is nonisolated.
386+
/// - handleClient: A closure which is called with the client. When the closure returns, the
387+
/// client is shutdown gracefully.
388+
public func withGRPCClient<Result: Sendable>(
389+
transport: some ClientTransport,
390+
interceptors: [any ClientInterceptor] = [],
391+
isolation: isolated (any Actor)? = #isolation,
392+
handleClient: (GRPCClient) async throws -> Result
393+
) async throws -> Result {
394+
try await withGRPCClient(
395+
transport: transport,
396+
interceptorPipeline: interceptors.map { .apply($0, to: .all) },
397+
isolation: isolation,
398+
handleClient: handleClient
399+
)
400+
}
401+
402+
/// Creates and runs a new client with the given transport and interceptors.
403+
///
404+
/// - Parameters:
405+
/// - transport: The transport used to establish a communication channel with a server.
406+
/// - interceptorPipeline: A collection of ``ClientInterceptorPipelineOperation`` providing cross-cutting
407+
/// functionality to each accepted RPC. Only applicable interceptors from the pipeline will be applied to each RPC.
408+
/// The order in which interceptors are added reflects the order in which they are called.
409+
/// The first interceptor added will be the first interceptor to intercept each request.
410+
/// The last interceptor added will be the final interceptor to intercept each request before calling the appropriate handler.
411+
/// - isolation: A reference to the actor to which the enclosing code is isolated, or nil if the
412+
/// code is nonisolated.
413+
/// - handleClient: A closure which is called with the client. When the closure returns, the
414+
/// client is shutdown gracefully.
415+
/// - Returns: The result of the `handleClient` closure.
416+
public func withGRPCClient<Result: Sendable>(
417+
transport: some ClientTransport,
418+
interceptorPipeline: [ClientInterceptorPipelineOperation],
419+
isolation: isolated (any Actor)? = #isolation,
420+
handleClient: (GRPCClient) async throws -> Result
421+
) async throws -> Result {
422+
try await withThrowingDiscardingTaskGroup { group in
423+
let client = GRPCClient(transport: transport, interceptorPipeline: interceptorPipeline)
424+
group.addTask {
425+
try await client.run()
426+
}
427+
428+
let result = try await handleClient(client)
429+
client.beginGracefulShutdown()
430+
return result
431+
}
432+
}

Sources/GRPCCore/GRPCServer.swift

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ private import Synchronization
2929
/// include request filtering, authentication, and logging. Once requests have been intercepted
3030
/// they are passed to a handler which in turn returns a response to send back to the client.
3131
///
32-
/// ## Creating and configuring a server
32+
/// ## Configuring and starting a server
3333
///
34-
/// The following example demonstrates how to create and configure a server.
34+
/// The following example demonstrates how to create and run a server.
3535
///
3636
/// ```swift
37-
/// // Create and an in-process transport.
38-
/// let inProcessTransport = InProcessTransport()
37+
/// // Create an transport
38+
/// let transport: any ServerTransport = ...
3939
///
4040
/// // Create the 'Greeter' and 'Echo' services.
4141
/// let greeter = GreeterService()
@@ -44,19 +44,24 @@ private import Synchronization
4444
/// // Create an interceptor.
4545
/// let statsRecorder = StatsRecordingServerInterceptors()
4646
///
47-
/// // Finally create the server.
48-
/// let server = GRPCServer(
49-
/// transport: inProcessTransport.server,
47+
/// // Run the server.
48+
/// try await withGRPCServer(
49+
/// transport: transport,
5050
/// services: [greeter, echo],
5151
/// interceptors: [statsRecorder]
52-
/// )
52+
/// ) { server in
53+
/// // ...
54+
/// // The server begins shutting down when this closure returns
55+
/// // ...
56+
/// }
5357
/// ```
5458
///
55-
/// ## Starting and stopping the server
59+
/// ## Creating a client manually
5660
///
57-
/// Once you have configured the server call ``serve()`` to start it. Calling ``serve()`` starts the server's
58-
/// transport too. A ``RuntimeError`` is thrown if the transport can't be started or encounters some other
59-
/// runtime error.
61+
/// If the `with`-style methods for creating a server isn't suitable for your application then you
62+
/// can create and run it manually. This requires you to call the ``serve()`` method in a task
63+
/// which instructs the server to start its transport and listen for new RPCs. A ``RuntimeError`` is
64+
/// thrown if the transport can't be started or encounters some other runtime error.
6065
///
6166
/// ```swift
6267
/// // Start running the server.
@@ -235,3 +240,73 @@ public final class GRPCServer: Sendable {
235240
}
236241
}
237242
}
243+
244+
/// Creates and runs a gRPC server.
245+
///
246+
/// - Parameters:
247+
/// - transport: The transport the server should listen on.
248+
/// - services: Services offered by the server.
249+
/// - interceptors: A collection of interceptors providing cross-cutting functionality to each
250+
/// accepted RPC. The order in which interceptors are added reflects the order in which they
251+
/// are called. The first interceptor added will be the first interceptor to intercept each
252+
/// request. The last interceptor added will be the final interceptor to intercept each
253+
/// request before calling the appropriate handler.
254+
/// - isolation: A reference to the actor to which the enclosing code is isolated, or nil if the
255+
/// code is nonisolated.
256+
/// - handleServer: A closure which is called with the server. When the closure returns, the
257+
/// server is shutdown gracefully.
258+
/// - Returns: The result of the `handleServer` closure.
259+
public func withGRPCServer<Result: Sendable>(
260+
transport: any ServerTransport,
261+
services: [any RegistrableRPCService],
262+
interceptors: [any ServerInterceptor] = [],
263+
isolation: isolated (any Actor)? = #isolation,
264+
handleServer: (GRPCServer) async throws -> Result
265+
) async throws -> Result {
266+
try await withGRPCServer(
267+
transport: transport,
268+
services: services,
269+
interceptorPipeline: interceptors.map { .apply($0, to: .all) },
270+
isolation: isolation,
271+
handleServer: handleServer
272+
)
273+
}
274+
275+
/// Creates and runs a gRPC server.
276+
///
277+
/// - Parameters:
278+
/// - transport: The transport the server should listen on.
279+
/// - services: Services offered by the server.
280+
/// - interceptorPipeline: A collection of interceptors providing cross-cutting functionality to each
281+
/// accepted RPC. The order in which interceptors are added reflects the order in which they
282+
/// are called. The first interceptor added will be the first interceptor to intercept each
283+
/// request. The last interceptor added will be the final interceptor to intercept each
284+
/// request before calling the appropriate handler.
285+
/// - isolation: A reference to the actor to which the enclosing code is isolated, or nil if the
286+
/// code is nonisolated.
287+
/// - handleServer: A closure which is called with the server. When the closure returns, the
288+
/// server is shutdown gracefully.
289+
/// - Returns: The result of the `handleServer` closure.
290+
public func withGRPCServer<Result: Sendable>(
291+
transport: any ServerTransport,
292+
services: [any RegistrableRPCService],
293+
interceptorPipeline: [ServerInterceptorPipelineOperation],
294+
isolation: isolated (any Actor)? = #isolation,
295+
handleServer: (GRPCServer) async throws -> Result
296+
) async throws -> Result {
297+
return try await withThrowingDiscardingTaskGroup { group in
298+
let server = GRPCServer(
299+
transport: transport,
300+
services: services,
301+
interceptorPipeline: interceptorPipeline
302+
)
303+
304+
group.addTask {
305+
try await server.serve()
306+
}
307+
308+
let result = try await handleServer(server)
309+
server.beginGracefulShutdown()
310+
return result
311+
}
312+
}

0 commit comments

Comments
 (0)