Browse Source

[Interop] Implemented the Test Service Provider (#1812)

Motivation:

We need the Test Service Provider in order to implement
the interoperability tests.

Modifications:

Created the Provider and implemented methods required by the conformance to the ServiceProtocol from the generated code for the Test Service.

Result:

We will be able to implement a makeInteroperabilityTestServer.
Stefana-Ioana Dranca 1 year ago
parent
commit
8c66acecb7
1 changed files with 191 additions and 0 deletions
  1. 191 0
      Sources/InteroperabilityTests/TestService.swift

+ 191 - 0
Sources/InteroperabilityTests/TestService.swift

@@ -0,0 +1,191 @@
+/*
+ * Copyright 2024, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import GRPCCore
+
+@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
+internal struct TestService: Grpc_Testing_TestService.ServiceProtocol {
+  internal func unimplementedCall(
+    request: ServerRequest.Single<Grpc_Testing_TestService.Method.UnimplementedCall.Input>
+  ) async throws
+    -> ServerResponse.Single<Grpc_Testing_TestService.Method.UnimplementedCall.Output>
+  {
+    throw RPCError(code: .unimplemented, message: "The RPC is not implemented.")
+  }
+
+  /// Server implements `emptyCall` which immediately returns the empty message.
+  internal func emptyCall(
+    request: ServerRequest.Single<Grpc_Testing_TestService.Method.EmptyCall.Input>
+  ) async throws -> ServerResponse.Single<Grpc_Testing_TestService.Method.EmptyCall.Output> {
+    let message = Grpc_Testing_TestService.Method.EmptyCall.Output()
+    return ServerResponse.Single(message: message)
+  }
+
+  /// Server implements `unaryCall` which immediately returns a `SimpleResponse` with a payload
+  /// body of size `SimpleRequest.responseSize` bytes and type as appropriate for the
+  /// `SimpleRequest.responseType`.
+  ///
+  /// If the server does not support the `responseType`, then it should fail the RPC with
+  /// `INVALID_ARGUMENT`.
+  internal func unaryCall(
+    request: ServerRequest.Single<Grpc_Testing_TestService.Method.UnaryCall.Input>
+  ) async throws -> ServerResponse.Single<Grpc_Testing_TestService.Method.UnaryCall.Output> {
+    // If the request has a responseStatus set, the server should return that status.
+    // If the code is an error code, the server will throw an error containing that code
+    // and the message set in the responseStatus.
+    // If the code is `ok`, the server will automatically send back an `ok` status.
+    if request.message.responseStatus.isInitialized {
+      guard let code = Status.Code(rawValue: Int(request.message.responseStatus.code)) else {
+        throw RPCError(code: .invalidArgument, message: "The response status code is invalid.")
+      }
+      let status = Status(
+        code: code,
+        message: request.message.responseStatus.message
+      )
+      if let error = RPCError(status: status) {
+        throw error
+      }
+    }
+
+    if case .UNRECOGNIZED = request.message.responseType {
+      throw RPCError(code: .invalidArgument, message: "The response type is not recognized.")
+    }
+
+    let responseMessage = Grpc_Testing_TestService.Method.UnaryCall.Output.with { response in
+      response.payload = Grpc_Testing_Payload.with { payload in
+        payload.body = Data(repeating: 0, count: Int(request.message.responseSize))
+        payload.type = request.message.responseType
+      }
+    }
+
+    return ServerResponse.Single(message: responseMessage)
+  }
+
+  /// Server gets the default `SimpleRequest` proto as the request. The content of the request is
+  /// ignored. It returns the `SimpleResponse` proto with the payload set to current timestamp.
+  /// The timestamp is an integer representing current time with nanosecond resolution. This
+  /// integer is formated as ASCII decimal in the response. The format is not really important as
+  /// long as the response payload is different for each request. In addition it adds cache control
+  /// headers such that the response can be cached by proxies in the response path. Server should
+  /// be behind a caching proxy for this test to pass. Currently we set the max-age to 60 seconds.
+  internal func cacheableUnaryCall(
+    request: ServerRequest.Single<Grpc_Testing_TestService.Method.CacheableUnaryCall.Input>
+  ) async throws
+    -> ServerResponse.Single<Grpc_Testing_TestService.Method.CacheableUnaryCall.Output>
+  {
+    throw RPCError(code: .unimplemented, message: "The RPC is not implemented.")
+  }
+
+  /// Server implements `streamingOutputCall` by replying, in order, with one
+  /// `StreamingOutputCallResponse` for each `ResponseParameter`s in `StreamingOutputCallRequest`.
+  /// Each `StreamingOutputCallResponse` should have a payload body of size `ResponseParameter.size`
+  /// bytes, as specified by its respective `ResponseParameter`. After sending all responses, it
+  /// closes with OK.
+  internal func streamingOutputCall(
+    request: ServerRequest.Single<
+      Grpc_Testing_TestService.Method.StreamingOutputCall.Input
+    >
+  ) async throws
+    -> ServerResponse.Stream<Grpc_Testing_TestService.Method.StreamingOutputCall.Output>
+  {
+    return ServerResponse.Stream { writer in
+      for responseParameter in request.message.responseParameters {
+        let response = Grpc_Testing_StreamingOutputCallResponse.with { response in
+          response.payload = Grpc_Testing_Payload.with { payload in
+            payload.body = Data(repeating: 0, count: Int(responseParameter.size))
+          }
+        }
+        try await writer.write(response)
+        // We convert the `intervalUs` value from microseconds to nanoseconds.
+        try await Task.sleep(nanoseconds: UInt64(responseParameter.intervalUs) * 1000)
+      }
+      return [:]
+    }
+  }
+
+  /// Server implements `streamingInputCall` which upon half close immediately returns a
+  /// `StreamingInputCallResponse` where `aggregatedPayloadSize` is the sum of all request payload
+  /// bodies received.
+  internal func streamingInputCall(
+    request: ServerRequest.Stream<Grpc_Testing_TestService.Method.StreamingInputCall.Input>
+  ) async throws
+    -> ServerResponse.Single<Grpc_Testing_TestService.Method.StreamingInputCall.Output>
+  {
+    var aggregatedPayloadSize = 0
+
+    for try await message in request.messages {
+      aggregatedPayloadSize += message.payload.body.count
+    }
+
+    let responseMessage = Grpc_Testing_TestService.Method.StreamingInputCall.Output.with {
+      $0.aggregatedPayloadSize = Int32(aggregatedPayloadSize)
+    }
+
+    return ServerResponse.Single(message: responseMessage)
+  }
+
+  /// Server implements `fullDuplexCall` by replying, in order, with one
+  /// `StreamingOutputCallResponse` for each `ResponseParameter`s in each
+  /// `StreamingOutputCallRequest`. Each `StreamingOutputCallResponse` should have a payload body
+  /// of size `ResponseParameter.size` bytes, as specified by its respective `ResponseParameter`s.
+  /// After receiving half close and sending all responses, it closes with OK.
+  internal func fullDuplexCall(
+    request: ServerRequest.Stream<Grpc_Testing_TestService.Method.FullDuplexCall.Input>
+  ) async throws
+    -> ServerResponse.Stream<Grpc_Testing_TestService.Method.FullDuplexCall.Output>
+  {
+    return ServerResponse.Stream { writer in
+      for try await message in request.messages {
+        // If a request message has a responseStatus set, the server should return that status.
+        // If the code is an error code, the server will throw an error containing that code
+        // and the message set in the responseStatus.
+        // If the code is `ok`, the server will automatically send back an `ok` status with the response.
+        if message.responseStatus.isInitialized {
+          guard let code = Status.Code(rawValue: Int(message.responseStatus.code)) else {
+            throw RPCError(code: .invalidArgument, message: "The response status code is invalid.")
+          }
+
+          let status = Status(code: code, message: message.responseStatus.message)
+          if let error = RPCError(status: status) {
+            throw error
+          }
+        }
+
+        for responseParameter in message.responseParameters {
+          let response = Grpc_Testing_StreamingOutputCallResponse.with { response in
+            response.payload = Grpc_Testing_Payload.with {
+              $0.body = Data(count: Int(responseParameter.size))
+            }
+          }
+          try await writer.write(response)
+        }
+      }
+      return [:]
+    }
+  }
+
+  /// This is not implemented as it is not described in the specification.
+  ///
+  /// See: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
+  internal func halfDuplexCall(
+    request: ServerRequest.Stream<Grpc_Testing_TestService.Method.HalfDuplexCall.Input>
+  ) async throws
+    -> ServerResponse.Stream<Grpc_Testing_TestService.Method.HalfDuplexCall.Output>
+  {
+    throw RPCError(code: .unimplemented, message: "The RPC is not implemented.")
+  }
+}