Skip to content

Commit 8f03192

Browse files
Implemented file-by-filename and list-services requests (#1669)
Motivation: The two requests are necessary for implementing the Reflection Service. Modifications: Implemented the ReflectionServiceData struct that stores the useful information from the file descriptor protos received when initiaizing the service and provides the data used for constructing the server response for each type of request. Also imlemented the functions that create the server responses for the two requests. The requests and the functionality of the ReflectionServiceData struct's methods are tested in the "Tests/GRPCTests/GRPCReflectionServiceTests/GRPCReflectionServiceTests.swift" file. Result: The Reflection Service will support the file-by-filename and list-services requests.
1 parent 0fde772 commit 8f03192

File tree

8 files changed

+1685
-30
lines changed

8 files changed

+1685
-30
lines changed

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,27 @@ ${REFLECTION_GRPC}: ${REFLECTION_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
147147
.PHONY:
148148
generate-reflection: ${REFLECTION_PB} ${REFLECTION_GRPC}
149149

150+
TEST_REFLECTION_GRPC=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/reflection.grpc.swift
151+
TEST_REFLECTION_PB=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/reflection.pb.swift
152+
153+
# For Reflection we'll generate only the Server code.
154+
${TEST_REFLECTION_GRPC}: ${REFLECTION_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
155+
protoc $< \
156+
--proto_path=$(dir $<) \
157+
--plugin=${PROTOC_GEN_GRPC_SWIFT} \
158+
--grpc-swift_opt=Client=true,Server=false \
159+
--grpc-swift_out=$(dir ${TEST_REFLECTION_GRPC})
160+
161+
${TEST_REFLECTION_PB}: ${REFLECTION_PROTO} ${PROTOC_GEN_SWIFT}
162+
protoc $< \
163+
--proto_path=$(dir $<) \
164+
--plugin=${PROTOC_GEN_SWIFT} \
165+
--swift_out=$(dir ${TEST_REFLECTION_PB})
166+
167+
# Generates protobufs and gRPC client for the Reflection Service Tests
168+
.PHONY:
169+
generate-reflection-client: ${TEST_REFLECTION_PB} ${TEST_REFLECTION_GRPC}
170+
150171
### Testing ####################################################################
151172

152173
# Normal test suite.

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ extension Target {
207207
.nioEmbedded,
208208
.nioTransportServices,
209209
.logging,
210+
.reflectionService,
210211
].appending(
211212
.nioSSL, if: includeNIOSSL
212213
),

Sources/GRPCReflectionService/Model/reflection.pb.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// For information on using the generated types, please see the documentation:
88
// https://github.com/apple/swift-protobuf/
99

10-
// Copyright 2023 The gRPC Authors
10+
// Copyright 2016 The gRPC Authors
1111
//
1212
// Licensed under the Apache License, Version 2.0 (the "License");
1313
// you may not use this file except in compliance with the License.

Sources/GRPCReflectionService/Server/ReflectionProvider.swift

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright 2023, 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 DequeModule
18+
import Foundation
19+
import GRPC
20+
import SwiftProtobuf
21+
22+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
23+
public final class ReflectionService: CallHandlerProvider, Sendable {
24+
private let reflectionService: ReflectionServiceProvider
25+
public var serviceName: Substring {
26+
self.reflectionService.serviceName
27+
}
28+
29+
public init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
30+
self.reflectionService = try ReflectionServiceProvider(fileDescriptorProtos: fileDescriptors)
31+
}
32+
33+
public func handle(
34+
method name: Substring,
35+
context: GRPC.CallHandlerContext
36+
) -> GRPC.GRPCServerHandlerProtocol? {
37+
self.reflectionService.handle(method: name, context: context)
38+
}
39+
}
40+
41+
internal struct ReflectionServiceData: Sendable {
42+
internal struct FileDescriptorProtoData: Sendable {
43+
internal var serializedFileDescriptorProto: Data
44+
internal var dependencyFileNames: [String]
45+
}
46+
47+
internal var fileDescriptorDataByFilename: [String: FileDescriptorProtoData]
48+
internal var serviceNames: [String]
49+
50+
internal init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
51+
self.serviceNames = []
52+
self.fileDescriptorDataByFilename = [:]
53+
for fileDescriptorProto in fileDescriptors {
54+
let serializedFileDescriptorProto: Data
55+
do {
56+
serializedFileDescriptorProto = try fileDescriptorProto.serializedData()
57+
} catch {
58+
throw GRPCStatus(
59+
code: .invalidArgument,
60+
message:
61+
"The \(fileDescriptorProto.name) could not be serialized."
62+
)
63+
}
64+
let protoData = FileDescriptorProtoData(
65+
serializedFileDescriptorProto: serializedFileDescriptorProto,
66+
dependencyFileNames: fileDescriptorProto.dependency
67+
)
68+
self.fileDescriptorDataByFilename[fileDescriptorProto.name] = protoData
69+
self.serviceNames.append(contentsOf: fileDescriptorProto.service.map { $0.name })
70+
}
71+
}
72+
73+
internal func serialisedFileDescriptorProtosForDependenciesOfFile(
74+
named fileName: String
75+
) throws -> [Data] {
76+
var toVisit = Deque<String>()
77+
var visited = Set<String>()
78+
var serializedFileDescriptorProtos: [Data] = []
79+
toVisit.append(fileName)
80+
81+
while let currentFileName = toVisit.popFirst() {
82+
if let protoData = self.fileDescriptorDataByFilename[currentFileName] {
83+
toVisit.append(
84+
contentsOf: protoData.dependencyFileNames
85+
.filter { name in
86+
return !visited.contains(name)
87+
}
88+
)
89+
90+
let serializedFileDescriptorProto = protoData.serializedFileDescriptorProto
91+
serializedFileDescriptorProtos.append(serializedFileDescriptorProto)
92+
} else {
93+
throw GRPCStatus(
94+
code: .notFound,
95+
message: "The provided file or a dependency of the provided file could not be found."
96+
)
97+
}
98+
visited.insert(currentFileName)
99+
}
100+
return serializedFileDescriptorProtos
101+
}
102+
}
103+
104+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
105+
internal final class ReflectionServiceProvider: Reflection_ServerReflectionAsyncProvider {
106+
private let protoRegistry: ReflectionServiceData
107+
108+
internal init(fileDescriptorProtos: [Google_Protobuf_FileDescriptorProto]) throws {
109+
self.protoRegistry = try ReflectionServiceData(
110+
fileDescriptors: fileDescriptorProtos
111+
)
112+
}
113+
114+
internal func findFileByFileName(
115+
_ fileName: String,
116+
request: Reflection_ServerReflectionRequest
117+
) throws -> Reflection_ServerReflectionResponse {
118+
return Reflection_ServerReflectionResponse(
119+
request: request,
120+
fileDescriptorResponse: try .with {
121+
$0.fileDescriptorProto = try self.protoRegistry
122+
.serialisedFileDescriptorProtosForDependenciesOfFile(named: fileName)
123+
}
124+
)
125+
}
126+
127+
internal func getServicesNames(
128+
request: Reflection_ServerReflectionRequest
129+
) throws -> Reflection_ServerReflectionResponse {
130+
var listServicesResponse = Reflection_ListServiceResponse()
131+
listServicesResponse.service = self.protoRegistry.serviceNames.map { serviceName in
132+
Reflection_ServiceResponse.with {
133+
$0.name = serviceName
134+
}
135+
}
136+
return Reflection_ServerReflectionResponse(
137+
request: request,
138+
listServicesResponse: listServicesResponse
139+
)
140+
}
141+
142+
internal func serverReflectionInfo(
143+
requestStream: GRPCAsyncRequestStream<Reflection_ServerReflectionRequest>,
144+
responseStream: GRPCAsyncResponseStreamWriter<Reflection_ServerReflectionResponse>,
145+
context: GRPCAsyncServerCallContext
146+
) async throws {
147+
for try await request in requestStream {
148+
switch request.messageRequest {
149+
case let .fileByFilename(fileName):
150+
let response = try self.findFileByFileName(
151+
fileName,
152+
request: request
153+
)
154+
try await responseStream.send(response)
155+
156+
case .listServices:
157+
let response = try self.getServicesNames(request: request)
158+
try await responseStream.send(response)
159+
160+
default:
161+
throw GRPCStatus(code: .unimplemented)
162+
}
163+
}
164+
}
165+
}
166+
167+
extension Reflection_ServerReflectionResponse {
168+
init(
169+
request: Reflection_ServerReflectionRequest,
170+
fileDescriptorResponse: Reflection_FileDescriptorResponse
171+
) {
172+
self = .with {
173+
$0.validHost = request.host
174+
$0.originalRequest = request
175+
$0.fileDescriptorResponse = fileDescriptorResponse
176+
}
177+
}
178+
179+
init(
180+
request: Reflection_ServerReflectionRequest,
181+
listServicesResponse: Reflection_ListServiceResponse
182+
) {
183+
self = .with {
184+
$0.validHost = request.host
185+
$0.originalRequest = request
186+
$0.listServicesResponse = listServicesResponse
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)