@@ -3,16 +3,21 @@ package org.ivovk.connect_rpc_scala.conformance
3
3
import cats .effect .std .Dispatcher
4
4
import cats .effect .{IO , IOApp }
5
5
import cats .syntax .all .*
6
+ import com .google .protobuf .any .Any
7
+ import connectrpc .ErrorDetailsAny
6
8
import connectrpc .conformance .v1 as conformance
7
9
import connectrpc .conformance .v1 .*
8
- import io .grpc .{ Metadata , StatusRuntimeException }
10
+ import io .grpc .Metadata
9
11
import org .http4s .Uri
10
12
import org .http4s .ember .client .EmberClientBuilder
11
13
import org .ivovk .connect_rpc_scala .conformance .util .{ConformanceHeadersConv , ProtoSerDeser }
12
- import org .ivovk .connect_rpc_scala .connect .StatusCodeMappings . toConnectCode
14
+ import org .ivovk .connect_rpc_scala .connect .ErrorHandling
13
15
import org .ivovk .connect_rpc_scala .http4s .ConnectHttp4sClientBuilder
14
16
import org .slf4j .LoggerFactory
15
17
18
+ import java .util .concurrent .TimeUnit
19
+ import scala .concurrent .duration .Duration
20
+
16
21
/**
17
22
* Flow:
18
23
*
@@ -39,11 +44,11 @@ object Http4sClientLauncher extends IOApp.Simple {
39
44
.unfoldEval(0 ) { _ =>
40
45
ProtoSerDeser [IO ].read[ClientCompatRequest ](System .in).option.map(_.map(_ -> 0 ))
41
46
}
42
- .evalMap { req =>
47
+ .evalMap { ( spec : ClientCompatRequest ) =>
43
48
val f = for
44
- _ <- IO (logger.info(s " Received request: $req " )).toResource
49
+ _ <- IO (logger.info(s " Received request: $spec " )).toResource
45
50
46
- baseUri <- IO .fromEither(Uri .fromString(s " http:// ${req .host}: ${req .port}" )).toResource
51
+ baseUri <- IO .fromEither(Uri .fromString(s " http:// ${spec .host}: ${spec .port}" )).toResource
47
52
48
53
channel <- ConnectHttp4sClientBuilder (httpClient)
49
54
.withJsonCodecConfigurator(
@@ -53,10 +58,12 @@ object Http4sClientLauncher extends IOApp.Simple {
53
58
.registerType[conformance.UnaryRequest ]
54
59
.registerType[conformance.IdempotentUnaryRequest ]
55
60
)
61
+ .withRequestTimeout(spec.timeoutMs.map(Duration (_, TimeUnit .MILLISECONDS )))
56
62
.build(baseUri)
63
+
57
64
stub = ConformanceServiceFs2GrpcTrailers .stub[IO ](dispatcher, channel)
58
65
59
- resp <- runTestCase (stub, req ).toResource
66
+ resp <- testClientCompat (stub, spec ).toResource
60
67
61
68
_ <- ProtoSerDeser [IO ].write(System .out, resp).toResource
62
69
yield ()
@@ -72,28 +79,28 @@ object Http4sClientLauncher extends IOApp.Simple {
72
79
}
73
80
}
74
81
75
- private def runTestCase (
82
+ private def testClientCompat (
76
83
stub : ConformanceServiceFs2GrpcTrailers [IO , Metadata ],
77
- specReq : ClientCompatRequest ,
84
+ spec : ClientCompatRequest ,
78
85
): IO [ClientCompatResponse ] = {
79
- logger.info(" >>> Running conformance test: {}" , specReq .testName)
86
+ logger.info(" >>> Running conformance test: {}" , spec .testName)
80
87
81
88
require(
82
- specReq .service.contains(" connectrpc.conformance.v1.ConformanceService" ),
83
- s " Invalid service name: ${specReq .service}. Expected 'connectrpc.conformance.v1.ConformanceService'. " ,
89
+ spec .service.contains(" connectrpc.conformance.v1.ConformanceService" ),
90
+ s " Invalid service name: ${spec .service}. Expected 'connectrpc.conformance.v1.ConformanceService'. " ,
84
91
)
85
92
86
- specReq .method match {
93
+ spec .method match {
87
94
case Some (" Unary" ) =>
88
- val req = specReq .requestMessages.head.unpack[UnaryRequest ]
89
- val requestMetadata = ConformanceHeadersConv .toMetadata(specReq .requestHeaders)
95
+ val req = spec .requestMessages.head.unpack[UnaryRequest ]
96
+ val requestMetadata = ConformanceHeadersConv .toMetadata(spec .requestHeaders)
90
97
91
98
logger.info(" Decoded Request: {}" , req)
92
99
logger.info(" Decoded Request metadata: {}" , requestMetadata)
93
100
94
101
stub.unary(req, requestMetadata)
95
102
.map { (resp, trailers) =>
96
- logger.info(" <<< Conformance test completed: {}" , specReq .testName)
103
+ logger.info(" <<< Conformance test completed: {}" , spec .testName)
97
104
98
105
ClientCompatResponse .Result .Response (
99
106
ClientResponseResult (
@@ -103,41 +110,37 @@ object Http4sClientLauncher extends IOApp.Simple {
103
110
)
104
111
)
105
112
}
106
- .handleError {
107
- case e : StatusRuntimeException =>
108
- logger.error(" Error during conformance test: {}" , specReq.testName, e)
109
-
110
- ClientCompatResponse .Result .Response (
111
- ClientResponseResult (
112
- responseHeaders = ConformanceHeadersConv .toHeaderSeq(e.getTrailers),
113
- error = Some (
114
- conformance.Error (
115
- code = conformance.Code .fromValue(e.getStatus.toConnectCode.value),
116
- message = Some (e.getMessage),
117
- details = Seq .empty,
118
- )
119
- ),
120
- responseTrailers = ConformanceHeadersConv .toTrailingHeaderSeq(e.getTrailers),
121
- )
122
- )
123
- case e : Throwable =>
124
- ClientCompatResponse .Result .Error (
125
- ClientErrorResult (
126
- message = e.getMessage
127
- )
113
+ .handleError { (t : Throwable ) =>
114
+ val errorDetails = ErrorHandling .extractDetails(t)
115
+ logger.info(s " Error during the conformance test: ${spec.testName}. Error: $errorDetails" )
116
+
117
+ ClientCompatResponse .Result .Response (
118
+ ClientResponseResult (
119
+ error = Some (
120
+ conformance.Error (
121
+ code = conformance.Code .fromValue(errorDetails.error.code.value),
122
+ message = errorDetails.error.message,
123
+ details = errorDetails.error.details
124
+ // TODO: simplify
125
+ .map(d => Any (" type.googleapis.com/" + d.`type`, d.value).unpack[ErrorDetailsAny ])
126
+ .map(d => Any (d.`type`, d.value)),
127
+ )
128
+ ),
129
+ responseTrailers = ConformanceHeadersConv .toTrailingHeaderSeq(errorDetails.metadata),
128
130
)
131
+ )
129
132
}
130
- .map(result => ClientCompatResponse (specReq .testName, result))
133
+ .map(result => ClientCompatResponse (spec .testName, result))
131
134
case Some (other) =>
132
- ClientCompatResponse (specReq .testName)
135
+ ClientCompatResponse (spec .testName)
133
136
.withError(
134
137
ClientErrorResult (
135
138
message = s " Unsupported method: $other. Only 'Unary' is supported in this client. "
136
139
)
137
140
)
138
141
.pure[IO ]
139
142
case None =>
140
- ClientCompatResponse (specReq .testName)
143
+ ClientCompatResponse (spec .testName)
141
144
.withError(
142
145
ClientErrorResult (
143
146
message = s " Method is not specified in the request. "
0 commit comments