Skip to content

Commit

Permalink
Add marshal/unmarshal exception processing
Browse files Browse the repository at this point in the history
  • Loading branch information
UnknownNPC committed Aug 28, 2019
1 parent 0d41dca commit 9bd1e53
Show file tree
Hide file tree
Showing 17 changed files with 230 additions and 190 deletions.
Expand Up @@ -7,5 +7,9 @@ class InvalidParam(message: String = "Invalid external param", cause: Throwable)
extends APIException(message, cause)

final case
class ExternalAPIException(message: String = "External request error", cause: Throwable)
class ExternalAPICallException(message: String = "External request error", cause: Throwable)
extends APIException(message, cause)

final case
class ExternalAPIPayloadParseException(message: String = "Unable to parse payload", cause: Throwable)
extends APIException(message, cause)
Expand Up @@ -2,8 +2,8 @@ package com.github.unknownnpc.psw.api

trait Serializer[IN, OUT, REQ, RES] {

def toReq(obj: IN): REQ
def toReq(obj: IN): Either[ExternalAPIPayloadParseException, REQ]

def fromRes(out: RES): OUT
def fromRes(out: RES): Either[ExternalAPIPayloadParseException, OUT]

}
10 changes: 10 additions & 0 deletions api/src/main/scala/com/github/unknownnpc/psw/api/Utils.scala
@@ -0,0 +1,10 @@
package com.github.unknownnpc.psw.api

object Utils {

def safeParse[T](fn: => T): Either[ExternalAPIPayloadParseException, T] = {
import scala.util.control.Exception.allCatch
allCatch.either(fn).left.map(e => ExternalAPIPayloadParseException(cause = e))
}

}
@@ -1,20 +1,22 @@
package com.github.unknownnpc.psw.api.action

import com.github.unknownnpc.psw.api.executor.Executor
import com.github.unknownnpc.psw.api.{APIException, ExternalAPIException, Serializer}
import com.github.unknownnpc.psw.api.{APIException, ExternalAPICallException, Serializer}

import scala.util.Try
import scala.util.control.Exception.allCatch

trait ActionContext[IN, OUT, REQ, RES] {
executor: Executor[REQ, RES] =>

def run(in: IN)(implicit ser: Serializer[IN, OUT, REQ, RES]): Either[APIException, OUT] = {
Try(ser.fromRes(
executor.execute(
ser.toReq(in)
)
)).toEither.left
.map(e => ExternalAPIException(cause = e))

for {
request <- ser.toReq(in).right
rawResponse <- allCatch.either(executor.execute(request))
.left.map(e => ExternalAPICallException(cause = e)).right
response <- ser.fromRes(rawResponse).right
} yield response

}

}
@@ -1,6 +1,7 @@
package com.github.unknownnpc.psw.p24.serializer

import com.github.unknownnpc.psw.api.Serializer
import com.github.unknownnpc.psw.api.Utils.safeParse
import com.github.unknownnpc.psw.api.{ExternalAPIPayloadParseException, Serializer}
import com.github.unknownnpc.psw.p24.model._
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.{ContentType, StringEntity}
Expand All @@ -11,52 +12,54 @@ private[serializer] class CardBalanceReqResSerializer extends Serializer[Request

private val urlTarget: String = "https://api.privatbank.ua/p24api/balance"

override def toReq(obj: Request): HttpPost = {
override def toReq(obj: Request): Either[ExternalAPIPayloadParseException, HttpPost] = {
safeParse {
def formHttpPostReq(payload: String): HttpPost = {
val httpPost = new HttpPost(urlTarget)
httpPost.setEntity(new StringEntity(payload, ContentType.APPLICATION_XML))
httpPost
}

def formHttpPostReq(payload: String): HttpPost = {
val httpPost = new HttpPost(urlTarget)
httpPost.setEntity(new StringEntity(payload, ContentType.APPLICATION_XML))
httpPost
formHttpPostReq(
formRequestXmlStr(obj)
)
}

formHttpPostReq(
formRequestXmlStr(obj)
)
}

override def fromRes(out: String): CardBalanceResponse = {
val responseXml = XML.loadString(unPrettyOut(out))

CardBalanceResponse(
Merchant(
(responseXml \ "merchant" \ "id").text.toLong,
Option((responseXml \ "merchant" \ "signature").text)
),
CardBalanceResponseData(
(responseXml \ "data" \ "oper").text,
CardBalanceResponseCardBalance(
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "av_balance").text),
p24ResponseDateTimeFormatter.parse(
(responseXml \ "data" \ "info" \ "cardbalance" \ "bal_date").text
),
(responseXml \ "data" \ "info" \ "cardbalance" \ "bal_dyn").text,
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "balance").text),
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "fin_limit").text),
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "trade_limit").text)
override def fromRes(out: String): Either[ExternalAPIPayloadParseException, CardBalanceResponse] = {
safeParse {
val responseXml = XML.loadString(unPrettyOut(out))

CardBalanceResponse(
Merchant(
(responseXml \ "merchant" \ "id").text.toLong,
Option((responseXml \ "merchant" \ "signature").text)
),
CardBalanceResponseCard(
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "account").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "card_number").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "acc_name").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "acc_type").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "currency").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "card_type").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "main_card_number").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "card_stat").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "src").text
CardBalanceResponseData(
(responseXml \ "data" \ "oper").text,
CardBalanceResponseCardBalance(
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "av_balance").text),
p24ResponseDateTimeFormatter.parse(
(responseXml \ "data" \ "info" \ "cardbalance" \ "bal_date").text
),
(responseXml \ "data" \ "info" \ "cardbalance" \ "bal_dyn").text,
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "balance").text),
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "fin_limit").text),
BigDecimal((responseXml \ "data" \ "info" \ "cardbalance" \ "trade_limit").text)
),
CardBalanceResponseCard(
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "account").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "card_number").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "acc_name").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "acc_type").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "currency").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "card_type").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "main_card_number").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "card_stat").text,
(responseXml \ "data" \ "info" \ "cardbalance" \ "card" \ "src").text
)
)
)
)
}
}

}
@@ -1,6 +1,7 @@
package com.github.unknownnpc.psw.p24.serializer

import com.github.unknownnpc.psw.api.Serializer
import com.github.unknownnpc.psw.api.Utils.safeParse
import com.github.unknownnpc.psw.api.{ExternalAPIPayloadParseException, Serializer}
import com.github.unknownnpc.psw.p24.model._
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.{ContentType, StringEntity}
Expand All @@ -21,48 +22,50 @@ private[serializer] class WalletHistoryReqResSerializer extends Serializer[Reque
* @param walletHistoryReq the request entity.
* @return the p24 request on the xml format.
*/
override def toReq(req: Request): HttpPost = {
override def toReq(req: Request): Either[ExternalAPIPayloadParseException, HttpPost] = {
safeParse {
def formHttpPostReq(payload: String): HttpPost = {
val httpPost = new HttpPost(urlTarget)
httpPost.setEntity(new StringEntity(payload, ContentType.APPLICATION_XML))
httpPost
}

def formHttpPostReq(payload: String): HttpPost = {
val httpPost = new HttpPost(urlTarget)
httpPost.setEntity(new StringEntity(payload, ContentType.APPLICATION_XML))
httpPost
}

val reqString = formRequestXmlStr(req)
val reqString = formRequestXmlStr(req)

formHttpPostReq(reqString)
formHttpPostReq(reqString)
}
}

override def fromRes(out: String): WalletHistoryResponse = {
val responseXml = XML.loadString(unPrettyOut(out))
override def fromRes(out: String): Either[ExternalAPIPayloadParseException, WalletHistoryResponse] = {
safeParse {
val responseXml = XML.loadString(unPrettyOut(out))

WalletHistoryResponse(
Merchant(
(responseXml \ "merchant" \ "id").text.toLong,
Option((responseXml \ "merchant" \ "signature").text)
),
WalletHistoryResponseData(
(responseXml \ "data" \ "oper").text,
WalletHistoryResponseInfo(
(responseXml \ "data" \ "info" \ "statements" \ "@status").text,
(responseXml \ "data" \ "info" \ "statements" \ "@credit").text,
(responseXml \ "data" \ "info" \ "statements" \ "@debet").text,
(responseXml \ "data" \ "info" \ "statements" \ "statement").map(statementXml => {
WalletHistoryResponseStatementEntity(
(statementXml \ "@card").text,
(statementXml \ "@appcode").text,
p24ResponseDateFormatter.parse((statementXml \ "@trandate").text),
(statementXml \ "@amount").text,
(statementXml \ "@cardamount").text,
(statementXml \ "@rest").text,
(statementXml \ "@terminal").text,
(statementXml \ "@description").text
)
}).toList
WalletHistoryResponse(
Merchant(
(responseXml \ "merchant" \ "id").text.toLong,
Option((responseXml \ "merchant" \ "signature").text)
),
WalletHistoryResponseData(
(responseXml \ "data" \ "oper").text,
WalletHistoryResponseInfo(
(responseXml \ "data" \ "info" \ "statements" \ "@status").text,
(responseXml \ "data" \ "info" \ "statements" \ "@credit").text,
(responseXml \ "data" \ "info" \ "statements" \ "@debet").text,
(responseXml \ "data" \ "info" \ "statements" \ "statement").map(statementXml => {
WalletHistoryResponseStatementEntity(
(statementXml \ "@card").text,
(statementXml \ "@appcode").text,
p24ResponseDateFormatter.parse((statementXml \ "@trandate").text),
(statementXml \ "@amount").text,
(statementXml \ "@cardamount").text,
(statementXml \ "@rest").text,
(statementXml \ "@terminal").text,
(statementXml \ "@description").text
)
}).toList
)
)
)
)
}
}

}
Expand Up @@ -24,7 +24,7 @@ class CardBalanceReqResSerializerTest extends FunSpec with Matchers {
)
)

val requestSample = P24Serializer.cardBalanceReqResSerializer.toReq(request)
val requestSample = P24Serializer.cardBalanceReqResSerializer.toReq(request).right.get

requestSample should not be null
requestSample.getAllHeaders.toList shouldBe List()
Expand Down Expand Up @@ -68,7 +68,7 @@ class CardBalanceReqResSerializerTest extends FunSpec with Matchers {
</data>
</response>""".stripMargin

val result = P24Serializer.cardBalanceReqResSerializer.fromRes(responseSample)
val result = P24Serializer.cardBalanceReqResSerializer.fromRes(responseSample).right.get

result should not be null
result.merchant.id shouldBe 75482
Expand Down
Expand Up @@ -37,7 +37,7 @@ class WalletHistoryReqResSerializerTest extends FunSpec with Matchers {
)
)

val requestSample = P24Serializer.walletHistoryReqResSerializer.toReq(request)
val requestSample = P24Serializer.walletHistoryReqResSerializer.toReq(request).right.get

requestSample should not be null
requestSample.getAllHeaders.toList shouldBe List()
Expand Down Expand Up @@ -84,7 +84,7 @@ class WalletHistoryReqResSerializerTest extends FunSpec with Matchers {
| </response>
""".stripMargin

val result = P24Serializer.walletHistoryReqResSerializer.fromRes(responseSample)
val result = P24Serializer.walletHistoryReqResSerializer.fromRes(responseSample).right.get

result should not be null
result.merchant.id shouldBe idVal
Expand Down
Expand Up @@ -51,7 +51,7 @@ private[qiwi] class QiwiAPI(token: String, httpClient: CloseableHttpClient) {
* Requests wallets balance
* https://developer.qiwi.com/ru/qiwi-wallet-personal/#balances_list
*
* @param personId the personId valie, eg: 30501234567
* @param personId the personId value, eg: 30501234567
* @return the entity with response or error
*/
def retrieveAccountBalance(personId: String): Either[APIException, AccountBalanceResponse] = {
Expand Down
@@ -1,29 +1,33 @@
package com.github.unknownnpc.psw.qiwi.serializer

import com.github.unknownnpc.psw.api.Serializer
import com.github.unknownnpc.psw.api.Utils.safeParse
import com.github.unknownnpc.psw.api.{ExternalAPIPayloadParseException, Serializer}
import com.github.unknownnpc.psw.qiwi.model.{AccountBalanceRequest, AccountBalanceResponse}
import org.apache.http.client.methods.HttpGet

private[serializer] class AccountBalanceReqResSerializer extends Serializer[AccountBalanceRequest, AccountBalanceResponse, HttpGet, String] {

private val urlTarget: String = "https://edge.qiwi.com/funding-sources/v2/persons/%s/accounts"

override def toReq(req: AccountBalanceRequest): HttpGet = {
val fullRequestUrl = String.format(urlTarget, req.wallet)
val httpGet = new HttpGet(fullRequestUrl)
httpGet.setHeader("Authorization", "Bearer " + req.apiToken)
httpGet.setHeader("Accept", "application/json")
override def toReq(req: AccountBalanceRequest): Either[ExternalAPIPayloadParseException, HttpGet] = {
safeParse {
val fullRequestUrl = String.format(urlTarget, req.wallet)
val httpGet = new HttpGet(fullRequestUrl)
httpGet.setHeader("Authorization", "Bearer " + req.apiToken)
httpGet.setHeader("Accept", "application/json")

httpGet
httpGet
}
}

override def fromRes(out: String): AccountBalanceResponse = {
override def fromRes(out: String): Either[ExternalAPIPayloadParseException, AccountBalanceResponse] = {
import org.json4s._
import org.json4s.native.Serialization
import org.json4s.native.Serialization.read
implicit val formats = Serialization.formats(NoTypeHints)

read[AccountBalanceResponse](out)
safeParse {
read[AccountBalanceResponse](out)
}
}

}

0 comments on commit 9bd1e53

Please sign in to comment.