1
1
package org .ivovk .connect_rpc_scala .conformance
2
2
3
+ import cats .syntax .all .*
4
+ import cats .effect .std .Dispatcher
3
5
import cats .effect .{IO , IOApp }
4
- import connectrpc .conformance .v1 .{
5
- ClientCompatRequest ,
6
- ClientCompatResponse ,
7
- ConformanceServiceFs2GrpcTrailers ,
8
- }
9
- import org .ivovk .connect_rpc_scala .conformance .util .ProtoSerDeser
6
+ import connectrpc .conformance .v1 as conformance
7
+ import connectrpc .conformance .v1 .*
8
+ import io .grpc .{Metadata , StatusRuntimeException }
9
+ import org .http4s .Uri
10
+ import org .http4s .ember .client .EmberClientBuilder
11
+ import org .ivovk .connect_rpc_scala .conformance .util .{ConformanceHeadersConv , ProtoSerDeser }
12
+ import org .ivovk .connect_rpc_scala .connect .StatusCodeMappings .toConnectCode
13
+ import org .ivovk .connect_rpc_scala .http4s .ConnectHttp4sClientBuilder
14
+ import org .slf4j .LoggerFactory
10
15
11
16
/**
12
17
* Flow:
13
18
*
14
- * - Upon launch, `ServerCompatRequest` message is sent from the test runner to the server to STDIN.
15
- * - Server is started and listens on a random port.
16
- * - `ServerCompatResponse` is sent from the server to STDOUT, which instructs the test runner on which port
17
- * the server is listening.
19
+ * TODO: Describe the flow of the conformance client.
18
20
*
19
21
* All diagnostics should be written to STDERR.
20
22
*
@@ -24,23 +26,117 @@ import org.ivovk.connect_rpc_scala.conformance.util.ProtoSerDeser
24
26
*/
25
27
object Http4sClientLauncher extends IOApp .Simple {
26
28
27
- // private val logger = LoggerFactory.getLogger(getClass)
29
+ private val logger = LoggerFactory .getLogger(getClass)
30
+
31
+ override def run : IO [Unit ] = {
32
+ logger.info(" Starting conformance client tests..." )
33
+
34
+ val res = for
35
+ dispatcher <- Dispatcher .parallel[IO ]
36
+ httpClient <- EmberClientBuilder .default[IO ].build
37
+
38
+ _ <- fs2.Stream
39
+ .unfoldEval(0 ) { _ =>
40
+ ProtoSerDeser [IO ].read[ClientCompatRequest ](System .in).option.map(_.map(_ -> 0 ))
41
+ }
42
+ .evalMap { req =>
43
+ val f = for
44
+ _ <- IO (logger.info(s " Received request: $req" )).toResource
45
+
46
+ baseUri <- IO .fromEither(Uri .fromString(s " http:// ${req.host}: ${req.port}" )).toResource
47
+
48
+ channel <- new ConnectHttp4sClientBuilder (httpClient).build(baseUri)
49
+ stub = ConformanceServiceFs2GrpcTrailers .stub[IO ](dispatcher, channel)
50
+
51
+ resp <- runTest(stub, req).toResource
52
+
53
+ _ <- ProtoSerDeser [IO ].write(System .out, resp).toResource
54
+ yield ()
55
+
56
+ f.use_
57
+ }.compile.resource.drain
58
+ yield logger.info(" Conformance client tests finished." )
59
+
60
+ res
61
+ .use_
62
+ .onError { case e : Throwable =>
63
+ IO (logger.error(" An error occurred during conformance client tests." , e))
64
+ }
65
+ }
66
+
67
+ private def runTest (
68
+ stub : ConformanceServiceFs2GrpcTrailers [IO , Metadata ],
69
+ specReq : ClientCompatRequest ,
70
+ ): IO [ClientCompatResponse ] = {
71
+ logger.info(" >>> Running conformance test: {}" , specReq.testName)
72
+
73
+ require(
74
+ specReq.service.contains(" connectrpc.conformance.v1.ConformanceService" ),
75
+ s " Invalid service name: ${specReq.service}. Expected 'connectrpc.conformance.v1.ConformanceService'. " ,
76
+ )
28
77
29
- override def run : IO [ Unit ] =
30
- fs2. Stream .repeatEval {
31
- val f = for
32
- req <- ProtoSerDeser [ IO ].read[ ClientCompatRequest ]( System .in).toResource
78
+ specReq.method match {
79
+ case Some ( " Unary " ) =>
80
+ val req = UnaryRequest .parseFrom(specReq.requestMessages.head.value.newCodedInput())
81
+ val requestMetadata = ConformanceHeadersConv .toMetadata(specReq.requestHeaders)
33
82
34
- service <- ConformanceServiceFs2GrpcTrailers .bindServiceResource(
35
- ConformanceServiceImpl [IO ]()
36
- )
83
+ logger.info(" Decoded Request: {}" , req)
84
+ logger.info(" Decoded Request metadata: {}" , requestMetadata)
37
85
38
- resp = ClientCompatResponse (req.testName)
86
+ stub.unary(req, requestMetadata)
87
+ .map { (resp, trailers) =>
88
+ logger.info(" <<< Conformance test completed: {}" , specReq.testName)
39
89
40
- _ <- ProtoSerDeser [IO ].write(System .out, resp).toResource
41
- yield ()
90
+ ClientCompatResponse .Result .Response (
91
+ ClientResponseResult (
92
+ responseHeaders = ConformanceHeadersConv .toHeaderSeq(trailers),
93
+ payloads = resp.payload.toSeq,
94
+ responseTrailers = ConformanceHeadersConv .toTrailingHeaderSeq(trailers),
95
+ )
96
+ )
97
+ }
98
+ .handleError {
99
+ case e : StatusRuntimeException =>
100
+ logger.error(" Error during conformance test: {}" , specReq.testName, e)
42
101
43
- f.use_
44
- }.compile.drain
102
+ ClientCompatResponse .Result .Response (
103
+ ClientResponseResult (
104
+ responseHeaders = ConformanceHeadersConv .toHeaderSeq(e.getTrailers),
105
+ error = Some (
106
+ conformance.Error (
107
+ code = conformance.Code .fromValue(e.getStatus.toConnectCode.value),
108
+ message = Some (e.getMessage),
109
+ details = Seq .empty,
110
+ )
111
+ ),
112
+ responseTrailers = ConformanceHeadersConv .toTrailingHeaderSeq(e.getTrailers),
113
+ )
114
+ )
115
+ case e : Throwable =>
116
+ ClientCompatResponse .Result .Error (
117
+ ClientErrorResult (
118
+ message = e.getMessage
119
+ )
120
+ )
121
+ }
122
+ .map(result => ClientCompatResponse (specReq.testName, result))
123
+ case Some (other) =>
124
+ ClientCompatResponse (specReq.testName)
125
+ .withError(
126
+ ClientErrorResult (
127
+ message = s " Unsupported method: $other. Only 'Unary' is supported in this client. "
128
+ )
129
+ )
130
+ .pure[IO ]
131
+ case None =>
132
+ ClientCompatResponse (specReq.testName)
133
+ .withError(
134
+ ClientErrorResult (
135
+ message = s " Method is not specified in the request. "
136
+ )
137
+ )
138
+ .pure[IO ]
139
+ }
140
+ }
45
141
46
142
}
0 commit comments