Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding feature to give additional status codes to client in cas… #211

Merged
merged 2 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import scala.concurrent.ExecutionContext

private class GenericAPIClient[Alg[_[_, _, _, _, _]]](
service: Service[Alg],
client: RequestClient
client: RequestClient,
additionalSuccessCodes: List[Int] = List.empty
)(implicit ec: ExecutionContext) {

private val smithyPlayClient = new SmithyPlayClient("/", service, client)
private val smithyPlayClient = new SmithyPlayClient("/", service, client, additionalSuccessCodes)

/* Takes a service and creates a Transformation[Op, ClientRequest] */
private def transformer(): Alg[Kind1[RunnableClientRequest]#toKind5] =
Expand Down Expand Up @@ -45,26 +46,30 @@ private class GenericAPIClient[Alg[_[_, _, _, _, _]]](
object GenericAPIClient {

implicit class EnhancedGenericAPIClient[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](service: Service[Alg]) {

def withClientAndHeaders(
client: RequestClient,
additionalHeaders: Option[Map[String, Seq[String]]]
)(implicit ec: ExecutionContext) = apply(service, additionalHeaders, client)
)(implicit ec: ExecutionContext): Alg[Kind1[ClientResponse]#toKind5] = apply(service, client, additionalHeaders)

def withClient(
client: RequestClient
)(implicit ec: ExecutionContext) = apply(service, client)
)(implicit ec: ExecutionContext): Alg[Kind1[ClientResponse]#toKind5] = apply(service, client)

def toGenericClient(
client: RequestClient,
additionalHeaders: Option[Map[String, Seq[String]]] = None,
additionalSuccessCodes: List[Int] = List.empty
)(implicit ec: ExecutionContext): Alg[Kind1[ClientResponse]#toKind5] =
apply(service, client, additionalHeaders, additionalSuccessCodes)
}

def apply[Alg[_[_, _, _, _, _]]](
serviceI: Service[Alg],
client: RequestClient,
additionalHeaders: Option[Map[String, Seq[String]]] = None,
client: RequestClient
additionalSuccessCodes: List[Int] = List.empty
)(implicit ec: ExecutionContext): Alg[Kind1[ClientResponse]#toKind5] =
new GenericAPIClient(serviceI, client).transformer(additionalHeaders)

def apply[Alg[_[_, _, _, _, _]]](
serviceI: Service[Alg],
client: RequestClient
)(implicit ec: ExecutionContext): Alg[Kind1[RunnableClientRequest]#toKind5] =
new GenericAPIClient(serviceI, client).transformer()
new GenericAPIClient(serviceI, client, additionalSuccessCodes).transformer(additionalHeaders)

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import scala.concurrent.ExecutionContext
class SmithyPlayClient[Alg[_[_, _, _, _, _]], F[_]](
baseUri: String,
val service: smithy4s.Service[Alg],
client: RequestClient
client: RequestClient,
additionalSuccessCodes: List[Int] = List.empty
)(implicit executionContext: ExecutionContext) {

def send[I, E, O, SI, SO](
Expand All @@ -20,7 +21,15 @@ class SmithyPlayClient[Alg[_[_, _, _, _, _]], F[_]](
HttpEndpoint
.cast(endpoint)
.map(httpEndpoint =>
new SmithyPlayClientEndpoint(endpoint, baseUri, additionalHeaders, httpEndpoint, input, client).send()
new SmithyPlayClientEndpoint(
endpoint = endpoint,
baseUri = baseUri,
additionalHeaders = additionalHeaders,
additionalSuccessCodes = additionalSuccessCodes,
httpEndpoint = httpEndpoint,
input = input,
client = client
).send()
)
.toOption
.get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O,
endpoint: Endpoint[Op, I, E, O, SI, SO],
baseUri: String,
additionalHeaders: Option[Map[String, Seq[String]]],
additionalSuccessCodes: List[Int] = List.empty,
httpEndpoint: HttpEndpoint[I],
input: I,
client: RequestClient
Expand Down Expand Up @@ -50,11 +51,12 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O,
for {
res <- response
metadata = Metadata(headers = res.headers.map(headers => (CaseInsensitive(headers._1), headers._2)))
output <- if (res.statusCode == expectedCode) handleSuccess(metadata, res, expectedCode)
else handleError(res, expectedCode)
output <- if ((additionalSuccessCodes :+ expectedCode).contains(res.statusCode))
handleSuccess(metadata, res)
else handleError(res)
} yield output

def handleSuccess(metadata: Metadata, response: SmithyClientResponse, expectedCode: Int) = {
def handleSuccess(metadata: Metadata, response: SmithyClientResponse) = {
val headers = response.headers.map(x => (x._1.toLowerCase, x._2))
val output = outputMetadataDecoder.total match {
case Some(totalDecoder) =>
Expand All @@ -65,23 +67,23 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O,
codecApi = CodecUtils.extractCodec(headers)
bodyPartial <-
codecApi.decodeFromByteArrayPartial(codecApi.compileCodec(outputSchema), response.body.get)
} yield metadataPartial.combine(bodyPartial)
output <- metadataPartial.combineCatch(bodyPartial)
} yield output
}
Future(
output.map(o => SmithyPlayClientEndpointResponse(Some(o), headers, response.statusCode, expectedCode)).left.map {
output.map(o => SmithyPlayClientEndpointResponse(Some(o), headers, response.statusCode)).left.map {
case error: PayloadError =>
SmithyPlayClientEndpointErrorResponse(error.expected.getBytes, response.statusCode, expectedCode)
SmithyPlayClientEndpointErrorResponse(error.expected.getBytes, response.statusCode)
case error: MetadataError =>
SmithyPlayClientEndpointErrorResponse(error.getMessage().getBytes(), response.statusCode, expectedCode)
SmithyPlayClientEndpointErrorResponse(error.getMessage().getBytes(), response.statusCode)
}
)
}
def handleError(response: SmithyClientResponse, expectedCode: Int) = Future(
def handleError(response: SmithyClientResponse) = Future(
Left {
SmithyPlayClientEndpointErrorResponse(
response.body.getOrElse(Array.emptyByteArray),
response.statusCode,
expectedCode
response.statusCode
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ import de.innfactory.smithy4play.Showable

case class SmithyPlayClientEndpointErrorResponse(
error: Array[Byte],
statusCode: Int,
expectedStatusCode: Int
statusCode: Int
) extends Showable
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ import de.innfactory.smithy4play.Showable
case class SmithyPlayClientEndpointResponse[O](
body: Option[O],
headers: Map[String, Seq[String]],
statusCode: Int,
expectedStatusCode: Int
statusCode: Int
) extends Showable
4 changes: 4 additions & 0 deletions smithy4playTest/app/controller/TestController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,8 @@ class TestController @Inject() (implicit
override def testAuth(): ContextRoute[Unit] = Kleisli { rc =>
EitherT.rightT[Future, ContextRouteError](())
}

override def testWithOtherStatusCode(): ContextRoute[Unit] = Kleisli { rc =>
EitherT.rightT[Future, ContextRouteError](())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package controller.middlewares

import de.innfactory.smithy4play.middleware.MiddlewareBase
import de.innfactory.smithy4play.{ EndpointResult, RouteResult, RoutingContext, Status }
import testDefinitions.test.ChangeStatusCode

import javax.inject.Inject
import scala.concurrent.ExecutionContext

class ChangeStatusCodeMiddleware @Inject() (implicit executionContext: ExecutionContext) extends MiddlewareBase {

override protected def skipMiddleware(r: RoutingContext): Boolean =
!r.endpointHints.has(ChangeStatusCode)

override protected def logic(
r: RoutingContext,
next: RoutingContext => RouteResult[EndpointResult]
): RouteResult[EndpointResult] = {
val res = next(r)
res.map { r =>
r.copy(status = Status(r.status.headers, 269))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import javax.inject.Inject
class MiddlewareRegistry @Inject() (
disableAbleMiddleware: DisableAbleMiddleware,
testMiddlewareImpl: TestMiddlewareImpl,
validateAuthMiddleware: ValidateAuthMiddleware
validateAuthMiddleware: ValidateAuthMiddleware,
changeStatusCodeMiddleware: ChangeStatusCodeMiddleware
) extends MiddlewareRegistryBase {
override val middlewares: Seq[MiddlewareBase] = Seq(disableAbleMiddleware, testMiddlewareImpl, validateAuthMiddleware)
override val middlewares: Seq[MiddlewareBase] =
Seq(disableAbleMiddleware, testMiddlewareImpl, validateAuthMiddleware, changeStatusCodeMiddleware)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package controller.middlewares

import de.innfactory.smithy4play.middleware.MiddlewareBase
import de.innfactory.smithy4play.{ EndpointResult, RouteResult, RoutingContext }
import de.innfactory.smithy4play.{ EndpointResult, RouteResult, RoutingContext, Status }

import javax.inject.Inject
import scala.concurrent.ExecutionContext
Expand Down
29 changes: 18 additions & 11 deletions smithy4playTest/test/TestControllerTest.scala
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import controller.models.TestError
import de.innfactory.smithy4play.CodecUtils
import de.innfactory.smithy4play.client.GenericAPIClient.EnhancedGenericAPIClient
import de.innfactory.smithy4play.client.{RequestClient, SmithyClientResponse}
import de.innfactory.smithy4play.client.{ RequestClient, SmithyClientResponse }
import de.innfactory.smithy4play.client.SmithyPlayTestUtils._
import de.innfactory.smithy4play.compliancetests.ComplianceClient
import models.TestJson
import org.scalatestplus.play.{BaseOneAppPerSuite, FakeApplicationFactory, PlaySpec}
import org.scalatestplus.play.{ BaseOneAppPerSuite, FakeApplicationFactory, PlaySpec }
import play.api.Application
import play.api.Play.materializer
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.libs.json.{Json, OWrites}
import play.api.mvc.{AnyContentAsEmpty, Result}
import play.api.libs.json.{ Json, OWrites }
import play.api.mvc.{ AnyContentAsEmpty, Result }
import play.api.test.FakeRequest
import play.api.test.Helpers._
import testDefinitions.test.{SimpleTestResponse, TestControllerServiceGen, TestRequestBody}
import testDefinitions.test.{ SimpleTestResponse, TestControllerServiceGen, TestRequestBody }
import smithy4s.ByteArray

import java.io.File
import java.nio.file.Files
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration.DurationInt

class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeApplicationFactory {

Expand Down Expand Up @@ -52,7 +53,7 @@ class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeAppli
}
}

val genericClient = TestControllerServiceGen.withClientAndHeaders(FakeRequestClient, None)
val genericClient = TestControllerServiceGen.toGenericClient(FakeRequestClient, None, List(269))

override def fakeApplication(): Application =
new GuiceApplicationBuilder().build()
Expand All @@ -61,7 +62,7 @@ class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeAppli

"new autoTest test" in {
new ComplianceClient(genericClient).tests().map { result =>
result.expectedCode mustBe result.receivedCode
200 mustBe result.receivedCode
result.expectedBody mustBe result.receivedBody
}
}
Expand All @@ -75,7 +76,7 @@ class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeAppli

"route to Test Endpoint" in {
val result = genericClient.test().awaitRight
result.statusCode mustBe result.expectedStatusCode
result.statusCode mustBe 200
}

"route to Test Endpoint by SmithyTestClient with Query Parameter, Path Parameter and Body" in {
Expand All @@ -86,7 +87,7 @@ class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeAppli
val result = genericClient.testWithOutput(pathParam, testQuery, testHeader, body).awaitRight

val responseBody = result.body.get
result.statusCode mustBe result.expectedStatusCode
result.statusCode mustBe 200
responseBody.body.testQuery mustBe testQuery
responseBody.body.pathParam mustBe pathParam
responseBody.body.bodyMessage mustBe body.message
Expand Down Expand Up @@ -126,7 +127,7 @@ class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeAppli
val result = genericClient.health().awaitRight

result.headers.contains("endpointresulttest") mustBe true
result.statusCode mustBe result.expectedStatusCode
result.statusCode mustBe 200
}

"route to error Endpoint" in {
Expand All @@ -142,7 +143,7 @@ class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeAppli
val pngAsBytes = ByteArray(Files.readAllBytes(file.toPath))
val result = genericClient.testWithBlob(pngAsBytes, "image/png").awaitRight

result.statusCode mustBe result.expectedStatusCode
result.statusCode mustBe 200
pngAsBytes mustBe result.body.get.body
}

Expand All @@ -152,6 +153,12 @@ class TestControllerTest extends PlaySpec with BaseOneAppPerSuite with FakeAppli
result.statusCode mustBe 401
}

"test with different status code" in {
val result = genericClient.testWithOtherStatusCode().awaitRight

result.statusCode mustBe 269
}

"manual writing json" in {

val writtenData = CodecUtils.writeEntityToJsonBytes(SimpleTestResponse(Some("Test")), SimpleTestResponse.schema)
Expand Down
5 changes: 5 additions & 0 deletions smithy4playTest/testSpecs/Base.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ structure testMiddleware {}
breakingChanges: [{change: "remove"}]
)
structure disableTestMiddleware {}
@trait(
selector: "operation",
breakingChanges: [{change: "remove"}]
)
structure changeStatusCode {}



9 changes: 8 additions & 1 deletion smithy4playTest/testSpecs/TestController.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ use alloy#simpleRestJson
@simpleRestJson
service TestControllerService {
version: "0.0.1",
operations: [Test, TestWithOutput, Health, TestWithBlob, TestWithQuery, TestThatReturnsError, TestAuth]
operations: [Test, TestWithOutput, Health, TestWithBlob, TestWithQuery, TestThatReturnsError, TestAuth, TestWithOtherStatusCode]
}

@auth([])
@readonly
@changeStatusCode
@http(method: "GET", uri: "/other/status/code", code: 200)
operation TestWithOtherStatusCode {
}

@auth([])
Expand Down
Loading