Skip to content

Commit df6da28

Browse files
authored
Merge pull request #510 from uptane/api-gw
improve RemoteServiceError handling
2 parents d903aab + dc1692d commit df6da28

3 files changed

Lines changed: 46 additions & 9 deletions

File tree

libats-http/src/main/scala/com/advancedtelematic/libats/http/ErrorHandler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ object Errors {
5757
with NoStackTrace
5858

5959
case class RemoteServiceError(msg: String,
60-
status: StatusCode,
60+
response: HttpResponse,
6161
description: Json = Json.Null,
6262
causeCode: ErrorCode =
6363
ErrorCodes.RemoteServiceError,

libats-http/src/main/scala/com/advancedtelematic/libats/http/ServiceHttpClient.scala

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
package com.advancedtelematic.libats.http
22

33
import java.util.UUID
4-
54
import akka.actor.ActorSystem
65
import akka.http.scaladsl.Http
7-
import akka.http.scaladsl.model._
6+
import akka.http.scaladsl.model.*
87
import akka.http.scaladsl.model.headers.RawHeader
98
import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
109
import akka.http.scaladsl.util.FastFuture
1110
import akka.stream.Materializer
12-
import cats.syntax.either._
13-
import cats.syntax.option._
11+
import akka.util.ByteString
12+
import cats.syntax.either.*
13+
import cats.syntax.option.*
1414
import com.advancedtelematic.libats.data.DataType.Namespace
1515
import com.advancedtelematic.libats.data.ErrorRepresentation
1616
import com.advancedtelematic.libats.http.Errors.RemoteServiceError
1717
import com.advancedtelematic.libats.http.ServiceHttpClient.{ServiceHttpFullResponse, ServiceHttpFullResponseEither}
18-
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
18+
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport.*
1919
import io.circe.{Encoder, Json}
2020
import org.slf4j.LoggerFactory
2121

@@ -71,14 +71,24 @@ abstract class ServiceHttpClient(_httpClient: HttpRequest => Future[HttpResponse
7171
execHttpUnmarshalled(req)
7272
}
7373

74+
protected def execJsonHttpWithNamespace[Res : ClassTag : FromEntityUnmarshaller, Req : Encoder]
75+
(ns: Namespace, request: HttpRequest, entity: Req): Future[Either[RemoteServiceError, Res]] = {
76+
val req = request.addHeader(RawHeader("x-ats-namespace", ns.get))
77+
execJsonHttp(req, entity)
78+
}
79+
80+
// The entity is replaced into `response` so that it can be read again by api users, for example through `handleErrors`
7481
private def tryErrorParsing(response: HttpResponse)(implicit um: FromEntityUnmarshaller[ErrorRepresentation]): Future[RemoteServiceError] = {
7582
um(response.entity).map { rawError =>
76-
RemoteServiceError(s"${rawError.description}", response.status, rawError.cause.getOrElse(Json.Null),
83+
val entity = HttpEntity.Strict(response.entity.contentType, ByteString(rawError.asJson.noSpaces))
84+
RemoteServiceError(s"${rawError.description}", response.withEntity(entity), rawError.cause.getOrElse(Json.Null),
7785
rawError.code, rawError.some, rawError.errorId.getOrElse(UUID.randomUUID()))
7886
}.recoverWith { case _ =>
79-
Unmarshaller.stringUnmarshaller(response.entity).map(msg => RemoteServiceError(msg, response.status))
87+
Unmarshaller.stringUnmarshaller(response.entity).map { str =>
88+
RemoteServiceError(str, response.withEntity(HttpEntity.Strict(response.entity.contentType, ByteString(str))))
89+
}
8090
}.recover { case _ =>
81-
RemoteServiceError(s"Unknown error: $response", response.status)
91+
RemoteServiceError(s"Unknown error: $response", response)
8292
}
8393
}
8494

libats/src/main/scala/com/advancedtelematic/libats/data/PaginationResult.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package com.advancedtelematic.libats.data
22

3+
import cats.{Applicative, Eval, Functor, Traverse}
4+
import cats.syntax.traverse.*
5+
import cats.syntax.foldable.*
6+
import cats.syntax.functor.*
7+
38
final case class PaginationResult[A](values: Seq[A], total: Long, offset: Long, limit: Long) {
49
def map[B](f: A => B): PaginationResult[B] = this.copy(values = values.map(f))
510
}
@@ -10,4 +15,26 @@ object PaginationResult {
1015

1116
implicit def paginationResultEncoder[T : Encoder]: Encoder[PaginationResult[T]] = deriveEncoder
1217
implicit def paginationResultDecoder[T : Decoder]: Decoder[PaginationResult[T]] = deriveDecoder
18+
19+
implicit val functor: Functor[PaginationResult] = new Functor[PaginationResult] {
20+
override def map[A, B](fa: PaginationResult[A])(f: A => B): PaginationResult[B] =
21+
fa.map(f)
22+
}
23+
24+
implicit val traverse: Traverse[PaginationResult] = new Traverse[PaginationResult] {
25+
override def traverse[G[_]: Applicative, A, B](fa: PaginationResult[A])(
26+
f: A => G[B]): G[PaginationResult[B]] = {
27+
import cats.syntax.traverse.*
28+
fa.values
29+
.traverse(f)
30+
.map(transformed => PaginationResult(transformed, fa.total, fa.offset, fa.limit))
31+
}
32+
33+
override def foldLeft[A, B](fa: PaginationResult[A], b: B)(f: (B, A) => B): B =
34+
fa.values.foldl(b)(f)
35+
36+
override def foldRight[A, B](fa: PaginationResult[A], lb: Eval[B])(
37+
f: (A, Eval[B]) => Eval[B]): Eval[B] =
38+
fa.values.foldr(lb)(f)
39+
}
1340
}

0 commit comments

Comments
 (0)