Skip to content

Commit

Permalink
response envelope
Browse files Browse the repository at this point in the history
  • Loading branch information
salamonpavel committed May 22, 2024
1 parent f98828b commit 461f917
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
package za.co.absa.atum.server.api.controller

import za.co.absa.atum.server.api.exception.ServiceError
import za.co.absa.atum.server.model.{ErrorResponse, GeneralErrorResponse, InternalServerErrorResponse}
import za.co.absa.atum.server.model.ErrorResponse.{ErrorResponse, GeneralErrorResponse, InternalServerErrorResponse}
import za.co.absa.atum.server.model.SuccessResponse.{MultiSuccessResponse, SingleSuccessResponse}
import za.co.absa.fadb.exceptions.StatusException
import zio._

Expand All @@ -40,4 +41,16 @@ trait BaseController {
}

}

protected def mapToSingleSingleSuccessResponse[A](
effect: IO[ErrorResponse, A]
): IO[ErrorResponse, SingleSuccessResponse[A]] = {
effect.map(SingleSuccessResponse(_))
}

protected def mapToSingleMultiSuccessResponse[A](
effect: IO[ErrorResponse, Seq[A]]
): IO[ErrorResponse, MultiSuccessResponse[A]] = {
effect.map(MultiSuccessResponse(_))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
package za.co.absa.atum.server.api.controller

import za.co.absa.atum.model.dto.CheckpointDTO
import za.co.absa.atum.server.model.ErrorResponse
import za.co.absa.atum.server.model.ErrorResponse.ErrorResponse
import za.co.absa.atum.server.model.SuccessResponse.SingleSuccessResponse
import zio.IO
import zio.macros.accessible

@accessible
trait CheckpointController {
def createCheckpoint(checkpointDTO: CheckpointDTO): IO[ErrorResponse, CheckpointDTO]
def createCheckpoint(checkpointDTO: CheckpointDTO): IO[ErrorResponse, SingleSuccessResponse[CheckpointDTO]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@ package za.co.absa.atum.server.api.controller

import za.co.absa.atum.model.dto.CheckpointDTO
import za.co.absa.atum.server.api.service.CheckpointService
import za.co.absa.atum.server.model.ErrorResponse
import za.co.absa.atum.server.model.ErrorResponse.ErrorResponse
import za.co.absa.atum.server.model.SuccessResponse.SingleSuccessResponse
import zio._

class CheckpointControllerImpl(checkpointService: CheckpointService)
extends CheckpointController with BaseController {
class CheckpointControllerImpl(checkpointService: CheckpointService) extends CheckpointController with BaseController {

override def createCheckpoint(checkpointDTO: CheckpointDTO): IO[ErrorResponse, CheckpointDTO] = {
serviceCallWithStatus[Unit, CheckpointDTO](
checkpointService.saveCheckpoint(checkpointDTO),
_ => checkpointDTO
override def createCheckpoint(
checkpointDTO: CheckpointDTO
): IO[ErrorResponse, SingleSuccessResponse[CheckpointDTO]] = {
mapToSingleSingleSuccessResponse(
serviceCallWithStatus[Unit, CheckpointDTO](
checkpointService.saveCheckpoint(checkpointDTO),
_ => checkpointDTO
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@
package za.co.absa.atum.server.api.controller

import za.co.absa.atum.model.dto.{AdditionalDataSubmitDTO, AtumContextDTO, PartitioningSubmitDTO}
import za.co.absa.atum.server.model.ErrorResponse
import za.co.absa.atum.server.model.ErrorResponse.ErrorResponse
import za.co.absa.atum.server.model.SuccessResponse.SingleSuccessResponse
import zio.IO
import zio.macros.accessible

@accessible
trait PartitioningController {
def createPartitioningIfNotExists(partitioningSubmitDTO: PartitioningSubmitDTO): IO[ErrorResponse, AtumContextDTO]
def createOrUpdateAdditionalData(additionalData: AdditionalDataSubmitDTO): IO[ErrorResponse, AdditionalDataSubmitDTO]
def createPartitioningIfNotExists(
partitioningSubmitDTO: PartitioningSubmitDTO
): IO[ErrorResponse, SingleSuccessResponse[AtumContextDTO]]

def createOrUpdateAdditionalData(
additionalData: AdditionalDataSubmitDTO
): IO[ErrorResponse, SingleSuccessResponse[AdditionalDataSubmitDTO]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,47 @@

package za.co.absa.atum.server.api.controller


import za.co.absa.atum.model.dto.{AdditionalDataSubmitDTO, AtumContextDTO, PartitioningSubmitDTO}
import za.co.absa.atum.server.api.exception.ServiceError
import za.co.absa.atum.server.api.service.PartitioningService
import za.co.absa.atum.server.model.{ErrorResponse, InternalServerErrorResponse}
import za.co.absa.atum.server.model.ErrorResponse.{ErrorResponse, InternalServerErrorResponse}
import za.co.absa.atum.server.model.SuccessResponse.SingleSuccessResponse
import zio._

class PartitioningControllerImpl(partitioningService: PartitioningService)
extends PartitioningController with BaseController {
extends PartitioningController
with BaseController {

override def createPartitioningIfNotExists(
partitioningSubmitDTO: PartitioningSubmitDTO
): IO[ErrorResponse, AtumContextDTO] = {
for {
_ <- partitioningService.createPartitioningIfNotExists(partitioningSubmitDTO)
): IO[ErrorResponse, SingleSuccessResponse[AtumContextDTO]] = {
val atumContextDTOEffect = for {
_ <- partitioningService
.createPartitioningIfNotExists(partitioningSubmitDTO)
.mapError(serviceError => InternalServerErrorResponse(serviceError.message))
measures <- partitioningService.getPartitioningMeasures(partitioningSubmitDTO.partitioning)
.mapError {
serviceError: ServiceError => InternalServerErrorResponse(serviceError.message)
}
additionalData <- partitioningService.getPartitioningAdditionalData(partitioningSubmitDTO.partitioning)
.mapError {
serviceError: ServiceError => InternalServerErrorResponse(serviceError.message)
}
measures <- partitioningService
.getPartitioningMeasures(partitioningSubmitDTO.partitioning)
.mapError { serviceError: ServiceError =>
InternalServerErrorResponse(serviceError.message)
}
additionalData <- partitioningService
.getPartitioningAdditionalData(partitioningSubmitDTO.partitioning)
.mapError { serviceError: ServiceError =>
InternalServerErrorResponse(serviceError.message)
}
} yield AtumContextDTO(partitioningSubmitDTO.partitioning, measures.toSet, additionalData)

mapToSingleSingleSuccessResponse(atumContextDTOEffect)
}

override def createOrUpdateAdditionalData(
additionalData: AdditionalDataSubmitDTO
): IO[ErrorResponse, AdditionalDataSubmitDTO] = {
serviceCallWithStatus[Unit, AdditionalDataSubmitDTO](
partitioningService.createOrUpdateAdditionalData(additionalData),
_ => additionalData
): IO[ErrorResponse, SingleSuccessResponse[AdditionalDataSubmitDTO]] = {
mapToSingleSingleSuccessResponse(
serviceCallWithStatus[Unit, AdditionalDataSubmitDTO](
partitioningService.createOrUpdateAdditionalData(additionalData),
_ => additionalData
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import sttp.tapir.json.play.jsonBody
import sttp.tapir.ztapir._
import sttp.tapir.{EndpointOutput, PublicEndpoint}
import za.co.absa.atum.server.Constants.Endpoints.{Api, V1}
import za.co.absa.atum.server.model._
import za.co.absa.atum.server.model.ErrorResponse._

trait BaseEndpoints {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,43 @@
package za.co.absa.atum.server.api.http

import sttp.model.StatusCode
import sttp.tapir.{PublicEndpoint, endpoint}
import sttp.tapir.generic.auto.schemaForCaseClass
import sttp.tapir.json.play.jsonBody
import sttp.tapir.ztapir._
import za.co.absa.atum.model.dto.{AtumContextDTO, CheckpointDTO, PartitioningSubmitDTO, AdditionalDataSubmitDTO}
import sttp.tapir.{PublicEndpoint, endpoint}
import za.co.absa.atum.model.dto.{AdditionalDataSubmitDTO, AtumContextDTO, CheckpointDTO, PartitioningSubmitDTO}
import za.co.absa.atum.server.Constants.Endpoints._
import za.co.absa.atum.server.model.ErrorResponse
import za.co.absa.atum.server.model.ErrorResponse.ErrorResponse
import za.co.absa.atum.server.model.PlayJsonImplicits._
import za.co.absa.atum.server.model.SuccessResponse.SingleSuccessResponse

trait Endpoints extends BaseEndpoints {

protected val createCheckpointEndpoint: PublicEndpoint[CheckpointDTO, ErrorResponse, CheckpointDTO, Any] = {
protected val createCheckpointEndpoint
: PublicEndpoint[CheckpointDTO, ErrorResponse, SingleSuccessResponse[CheckpointDTO], Any] = {
apiV1.post
.in(CreateCheckpoint)
.in(jsonBody[CheckpointDTO])
.out(statusCode(StatusCode.Created))
.out(jsonBody[CheckpointDTO])
.out(jsonBody[SingleSuccessResponse[CheckpointDTO]])
}

protected val createPartitioningEndpoint
: PublicEndpoint[PartitioningSubmitDTO, ErrorResponse, AtumContextDTO, Any] = {
: PublicEndpoint[PartitioningSubmitDTO, ErrorResponse, SingleSuccessResponse[AtumContextDTO], Any] = {
apiV1.post
.in(CreatePartitioning)
.in(jsonBody[PartitioningSubmitDTO])
.out(statusCode(StatusCode.Ok))
.out(jsonBody[AtumContextDTO])
.out(jsonBody[SingleSuccessResponse[AtumContextDTO]])
}

protected val createOrUpdateAdditionalDataEndpoint
: PublicEndpoint[AdditionalDataSubmitDTO, ErrorResponse, AdditionalDataSubmitDTO, Any] = {
: PublicEndpoint[AdditionalDataSubmitDTO, ErrorResponse, SingleSuccessResponse[AdditionalDataSubmitDTO], Any] = {
apiV1.post
.in(CreateOrUpdateAdditionalData)
.in(jsonBody[AdditionalDataSubmitDTO])
.out(statusCode(StatusCode.Ok))
.out(jsonBody[AdditionalDataSubmitDTO])
.out(jsonBody[SingleSuccessResponse[AdditionalDataSubmitDTO]])
}

protected val zioMetricsEndpoint: PublicEndpoint[Unit, Unit, String, Any] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.r
import sttp.tapir.server.interceptor.metrics.MetricsRequestInterceptor
import sttp.tapir.server.model.ValuedEndpointOutput
import sttp.tapir.ztapir.{headers, statusCode}
import za.co.absa.atum.server.model.BadRequestResponse
import za.co.absa.atum.server.model.ErrorResponse.BadRequestResponse
import zio.interop.catz._

trait ServerOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,28 @@ package za.co.absa.atum.server.model

import play.api.libs.json.{Json, Reads, Writes}

sealed trait ErrorResponse {
def message: String
}

object ErrorResponse {

sealed trait ErrorResponse {
def message: String
}

implicit val reads: Reads[ErrorResponse] = Json.reads[ErrorResponse]
implicit val writes: Writes[ErrorResponse] = Json.writes[ErrorResponse]
}

final case class BadRequestResponse(message: String) extends ErrorResponse
final case class BadRequestResponse(message: String) extends ErrorResponse

object BadRequestResponse {
implicit val reads: Reads[BadRequestResponse] = Json.reads[BadRequestResponse]
implicit val writes: Writes[BadRequestResponse] = Json.writes[BadRequestResponse]
}
implicit val readsBadRequestResponse: Reads[BadRequestResponse] = Json.reads[BadRequestResponse]
implicit val writesBadRequestResponse: Writes[BadRequestResponse] = Json.writes[BadRequestResponse]

final case class GeneralErrorResponse(message: String) extends ErrorResponse
final case class GeneralErrorResponse(message: String) extends ErrorResponse

object GeneralErrorResponse {
implicit val reads: Reads[GeneralErrorResponse] = Json.reads[GeneralErrorResponse]
implicit val writes: Writes[GeneralErrorResponse] = Json.writes[GeneralErrorResponse]
}
implicit val readsGeneralErrorResponse: Reads[GeneralErrorResponse] = Json.reads[GeneralErrorResponse]
implicit val writesGeneralErrorResponse: Writes[GeneralErrorResponse] = Json.writes[GeneralErrorResponse]

final case class InternalServerErrorResponse(message: String) extends ErrorResponse

final case class InternalServerErrorResponse(message: String) extends ErrorResponse
implicit val readsInternalServerErrorResponse: Reads[InternalServerErrorResponse] = Json.reads[InternalServerErrorResponse]
implicit val writesInternalServerErrorResponse: Writes[InternalServerErrorResponse] = Json.writes[InternalServerErrorResponse]

object InternalServerErrorResponse {
implicit val reads: Reads[InternalServerErrorResponse] = Json.reads[InternalServerErrorResponse]
implicit val writes: Writes[InternalServerErrorResponse] = Json.writes[InternalServerErrorResponse]
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import play.api.libs.functional.syntax.toFunctionalBuilderOps
import play.api.libs.json._
import za.co.absa.atum.model.dto.MeasureResultDTO.{ResultValueType, TypedValue}
import za.co.absa.atum.model.dto._
import za.co.absa.atum.server.model.SuccessResponse.SingleSuccessResponse

object PlayJsonImplicits {

Expand Down Expand Up @@ -93,4 +94,7 @@ object PlayJsonImplicits {
implicit val readsAtumContextDTO: Reads[AtumContextDTO] = Json.reads[AtumContextDTO]
implicit val writesAtumContextDTO: Writes[AtumContextDTO] = Json.writes[AtumContextDTO]

implicit def readsSingleApiResponse[T: Reads]: Reads[SingleSuccessResponse[T]] = Json.reads[SingleSuccessResponse[T]]
implicit def writesSingleApiResponse[T: Writes]: Writes[SingleSuccessResponse[T]] = Json.writes[SingleSuccessResponse[T]]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2021 ABSA Group Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 za.co.absa.atum.server.model

object SuccessResponse {

sealed trait SuccessResponse

case class SingleSuccessResponse[T](data: T) extends SuccessResponse
case class MultiSuccessResponse[T](data: Seq[T]) extends SuccessResponse

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,19 @@

package za.co.absa.atum.server.api.controller

import org.junit.runner.RunWith
import org.mockito.Mockito.{mock, when}
import za.co.absa.atum.server.api.TestData
import za.co.absa.atum.server.api.exception.ServiceError
import za.co.absa.atum.server.api.service.CheckpointService
import za.co.absa.atum.server.model.{GeneralErrorResponse, InternalServerErrorResponse}
import za.co.absa.atum.server.model.ErrorResponse.{GeneralErrorResponse, InternalServerErrorResponse}
import za.co.absa.atum.server.model.SuccessResponse.SingleSuccessResponse
import za.co.absa.fadb.exceptions.ErrorInDataException
import za.co.absa.fadb.status.FunctionStatus
import zio.test.Assertion.failsWithA
import zio._
import zio.test.Assertion.failsWithA
import zio.test._
import zio.test.junit.ZTestJUnitRunner

@RunWith(classOf[ZTestJUnitRunner])
class CheckpointControllerSpec extends ZIOSpecDefault with TestData {
object CheckpointControllerSpec extends ZIOSpecDefault with TestData {

private val checkpointServiceMock = mock(classOf[CheckpointService])

Expand All @@ -49,7 +47,7 @@ class CheckpointControllerSpec extends ZIOSpecDefault with TestData {
test("Returns expected CheckpointDTO") {
for {
result <- CheckpointController.createCheckpoint(checkpointDTO1)
} yield assertTrue(result == checkpointDTO1)
} yield assertTrue(result == SingleSuccessResponse(checkpointDTO1))
},
test("Returns expected InternalServerErrorResponse") {
assertZIO(CheckpointController.createCheckpoint(checkpointDTO3).exit)(failsWithA[InternalServerErrorResponse])
Expand Down
Loading

0 comments on commit 461f917

Please sign in to comment.