Skip to content

Commit c8e82dc

Browse files
committed
PR changes
1 parent 14fcae9 commit c8e82dc

11 files changed

+278
-133
lines changed

Examples/echo-metadata/Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let package = Package(
2121
name: "echo-metadata",
2222
platforms: [.macOS("15.0")],
2323
dependencies: [
24-
.package(url: "https://github.com/grpc/grpc-swift.git", exact: "2.0.0-rc.1"),
24+
.package(url: "https://github.com/grpc/grpc-swift.git", exact: "2.0.0-rc.1"),
2525
.package(url: "https://github.com/grpc/grpc-swift-protobuf.git", exact: "1.0.0-rc.1"),
2626
.package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", exact: "1.0.0-rc.1"),
2727
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"),

Examples/echo-metadata/README.md

+17-17
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
This example demonstrates how to interact with `Metadata` on RPCs: how to set and read it on unary
44
and streaming requests, as well as how to set and read both initial and trailing metadata on unary
5-
and streaming responses. This is done using a simple 'echo' server and client and the Swift NIO
5+
and streaming responses. This is done using a simple 'echo' server and client and the SwiftNIO
66
based HTTP/2 transport.
77

88
## Overview
99

10-
An `echo-metadata` command line tool that uses generated stubs for an 'echo' service
11-
which allows you to start a server and to make requests against it. The client will automatically
12-
run a unary request followed by a bidirectional streaming request. In both cases, no message will
13-
be sent as part of the request: only the metadata provided as arguments to the executable will be
14-
included.
15-
The server will then echo back all metadata key-value pairs that begin with "echo-". No message
16-
will be included in the responses, and the echoed values will be included in both the initial and
17-
the trailing metadata.
10+
An `echo-metadata` command line tool that uses generated stubs for an 'echo-metadata' service
11+
which allows you to start a server and to make requests against it.
12+
13+
You can use any of the client's subcommands (`get`, `collect`, `expand` and `update`) to send the
14+
provided `message` as both the request's message, and as the value for the `echo-message` key in
15+
the request's metadata.
16+
17+
The server will then echo back the message and the metadata's `echo-message` key-value pair sent
18+
by the client. The request's metadata will be echoed both in the initial and the trailing metadata.
1819

1920
The tool uses the [SwiftNIO](https://github.com/grpc/grpc-swift-nio-transport) HTTP/2 transport.
2021

@@ -36,16 +37,15 @@ $ PROTOC_PATH=$(which protoc) swift run echo-metadata serve
3637
Echo-Metadata listening on [ipv4]127.0.0.1:1234
3738
```
3839

39-
Use the CLI to run the client and make a unary request followed by a bidirectional streaming one:
40+
Use the CLI to run the client and make a `get` (unary) request:
4041

4142
```console
42-
$ PROTOC_PATH=$(which protoc) swift run echo-metadata echo --metadata "echo-key=value" --metadata "another-key=value"
43-
unary → [("echo-key", value)]
44-
unary ← Initial metadata: [("echo-key", value)]
45-
unary ← Trailing metadata: [("echo-key", value)]
46-
bidirectional → [("echo-key", value)]
47-
bidirectional ← Initial metadata: [("echo-key", value)]
48-
bidirectional ← Trailing metadata: [("echo-key", value)]
43+
$ PROTOC_PATH=$(which protoc) swift run echo-metadata get --message "hello"
44+
get → metadata: [("echo-message", "hello")]
45+
get → message: hello
46+
get ← initial metadata: [("echo-message", "hello")]
47+
get ← message: hello
48+
get ← trailing metadata: [("echo-message", "hello")]
4949
```
5050

5151
Get help with the CLI by running:

Examples/echo-metadata/Sources/Subcommands/ClientArguments.swift Examples/echo-metadata/Sources/ClientArguments.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ struct ClientArguments: ParsableArguments {
2323

2424
@Option(
2525
help:
26-
"Metadata 'key=value' pair to send to the server. Key must begin with 'echo-' to be echoed back."
26+
"Message to send to the server. It will also be sent in the request's metadata as the value for `echo-message`."
2727
)
28-
var metadata: [String]
28+
var message: String
2929
}
3030

3131
extension ClientArguments {

Examples/echo-metadata/Sources/EchoMetadata.swift

+1-7
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,6 @@ struct EchoMetadata: AsyncParsableCommand {
2222
static let configuration = CommandConfiguration(
2323
commandName: "echo-metadata",
2424
abstract: "A multi-tool to run an echo-metadata server and execute RPCs against it.",
25-
subcommands: [Serve.self, Echo.self]
25+
subcommands: [Serve.self, Get.self, Collect.self, Update.self, Expand.self]
2626
)
2727
}
28-
29-
extension Metadata {
30-
var echoPairs: Self {
31-
Metadata(self.filter({ $0.key.starts(with: "echo-") }))
32-
}
33-
}

Examples/echo-metadata/Sources/Subcommands/EchoService.swift Examples/echo-metadata/Sources/EchoService.swift

+21-23
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ struct EchoService: Echo_Echo.ServiceProtocol {
2121
request: ServerRequest<Echo_EchoRequest>,
2222
context: ServerContext
2323
) async throws -> ServerResponse<Echo_EchoResponse> {
24-
let responseMetadata = request.metadata.echoPairs
24+
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") }))
2525
return ServerResponse(
26-
message: .init(),
26+
message: .with { $0.text = request.message.text },
2727
metadata: responseMetadata,
2828
trailingMetadata: responseMetadata
2929
)
@@ -33,9 +33,12 @@ struct EchoService: Echo_Echo.ServiceProtocol {
3333
request: StreamingServerRequest<Echo_EchoRequest>,
3434
context: ServerContext
3535
) async throws -> ServerResponse<Echo_EchoResponse> {
36-
let responseMetadata = request.metadata.echoPairs
36+
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") }))
37+
let messages = try await request.messages.reduce(into: []) { $0.append($1.text) }
38+
let joined = messages.joined(separator: " ")
39+
3740
return ServerResponse(
38-
message: .init(),
41+
message: .with { $0.text = joined },
3942
metadata: responseMetadata,
4043
trailingMetadata: responseMetadata
4144
)
@@ -45,31 +48,26 @@ struct EchoService: Echo_Echo.ServiceProtocol {
4548
request: ServerRequest<Echo_EchoRequest>,
4649
context: ServerContext
4750
) async throws -> StreamingServerResponse<Echo_EchoResponse> {
48-
let responseMetadata = request.metadata.echoPairs
49-
return StreamingServerResponse(
50-
single: ServerResponse(
51-
message: .init(),
52-
metadata: responseMetadata,
53-
trailingMetadata: responseMetadata
54-
)
55-
)
51+
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") }))
52+
let parts = request.message.text.split(separator: " ")
53+
let messages = parts.map { part in Echo_EchoResponse.with { $0.text = String(part) } }
54+
55+
return StreamingServerResponse(metadata: responseMetadata) { writer in
56+
try await writer.write(contentsOf: messages)
57+
return responseMetadata
58+
}
5659
}
5760

5861
func update(
5962
request: StreamingServerRequest<Echo_EchoRequest>,
6063
context: ServerContext
6164
) async throws -> StreamingServerResponse<Echo_EchoResponse> {
62-
for try await _ in request.messages {
63-
// Wait for request to be done
65+
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") }))
66+
return StreamingServerResponse(metadata: responseMetadata) { writer in
67+
for try await message in request.messages {
68+
try await writer.write(.with { $0.text = message.text })
69+
}
70+
return responseMetadata
6471
}
65-
66-
let responseMetadata = request.metadata.echoPairs
67-
return StreamingServerResponse(
68-
single: ServerResponse(
69-
message: .init(),
70-
metadata: responseMetadata,
71-
trailingMetadata: responseMetadata
72-
)
73-
)
7472
}
7573
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2025, 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+
import ArgumentParser
18+
import GRPCCore
19+
import GRPCNIOTransportHTTP2
20+
21+
struct Collect: AsyncParsableCommand {
22+
static let configuration = CommandConfiguration(
23+
abstract: "Makes a client streaming RPC to the echo-metadata server."
24+
)
25+
26+
@OptionGroup
27+
var arguments: ClientArguments
28+
29+
func run() async throws {
30+
try await withGRPCClient(
31+
transport: .http2NIOPosix(
32+
target: self.arguments.target,
33+
transportSecurity: .plaintext
34+
)
35+
) { client in
36+
let echo = Echo_Echo.Client(wrapping: client)
37+
let requestMetadata: Metadata = ["echo-message": "\(arguments.message)"]
38+
39+
print("collect → metadata: \(requestMetadata)")
40+
try await echo.collect(metadata: requestMetadata) { writer in
41+
for part in self.arguments.message.split(separator: " ") {
42+
print("collect → \(part)")
43+
try await writer.write(.with { $0.text = String(part) })
44+
}
45+
} onResponse: { response in
46+
print(
47+
"collect ← initial metadata: \(response.metadata.filter({ $0.key.starts(with: "echo-") }))"
48+
)
49+
print("collect ← message: \(try response.message.text)")
50+
print("collect ← trailing metadata: \(response.trailingMetadata)")
51+
}
52+
}
53+
}
54+
}

Examples/echo-metadata/Sources/Subcommands/Echo.swift

-82
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2025, 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+
import ArgumentParser
18+
import GRPCCore
19+
import GRPCNIOTransportHTTP2
20+
21+
struct Expand: AsyncParsableCommand {
22+
static let configuration = CommandConfiguration(
23+
abstract: "Makes a server streaming RPC to the echo-metadata server."
24+
)
25+
26+
@OptionGroup
27+
var arguments: ClientArguments
28+
29+
func run() async throws {
30+
try await withGRPCClient(
31+
transport: .http2NIOPosix(
32+
target: self.arguments.target,
33+
transportSecurity: .plaintext
34+
)
35+
) { client in
36+
let echo = Echo_Echo.Client(wrapping: client)
37+
let requestMetadata: Metadata = ["echo-message": "\(arguments.message)"]
38+
let message = Echo_EchoRequest.with { $0.text = self.arguments.message }
39+
40+
print("expand → metadata: \(requestMetadata)")
41+
print("expand → message: \(message.text)")
42+
43+
try await echo.expand(message, metadata: requestMetadata) { response in
44+
let responseContents = try response.accepted.get()
45+
46+
print(
47+
"expand ← initial metadata: \(Metadata(responseContents.metadata.filter({ $0.key.starts(with: "echo-") })))"
48+
)
49+
for try await part in responseContents.bodyParts {
50+
switch part {
51+
case .message(let message):
52+
print("expand ← message: \(message.text)")
53+
54+
case .trailingMetadata(let trailingMetadata):
55+
print(
56+
"expand ← trailing metadata: \(Metadata(trailingMetadata.filter({ $0.key.starts(with: "echo-") })))"
57+
)
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)