Skip to content

Commit

Permalink
Merge pull request #84 from hmrc/scala-2.13-poc
Browse files Browse the repository at this point in the history
Scala 2.13 proof of concept
  • Loading branch information
mattstephens-hmrc authored May 27, 2022
2 parents 5ebeb70 + a071d5a commit 23def6b
Show file tree
Hide file tree
Showing 100 changed files with 666 additions and 703 deletions.
5 changes: 3 additions & 2 deletions app/definition/ApiDefinition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ case class APIDefinition(name: String,
require(versions.nonEmpty, "at least one version is required")
require(uniqueVersions, "version numbers must be unique")

private def uniqueVersions = {
!versions.map(_.version).groupBy(identity).mapValues(_.size).exists(_._2 > 1)
private def uniqueVersions: Boolean = {
val foundVersions: Seq[String] = versions.map(_.version)
foundVersions.distinct == foundVersions
}
}

Expand Down
4 changes: 2 additions & 2 deletions app/utils/ErrorHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ class ErrorHandler @Inject()(config: Configuration,
case _: JsValidationException => (BAD_REQUEST, BadRequestError, "ServerValidationError")
case e: HttpException => (e.responseCode, BadRequestError, "ServerValidationError")
case e: UpstreamErrorResponse if UpstreamErrorResponse.Upstream4xxResponse.unapply(e).isDefined => (e.reportAs, BadRequestError, "ServerValidationError")
case e: UpstreamErrorResponse if UpstreamErrorResponse.Upstream5xxResponse.unapply(e).isDefined => (e.reportAs, DownstreamError, "ServerInternalError")
case _ => (INTERNAL_SERVER_ERROR, DownstreamError, "ServerInternalError")
case e: UpstreamErrorResponse if UpstreamErrorResponse.Upstream5xxResponse.unapply(e).isDefined => (e.reportAs, InternalError, "ServerInternalError")
case _ => (INTERNAL_SERVER_ERROR, InternalError, "ServerInternalError")
}

auditConnector.sendEvent(
Expand Down
2 changes: 1 addition & 1 deletion app/v1/connectors/AmendDisclosuresConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class AmendDisclosuresConnector @Inject()(val http: HttpClient,
ec: ExecutionContext,
correlationId: String): Future[DownstreamOutcome[Unit]] = {

import v1.connectors.httpparsers.StandardDesHttpParser._
import v1.connectors.httpparsers.StandardDownstreamHttpParser._

val nino = request.nino.nino
val taxYear = request.taxYear
Expand Down
2 changes: 1 addition & 1 deletion app/v1/connectors/CreateMarriageAllowanceConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CreateMarriageAllowanceConnector @Inject()(val http: HttpClient,
ec: ExecutionContext,
correlationId: String): Future[DownstreamOutcome[Unit]] = {

import v1.connectors.httpparsers.StandardDesHttpParser._
import v1.connectors.httpparsers.StandardDownstreamHttpParser._

val nino = request.nino.nino

Expand Down
4 changes: 2 additions & 2 deletions app/v1/connectors/DeleteRetrieveConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class DeleteRetrieveConnector @Inject()(val http: HttpClient,
ifs1Uri: Ifs1Uri[Unit],
correlationId: String): Future[DownstreamOutcome[Unit]] = {

import v1.connectors.httpparsers.StandardDesHttpParser._
import v1.connectors.httpparsers.StandardDownstreamHttpParser._

delete(uri = ifs1Uri)
}
Expand All @@ -43,7 +43,7 @@ class DeleteRetrieveConnector @Inject()(val http: HttpClient,
ifs1Uri: Ifs1Uri[Resp],
correlationId: String): Future[DownstreamOutcome[Resp]] = {

import v1.connectors.httpparsers.StandardDesHttpParser._
import v1.connectors.httpparsers.StandardDownstreamHttpParser._

get(uri = ifs1Uri)
}
Expand Down
6 changes: 3 additions & 3 deletions app/v1/connectors/DownstreamUri.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

package v1.connectors

trait DownstreamUri[Resp] {
sealed trait DownstreamUri[Resp] {
val value: String
}

object DownstreamUri {
case class Ifs1Uri[Resp](value: String) extends DownstreamUri[Resp]
case class Ifs2Uri[Resp](value: String) extends DownstreamUri[Resp]
final case class Ifs1Uri[Resp](value: String) extends DownstreamUri[Resp]
final case class Ifs2Uri[Resp](value: String) extends DownstreamUri[Resp]
}
19 changes: 8 additions & 11 deletions app/v1/connectors/httpparsers/HttpParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import v1.models.errors._
import scala.util.{Success, Try}

trait HttpParser {

private val logger: Logger = Logger(this.getClass)

implicit class KnownJsonResponse(response: HttpResponse) {
Expand All @@ -39,7 +38,6 @@ trait HttpParser {
}

def parseResult[T](json: JsValue)(implicit reads: Reads[T]): Option[T] = json.validate[T] match {

case JsSuccess(value, _) => Some(value)
case JsError(error) =>
logger.warn(s"[KnownJsonResponse][validateJson] Unable to parse JSON: $error")
Expand All @@ -49,23 +47,22 @@ trait HttpParser {

def retrieveCorrelationId(response: HttpResponse): String = response.header("CorrelationId").getOrElse("")

private val multipleErrorReads: Reads[List[DesErrorCode]] = (__ \ "failures").read[List[DesErrorCode]]
private val multipleErrorReads: Reads[List[DownstreamErrorCode]] = (__ \ "failures").read[List[DownstreamErrorCode]]

private val bvrErrorReads: Reads[Seq[DesErrorCode]] = {
implicit val errorIdReads: Reads[DesErrorCode] = (__ \ "id").read[String].map(DesErrorCode(_))
(__ \ "bvrfailureResponseElement" \ "validationRuleFailures").read[Seq[DesErrorCode]]
private val bvrErrorReads: Reads[List[DownstreamErrorCode]] = {
implicit val errorIdReads: Reads[DownstreamErrorCode] = (__ \ "id").read[String].map(DownstreamErrorCode(_))
(__ \ "bvrfailureResponseElement" \ "validationRuleFailures").read[List[DownstreamErrorCode]]
}

def parseErrors(response: HttpResponse): DesError = {
val singleError = response.validateJson[DesErrorCode].map(err => DesErrors(List(err)))
lazy val multipleErrors = response.validateJson(multipleErrorReads).map(errs => DesErrors(errs))
def parseErrors(response: HttpResponse): DownstreamError = {
val singleError = response.validateJson[DownstreamErrorCode].map(err => DownstreamErrors(List(err)))
lazy val multipleErrors = response.validateJson(multipleErrorReads).map(errs => DownstreamErrors(errs))
lazy val bvrErrors = response.validateJson(bvrErrorReads).map(errs => OutboundError(BVRError, Some(errs.map(_.toMtd))))
lazy val unableToParseJsonError = {
logger.warn(s"unable to parse errors from response: ${response.body}")
OutboundError(DownstreamError)
OutboundError(InternalError)
}

singleError orElse multipleErrors orElse bvrErrors getOrElse unableToParseJsonError
}

}
6 changes: 3 additions & 3 deletions app/v1/connectors/httpparsers/MtdIdLookupHttpParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import play.api.http.Status.{FORBIDDEN, OK, UNAUTHORIZED}
import play.api.libs.json._
import uk.gov.hmrc.http.{HttpReads, HttpResponse}
import v1.connectors.MtdIdLookupOutcome
import v1.models.errors.{DownstreamError, InvalidBearerTokenError, NinoFormatError}
import v1.models.errors.{InternalError, InvalidBearerTokenError, NinoFormatError}

object MtdIdLookupHttpParser extends HttpParser {

Expand All @@ -30,11 +30,11 @@ object MtdIdLookupHttpParser extends HttpParser {
response.status match {
case OK => response.validateJson[String](mtdIdJsonReads) match {
case Some(mtdId) => Right(mtdId)
case None => Left(DownstreamError)
case None => Left(InternalError)
}
case FORBIDDEN => Left(NinoFormatError)
case UNAUTHORIZED => Left(InvalidBearerTokenError)
case _ => Left(DownstreamError)
case _ => Left(InternalError)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ import play.api.http.Status._
import play.api.libs.json.Reads
import uk.gov.hmrc.http.{HttpReads, HttpResponse}
import v1.connectors.DownstreamOutcome
import v1.models.errors.{DownstreamError, OutboundError}
import v1.models.errors.{InternalError, OutboundError}
import v1.models.outcomes.ResponseWrapper

object StandardDesHttpParser extends HttpParser {
object StandardDownstreamHttpParser extends HttpParser {

case class SuccessCode(status: Int) extends AnyVal

val logger: Logger = Logger(getClass)

// Return Right[DesResponse[Unit]] as success response has no body - no need to assign it a value
// Return Right[DownstreamOutcome[Unit]] as success response has no body - no need to assign it a value
implicit def readsEmpty(implicit successCode: SuccessCode = SuccessCode(NO_CONTENT)): HttpReads[DownstreamOutcome[Unit]] =
(_: String, url: String, response: HttpResponse) => doRead(url, response) { correlationId =>
Right(ResponseWrapper(correlationId, ()))
Expand All @@ -40,7 +40,7 @@ object StandardDesHttpParser extends HttpParser {
(_: String, url: String, response: HttpResponse) => doRead(url, response) { correlationId =>
response.validateJson[A] match {
case Some(ref) => Right(ResponseWrapper(correlationId, ref))
case None => Left(ResponseWrapper(correlationId, OutboundError(DownstreamError)))
case None => Left(ResponseWrapper(correlationId, OutboundError(InternalError)))
}
}

Expand All @@ -51,19 +51,19 @@ object StandardDesHttpParser extends HttpParser {

if (response.status != successCode.status) {
logger.warn(
"[StandardDesHttpParser][read] - " +
s"Error response received from DES with status: ${response.status} and body\n" +
"[StandardDownstreamHttpParser][read] - " +
s"Error response received from Downstream with status: ${response.status} and body\n" +
s"${response.body} and correlationId: $correlationId when calling $url")
}

response.status match {
case successCode.status =>
logger.info(
"[StandardDesHttpParser][read] - " +
s"Success response received from DES with correlationId: $correlationId when calling $url")
"[StandardDownstreamHttpParser][read] - " +
s"Success response received from Downstream with correlationId: $correlationId when calling $url")
successOutcomeFactory(correlationId)
case BAD_REQUEST | NOT_FOUND | FORBIDDEN | CONFLICT | UNPROCESSABLE_ENTITY => Left(ResponseWrapper(correlationId, parseErrors(response)))
case _ => Left(ResponseWrapper(correlationId, OutboundError(DownstreamError)))
case _ => Left(ResponseWrapper(correlationId, OutboundError(InternalError)))
}
}
}
4 changes: 2 additions & 2 deletions app/v1/connectors/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

package v1

import v1.models.errors.{DesError, MtdError}
import v1.models.errors.{DownstreamError, MtdError}
import v1.models.outcomes.ResponseWrapper

package object connectors {

type MtdIdLookupOutcome = Either[MtdError, String]
type DownstreamOutcome[A] = Either[ResponseWrapper[DesError], ResponseWrapper[A]]
type DownstreamOutcome[A] = Either[ResponseWrapper[DownstreamError], ResponseWrapper[A]]
}
32 changes: 15 additions & 17 deletions app/v1/controllers/AmendDisclosuresController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ class AmendDisclosuresController @Inject()(val authService: EnrolmentsAuthServic
} yield {
logger.info(
s"[${endpointLogContext.controllerName}][${endpointLogContext.endpointName}] - " +
s"Success response received with CorrelationId: ${serviceResponse.correlationId}")
s"Success response received with CorrelationId: ${serviceResponse.correlationId}"
)

val hateoasResponse = amendDisclosuresHateoasBody(appConfig, nino, taxYear)

auditSubmission(
GenericAuditDetail(request.userDetails, Map("nino" -> nino, "taxYear" -> taxYear), Some(request.body),
serviceResponse.correlationId, AuditResponse(httpStatus = OK, response = Right(Some(hateoasResponse)))
)
)
auditSubmission(GenericAuditDetail(
request.userDetails, Map("nino" -> nino, "taxYear" -> taxYear), Some(request.body),
serviceResponse.correlationId, AuditResponse(httpStatus = OK, response = Right(Some(hateoasResponse)))
))

Ok(hateoasResponse)
.withApiHeaders(serviceResponse.correlationId)
Expand All @@ -93,14 +93,14 @@ class AmendDisclosuresController @Inject()(val authService: EnrolmentsAuthServic
val result = errorResult(errorWrapper).withApiHeaders(resCorrelationId)
logger.warn(
s"[${endpointLogContext.controllerName}][${endpointLogContext.endpointName}] - " +
s"Error response received with CorrelationId: $resCorrelationId")

auditSubmission(
GenericAuditDetail(request.userDetails, Map("nino" -> nino, "taxYear" -> taxYear), Some(request.body),
resCorrelationId, AuditResponse(httpStatus = result.header.status, response = Left(errorWrapper.auditErrors))
)
s"Error response received with CorrelationId: $resCorrelationId"
)

auditSubmission(GenericAuditDetail(
request.userDetails, Map("nino" -> nino, "taxYear" -> taxYear), Some(request.body), resCorrelationId,
AuditResponse(httpStatus = result.header.status, response = Left(errorWrapper.auditErrors))
))

result
}.merge
}
Expand All @@ -109,14 +109,12 @@ class AmendDisclosuresController @Inject()(val authService: EnrolmentsAuthServic
(errorWrapper.error: @unchecked) match {
case BadRequestError | NinoFormatError | TaxYearFormatError | RuleTaxYearNotSupportedError |
RuleTaxYearRangeInvalidError | MtdErrorWithCustomMessage(RuleIncorrectOrEmptyBodyError.code) |
MtdErrorWithCustomMessage(SRNFormatError.code) |
MtdErrorWithCustomMessage(TaxYearFormatError.code) |
MtdErrorWithCustomMessage(SRNFormatError.code) | MtdErrorWithCustomMessage(TaxYearFormatError.code) |
MtdErrorWithCustomMessage(RuleTaxYearRangeInvalidError.code) |
MtdErrorWithCustomMessage(RuleVoluntaryClass2ValueInvalidError.code)
=> BadRequest(Json.toJson(errorWrapper))
MtdErrorWithCustomMessage(RuleVoluntaryClass2ValueInvalidError.code) => BadRequest(Json.toJson(errorWrapper))
case RuleVoluntaryClass2CannotBeChangedError => Forbidden(Json.toJson(errorWrapper))
case NotFoundError => NotFound(Json.toJson(errorWrapper))
case DownstreamError => InternalServerError(Json.toJson(errorWrapper))
case InternalError => InternalServerError(Json.toJson(errorWrapper))
}
}

Expand Down
11 changes: 5 additions & 6 deletions app/v1/controllers/AuthorisedController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,21 @@ abstract class AuthorisedController(cc: ControllerComponents)(implicit ec: Execu
def invokeBlockWithAuthCheck[A](mtdId: String, request: Request[A], block: UserRequest[A] => Future[Result])(
implicit headerCarrier: HeaderCarrier): Future[Result] = {
authService.authorised(predicate(mtdId)).flatMap[Result] {
case Right(userDetails) => block(UserRequest(userDetails.copy(mtdId = mtdId), request))
case Right(userDetails) => block(UserRequest(userDetails.copy(mtdId = mtdId), request))
case Left(UnauthorisedError) => Future.successful(Forbidden(Json.toJson(UnauthorisedError)))
case Left(_) => Future.successful(InternalServerError(Json.toJson(DownstreamError)))
case Left(_) => Future.successful(InternalServerError(Json.toJson(InternalError)))
}
}

override def invokeBlock[A](request: Request[A], block: UserRequest[A] => Future[Result]): Future[Result] = {

implicit val headerCarrier: HeaderCarrier = hc(request)

lookupService.lookup(nino).flatMap[Result] {
case Right(mtdId) => invokeBlockWithAuthCheck(mtdId, request, block)
case Left(NinoFormatError) => Future.successful(BadRequest(Json.toJson(NinoFormatError)))
case Right(mtdId) => invokeBlockWithAuthCheck(mtdId, request, block)
case Left(NinoFormatError) => Future.successful(BadRequest(Json.toJson(NinoFormatError)))
case Left(UnauthorisedError) => Future.successful(Forbidden(Json.toJson(UnauthorisedError)))
case Left(InvalidBearerTokenError) => Future.successful(Unauthorized(Json.toJson(InvalidBearerTokenError)))
case Left(_) => Future.successful(InternalServerError(Json.toJson(DownstreamError)))
case Left(_) => Future.successful(InternalServerError(Json.toJson(InternalError)))
}
}
}
Expand Down
1 change: 0 additions & 1 deletion app/v1/controllers/BaseController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ trait BaseController {
self: Logging =>

implicit class Response(result: Result) {

def withApiHeaders(correlationId: String, responseHeaders: (String, String)*): Result = {

val newHeaders: Seq[(String, String)] = responseHeaders ++ Seq(
Expand Down
2 changes: 1 addition & 1 deletion app/v1/controllers/CreateMarriageAllowanceController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class CreateMarriageAllowanceController @Inject()(val authService: EnrolmentsAut
case RuleDeceasedRecipientError |
RuleInvalidRequestError |
RuleActiveMarriageAllowanceClaimError => Forbidden(Json.toJson(errorWrapper))
case DownstreamError => InternalServerError(Json.toJson(errorWrapper))
case InternalError => InternalServerError(Json.toJson(errorWrapper))
}
}

Expand Down
Loading

0 comments on commit 23def6b

Please sign in to comment.