From c1adc3a38f8f776c24ceeb23fd9fc5dd0b9d9534 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 16 Aug 2024 15:49:13 +0200 Subject: [PATCH] wip --- .../src/smithy4s/http/HttpDiscriminator.scala | 52 ++++++++++++++----- .../http/HttpDiscriminatorMatchResults.scala | 22 ++++++++ .../core/src/smithy4s/http/HttpResponse.scala | 13 ++--- .../smithy4s/http/HttpUnaryClientCodecs.scala | 13 +++-- .../smithy4s/http/UnknownErrorResponse.scala | 5 +- 5 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 modules/core/src/smithy4s/http/HttpDiscriminatorMatchResults.scala diff --git a/modules/core/src/smithy4s/http/HttpDiscriminator.scala b/modules/core/src/smithy4s/http/HttpDiscriminator.scala index 3fc5630ac..a2bde973f 100644 --- a/modules/core/src/smithy4s/http/HttpDiscriminator.scala +++ b/modules/core/src/smithy4s/http/HttpDiscriminator.scala @@ -52,21 +52,45 @@ object HttpDiscriminator { discriminatingHeaderNames: List[String], statusCode: Int, headers: Map[CaseInsensitive, Seq[String]] - ): HttpDiscriminator = { - discriminatingHeaderNames.iterator + ): HttpDiscriminator = fromStatusOrHeaderWithResults( + discriminatingHeaderNames, + statusCode, + headers + ).discriminator + + def fromStatusOrHeaderWithResults( + discriminatingHeaderNames: List[String], + statusCode: Int, + headers: Map[CaseInsensitive, Seq[String]] + ): HttpDiscriminatorMatchResults = { + val (reasons, discriminator) = discriminatingHeaderNames .map(CaseInsensitive(_)) - .map(headers.get) - .collectFirst { case Some(h) => h } - .flatMap(_.headOption) - .map(errorType => - ShapeId - .parse(errorType) - .map(FullId(_)) - .getOrElse(NameOnly(errorType)) - ) - .getOrElse( - StatusCode(statusCode) - ) + .foldLeft((List.empty[String], Option.empty[HttpDiscriminator])) { + (acc, item) => + acc match { + case (info, None) => + val header = headers + .get(item) + .flatMap(_.headOption) + header match { + case Some(errorType) => + ( + info, + Some( + ShapeId + .parse(errorType) + .map(FullId(_)) + .getOrElse(NameOnly(errorType)) + ) + ) + case None => + (info :+ s"Cannot discriminate error by $item header due to missing response header", None) + } + case (reasons, header) => (reasons, header) + } + } + + HttpDiscriminatorMatchResults(reasons, discriminator.getOrElse(StatusCode(statusCode))) } } diff --git a/modules/core/src/smithy4s/http/HttpDiscriminatorMatchResults.scala b/modules/core/src/smithy4s/http/HttpDiscriminatorMatchResults.scala new file mode 100644 index 000000000..9eaef5ca0 --- /dev/null +++ b/modules/core/src/smithy4s/http/HttpDiscriminatorMatchResults.scala @@ -0,0 +1,22 @@ +/* + * Copyright 2021-2024 Disney Streaming + * + * Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://disneystreaming.github.io/TOST-1.0.txt + * + * 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. + */ + +package smithy4s.http + +final case class HttpDiscriminatorMatchResults( + failedToMatch: List[String], + discriminator: HttpDiscriminator +) diff --git a/modules/core/src/smithy4s/http/HttpResponse.scala b/modules/core/src/smithy4s/http/HttpResponse.scala index c134453f6..6c3b6e4f7 100644 --- a/modules/core/src/smithy4s/http/HttpResponse.scala +++ b/modules/core/src/smithy4s/http/HttpResponse.scala @@ -202,7 +202,7 @@ object HttpResponse { toStrict: Body => F[(Body, Blob)] ): Decoder[F, Body, E] = discriminating( - discriminate, + discriminate.andThen(df => MonadThrowLike[F].map(df)(d => (List.empty[String], d))), HttpErrorSelector(maybeErrorSchema, decoderCompiler), toStrict ) @@ -215,11 +215,11 @@ object HttpResponse { private[http] def forErrorAsThrowable[F[_]: MonadThrowLike, Body, E]( maybeErrorSchema: Option[ErrorSchema[E]], decoderCompiler: CachedSchemaCompiler[Decoder[F, Body, *]], - discriminate: HttpResponse[Body] => F[HttpDiscriminator], + discriminate: HttpResponse[Body] => F[HttpDiscriminatorMatchResults], toStrict: Body => F[(Body, Blob)] ): Decoder[F, Body, Throwable] = discriminating( - discriminate, + discriminate.andThen(df => MonadThrowLike[F].map(df)(d => (d.failedToMatch, d.discriminator))), HttpErrorSelector.asThrowable(maybeErrorSchema, decoderCompiler), toStrict ) @@ -229,7 +229,7 @@ object HttpResponse { * to a given decoder, based on some discriminator. */ private def discriminating[F[_], Body, Discriminator, E]( - discriminate: HttpResponse[Body] => F[Discriminator], + discriminate: HttpResponse[Body] => F[(List[String], Discriminator)], select: Discriminator => Option[Decoder[F, Body, E]], toStrict: Body => F[(Body, Blob)] )(implicit F: MonadThrowLike[F]): Decoder[F, Body, E] = @@ -237,7 +237,7 @@ object HttpResponse { def decode(response: HttpResponse[Body]): F[E] = { F.flatMap(toStrict(response.body)) { case (strictBody, bodyBlob) => val strictResponse = response.copy(body = strictBody) - F.flatMap(discriminate(strictResponse)) { discriminator => + F.flatMap(discriminate(strictResponse)) { case (failedToMatch, discriminator) => select(discriminator) match { case Some(decoder) => decoder.decode(strictResponse) case None => @@ -245,7 +245,8 @@ object HttpResponse { smithy4s.http.UnknownErrorResponse( response.statusCode, response.headers, - bodyBlob.toUTF8String + bodyBlob.toUTF8String, + failedToMatch ) ) } diff --git a/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala b/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala index 422ccc9d8..2dc899f7b 100644 --- a/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala +++ b/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala @@ -38,7 +38,7 @@ object HttpUnaryClientCodecs { requestBodyEncoders = BlobEncoder.noop, successResponseBodyDecoders = BlobDecoder.noop, errorResponseBodyDecoders = BlobDecoder.noop, - errorDiscriminator = _ => F.pure(HttpDiscriminator.Undetermined), + errorDiscriminator = _ => F.pure(HttpDiscriminatorMatchResults(List.empty, HttpDiscriminator.Undetermined)), metadataEncoders = None, metadataDecoders = None, rawStringsAndBlobPayloads = false, @@ -56,6 +56,7 @@ object HttpUnaryClientCodecs { def withSuccessBodyDecoders(decoders: BlobDecoder.Compiler): Builder[F, Request, Response] def withErrorBodyDecoders(decoders: BlobDecoder.Compiler): Builder[F, Request, Response] def withErrorDiscriminator(f: HttpResponse[Blob] => F[HttpDiscriminator]): Builder[F, Request, Response] + def withErrorDiscriminator(f: HttpResponse[Blob] => F[HttpDiscriminatorMatchResults]): Builder[F, Request, Response] def withMetadataEncoders(encoders: Metadata.Encoder.Compiler): Builder[F, Request, Response] def withMetadataDecoders(decoders: Metadata.Decoder.Compiler): Builder[F, Request, Response] def withRawStringsAndBlobsPayloads: Builder[F, Request, Response] @@ -73,7 +74,7 @@ object HttpUnaryClientCodecs { requestBodyEncoders: BlobEncoder.Compiler, successResponseBodyDecoders: BlobDecoder.Compiler, errorResponseBodyDecoders: BlobDecoder.Compiler, - errorDiscriminator: HttpResponse[Blob] => F[HttpDiscriminator], + errorDiscriminator: HttpResponse[Blob] => F[HttpDiscriminatorMatchResults], metadataEncoders: Option[Metadata.Encoder.Compiler], metadataDecoders: Option[Metadata.Decoder.Compiler], rawStringsAndBlobPayloads: Boolean, @@ -95,6 +96,12 @@ object HttpUnaryClientCodecs { def withErrorBodyDecoders(decoders: BlobDecoder.Compiler): Builder[F, Request, Response] = copy(errorResponseBodyDecoders = decoders) def withErrorDiscriminator(f: HttpResponse[Blob] => F[HttpDiscriminator]): Builder[F, Request, Response] = + copy(errorDiscriminator = + f.andThen(df => MonadThrowLike[F].map(df)(d => HttpDiscriminatorMatchResults(List.empty, d))) + ) + def withErrorDiscriminator( + f: HttpResponse[Blob] => F[HttpDiscriminatorMatchResults] + ): Builder[F, Request, Response] = copy(errorDiscriminator = f) def withMetadataEncoders(encoders: Metadata.Encoder.Compiler): Builder[F, Request, Response] = copy(metadataEncoders = Some(encoders)) @@ -212,7 +219,7 @@ object HttpUnaryClientCodecs { HttpResponse.Decoder.forErrorAsThrowable( endpoint.error, errorDecoders, - errorDiscriminator, + errorDiscriminator.andThen(df => MonadThrowLike[F].map(df)(_.discriminator)), toStrict ) new UnaryClientCodecs(inputEncoder, errorDecoder.decode, outputDecoder.decode) diff --git a/modules/core/src/smithy4s/http/UnknownErrorResponse.scala b/modules/core/src/smithy4s/http/UnknownErrorResponse.scala index 6f0152798..e2c6aa163 100644 --- a/modules/core/src/smithy4s/http/UnknownErrorResponse.scala +++ b/modules/core/src/smithy4s/http/UnknownErrorResponse.scala @@ -19,8 +19,9 @@ package smithy4s.http case class UnknownErrorResponse( code: Int, headers: Map[CaseInsensitive, Seq[String]], - body: String + body: String, + discriminatorMatchingInfo: List[String] ) extends Throwable { override def getMessage(): String = - s"status $code, headers: $headers, body:\n$body" + s"status $code, headers: $headers, body:\n$body, discriminators: ${discriminatorMatchingInfo.mkString(", ")}" }