2
0

TestService.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /*
  2. * Copyright 2024, 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. import Foundation
  17. import GRPCCore
  18. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  19. public struct TestService: Grpc_Testing_TestService.ServiceProtocol {
  20. public init() {}
  21. public func unimplementedCall(
  22. request: ServerRequest.Single<Grpc_Testing_TestService.Method.UnimplementedCall.Input>
  23. ) async throws
  24. -> ServerResponse.Single<Grpc_Testing_TestService.Method.UnimplementedCall.Output>
  25. {
  26. throw RPCError(code: .unimplemented, message: "The RPC is not implemented.")
  27. }
  28. /// Server implements `emptyCall` which immediately returns the empty message.
  29. public func emptyCall(
  30. request: ServerRequest.Single<Grpc_Testing_TestService.Method.EmptyCall.Input>
  31. ) async throws -> ServerResponse.Single<Grpc_Testing_TestService.Method.EmptyCall.Output> {
  32. let message = Grpc_Testing_TestService.Method.EmptyCall.Output()
  33. let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata()
  34. return ServerResponse.Single(
  35. message: message,
  36. metadata: initialMetadata,
  37. trailingMetadata: trailingMetadata
  38. )
  39. }
  40. /// Server implements `unaryCall` which immediately returns a `SimpleResponse` with a payload
  41. /// body of size `SimpleRequest.responseSize` bytes and type as appropriate for the
  42. /// `SimpleRequest.responseType`.
  43. ///
  44. /// If the server does not support the `responseType`, then it should fail the RPC with
  45. /// `INVALID_ARGUMENT`.
  46. public func unaryCall(
  47. request: ServerRequest.Single<Grpc_Testing_TestService.Method.UnaryCall.Input>
  48. ) async throws -> ServerResponse.Single<Grpc_Testing_TestService.Method.UnaryCall.Output> {
  49. // We can't validate messages at the wire-encoding layer (i.e. where the compression byte is
  50. // set), so we have to check via the encoding header. Note that it is possible for the header
  51. // to be set and for the message to not be compressed.
  52. let isRequestCompressed =
  53. request.metadata["grpc-encoding"].filter({ $0 != "identity" }).count > 0
  54. if request.message.expectCompressed.value, !isRequestCompressed {
  55. throw RPCError(
  56. code: .invalidArgument,
  57. message: "Expected compressed request, but 'grpc-encoding' was missing"
  58. )
  59. }
  60. // If the request has a responseStatus set, the server should return that status.
  61. // If the code is an error code, the server will throw an error containing that code
  62. // and the message set in the responseStatus.
  63. // If the code is `ok`, the server will automatically send back an `ok` status.
  64. if request.message.responseStatus.isInitialized {
  65. guard let code = Status.Code(rawValue: Int(request.message.responseStatus.code)) else {
  66. throw RPCError(code: .invalidArgument, message: "The response status code is invalid.")
  67. }
  68. let status = Status(
  69. code: code,
  70. message: request.message.responseStatus.message
  71. )
  72. if let error = RPCError(status: status) {
  73. throw error
  74. }
  75. }
  76. if case .UNRECOGNIZED = request.message.responseType {
  77. throw RPCError(code: .invalidArgument, message: "The response type is not recognized.")
  78. }
  79. let responseMessage = Grpc_Testing_TestService.Method.UnaryCall.Output.with { response in
  80. response.payload = Grpc_Testing_Payload.with { payload in
  81. payload.body = Data(repeating: 0, count: Int(request.message.responseSize))
  82. payload.type = request.message.responseType
  83. }
  84. }
  85. let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata()
  86. return ServerResponse.Single(
  87. message: responseMessage,
  88. metadata: initialMetadata,
  89. trailingMetadata: trailingMetadata
  90. )
  91. }
  92. /// Server gets the default `SimpleRequest` proto as the request. The content of the request is
  93. /// ignored. It returns the `SimpleResponse` proto with the payload set to current timestamp.
  94. /// The timestamp is an integer representing current time with nanosecond resolution. This
  95. /// integer is formated as ASCII decimal in the response. The format is not really important as
  96. /// long as the response payload is different for each request. In addition it adds cache control
  97. /// headers such that the response can be cached by proxies in the response path. Server should
  98. /// be behind a caching proxy for this test to pass. Currently we set the max-age to 60 seconds.
  99. public func cacheableUnaryCall(
  100. request: ServerRequest.Single<Grpc_Testing_TestService.Method.CacheableUnaryCall.Input>
  101. ) async throws
  102. -> ServerResponse.Single<Grpc_Testing_TestService.Method.CacheableUnaryCall.Output>
  103. {
  104. throw RPCError(code: .unimplemented, message: "The RPC is not implemented.")
  105. }
  106. /// Server implements `streamingOutputCall` by replying, in order, with one
  107. /// `StreamingOutputCallResponse` for each `ResponseParameter`s in `StreamingOutputCallRequest`.
  108. /// Each `StreamingOutputCallResponse` should have a payload body of size `ResponseParameter.size`
  109. /// bytes, as specified by its respective `ResponseParameter`. After sending all responses, it
  110. /// closes with OK.
  111. public func streamingOutputCall(
  112. request: ServerRequest.Single<
  113. Grpc_Testing_TestService.Method.StreamingOutputCall.Input
  114. >
  115. ) async throws
  116. -> ServerResponse.Stream<Grpc_Testing_TestService.Method.StreamingOutputCall.Output>
  117. {
  118. let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata()
  119. return ServerResponse.Stream(metadata: initialMetadata) { writer in
  120. for responseParameter in request.message.responseParameters {
  121. let response = Grpc_Testing_StreamingOutputCallResponse.with { response in
  122. response.payload = Grpc_Testing_Payload.with { payload in
  123. payload.body = Data(repeating: 0, count: Int(responseParameter.size))
  124. }
  125. }
  126. try await writer.write(response)
  127. // We convert the `intervalUs` value from microseconds to nanoseconds.
  128. try await Task.sleep(nanoseconds: UInt64(responseParameter.intervalUs) * 1000)
  129. }
  130. return trailingMetadata
  131. }
  132. }
  133. /// Server implements `streamingInputCall` which upon half close immediately returns a
  134. /// `StreamingInputCallResponse` where `aggregatedPayloadSize` is the sum of all request payload
  135. /// bodies received.
  136. public func streamingInputCall(
  137. request: ServerRequest.Stream<Grpc_Testing_TestService.Method.StreamingInputCall.Input>
  138. ) async throws
  139. -> ServerResponse.Single<Grpc_Testing_TestService.Method.StreamingInputCall.Output>
  140. {
  141. let isRequestCompressed =
  142. request.metadata["grpc-encoding"].filter({ $0 != "identity" }).count > 0
  143. var aggregatedPayloadSize = 0
  144. for try await message in request.messages {
  145. // We can't validate messages at the wire-encoding layer (i.e. where the compression byte is
  146. // set), so we have to check via the encoding header. Note that it is possible for the header
  147. // to be set and for the message to not be compressed.
  148. if message.expectCompressed.value, !isRequestCompressed {
  149. throw RPCError(
  150. code: .invalidArgument,
  151. message: "Expected compressed request, but 'grpc-encoding' was missing"
  152. )
  153. }
  154. aggregatedPayloadSize += message.payload.body.count
  155. }
  156. let responseMessage = Grpc_Testing_TestService.Method.StreamingInputCall.Output.with {
  157. $0.aggregatedPayloadSize = Int32(aggregatedPayloadSize)
  158. }
  159. let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata()
  160. return ServerResponse.Single(
  161. message: responseMessage,
  162. metadata: initialMetadata,
  163. trailingMetadata: trailingMetadata
  164. )
  165. }
  166. /// Server implements `fullDuplexCall` by replying, in order, with one
  167. /// `StreamingOutputCallResponse` for each `ResponseParameter`s in each
  168. /// `StreamingOutputCallRequest`. Each `StreamingOutputCallResponse` should have a payload body
  169. /// of size `ResponseParameter.size` bytes, as specified by its respective `ResponseParameter`s.
  170. /// After receiving half close and sending all responses, it closes with OK.
  171. public func fullDuplexCall(
  172. request: ServerRequest.Stream<Grpc_Testing_TestService.Method.FullDuplexCall.Input>
  173. ) async throws
  174. -> ServerResponse.Stream<Grpc_Testing_TestService.Method.FullDuplexCall.Output>
  175. {
  176. let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata()
  177. return ServerResponse.Stream(metadata: initialMetadata) { writer in
  178. for try await message in request.messages {
  179. // If a request message has a responseStatus set, the server should return that status.
  180. // If the code is an error code, the server will throw an error containing that code
  181. // and the message set in the responseStatus.
  182. // If the code is `ok`, the server will automatically send back an `ok` status with the response.
  183. if message.responseStatus.isInitialized {
  184. guard let code = Status.Code(rawValue: Int(message.responseStatus.code)) else {
  185. throw RPCError(code: .invalidArgument, message: "The response status code is invalid.")
  186. }
  187. let status = Status(code: code, message: message.responseStatus.message)
  188. if let error = RPCError(status: status) {
  189. throw error
  190. }
  191. }
  192. for responseParameter in message.responseParameters {
  193. let response = Grpc_Testing_StreamingOutputCallResponse.with { response in
  194. response.payload = Grpc_Testing_Payload.with {
  195. $0.body = Data(count: Int(responseParameter.size))
  196. }
  197. }
  198. try await writer.write(response)
  199. }
  200. }
  201. return trailingMetadata
  202. }
  203. }
  204. /// This is not implemented as it is not described in the specification.
  205. ///
  206. /// See: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
  207. public func halfDuplexCall(
  208. request: ServerRequest.Stream<Grpc_Testing_TestService.Method.HalfDuplexCall.Input>
  209. ) async throws
  210. -> ServerResponse.Stream<Grpc_Testing_TestService.Method.HalfDuplexCall.Output>
  211. {
  212. throw RPCError(code: .unimplemented, message: "The RPC is not implemented.")
  213. }
  214. }
  215. extension Metadata {
  216. fileprivate func makeInitialAndTrailingMetadata() -> (Metadata, Metadata) {
  217. var initialMetadata = Metadata()
  218. var trailingMetadata = Metadata()
  219. for value in self[stringValues: "x-grpc-test-echo-initial"] {
  220. initialMetadata.addString(value, forKey: "x-grpc-test-echo-initial")
  221. }
  222. for value in self[binaryValues: "x-grpc-test-echo-trailing-bin"] {
  223. trailingMetadata.addBinary(value, forKey: "x-grpc-test-echo-trailing-bin")
  224. }
  225. return (initialMetadata, trailingMetadata)
  226. }
  227. }