TestService.swift 11 KB

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