Skip to content

Commit

Permalink
feat: ATL-6833 integrate ZIO failures and defects in wallet event con…
Browse files Browse the repository at this point in the history
…troller (#1186)

Signed-off-by: Benjamin Voiturier <benjamin.voiturier@iohk.io>
  • Loading branch information
bvoiturier authored Jun 14, 2024
1 parent 628f2f0 commit 8bc2018
Show file tree
Hide file tree
Showing 21 changed files with 195 additions and 221 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,8 @@ object AgentInitialization {
walletService <- ZIO.service[WalletManagementService]
isDefaultWalletEnabled = config.enabled
isDefaultWalletExist <- walletService
.getWallet(defaultWalletId)
.findWallet(defaultWalletId)
.map(_.isDefined)
.mapError(_.toThrowable)
_ <- ZIO.logInfo(s"Default wallet not enabled.").when(!isDefaultWalletEnabled)
_ <- ZIO.logInfo(s"Default wallet already exist.").when(isDefaultWalletExist)
_ <- createDefaultWallet.when(isDefaultWalletEnabled && !isDefaultWalletExist)
Expand All @@ -213,14 +212,14 @@ object AgentInitialization {
_ <- ZIO.logInfo(s"Default wallet seed is not provided. New seed will be generated.").when(seed.isEmpty)
_ <- walletService
.createWallet(defaultWallet, seed)
.mapError(_.toThrowable)
.orDieAsUnmanagedFailure
_ <- entityService.create(defaultEntity).mapError(e => Exception(e.message))
_ <- apiKeyAuth.add(defaultEntity.id, config.authApiKey).mapError(e => Exception(e.message))
_ <- config.webhookUrl.fold(ZIO.unit) { url =>
val customHeaders = config.webhookApiKey.fold(Map.empty)(apiKey => Map("Authorization" -> s"Bearer $apiKey"))
walletService
.createWalletNotification(EventNotificationConfig(defaultWalletId, url, customHeaders))
.mapError(_.toThrowable)
.orDieAsUnmanagedFailure
.provide(ZLayer.succeed(WalletAccessContext(defaultWalletId)))
}
} yield ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object EndpointOutputs {
def basicFailuresWith(extraFailures: OneOfVariant[ErrorResponse]*) = {
oneOf(
FailureVariant.badRequest,
(FailureVariant.internalServerError +: extraFailures)*
(FailureVariant.internalServerError +: FailureVariant.unprocessableEntity +: extraFailures)*
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.hyperledger.identus.event.controller

import org.hyperledger.identus.agent.walletapi.service.{WalletManagementService, WalletManagementServiceError}
import org.hyperledger.identus.agent.walletapi.service.WalletManagementService
import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext}
import org.hyperledger.identus.api.http.model.{CollectionStats, PaginationInput}
import org.hyperledger.identus.api.util.PaginationUtils
Expand All @@ -10,7 +10,6 @@ import org.hyperledger.identus.event.controller.http.{
WebhookNotificationPage
}
import org.hyperledger.identus.event.notification.EventNotificationConfig
import org.hyperledger.identus.iam.wallet.http.controller.WalletManagementController
import org.hyperledger.identus.shared.models.WalletAccessContext
import zio.*

Expand All @@ -30,15 +29,8 @@ trait EventController {
def deleteWebhookNotification(id: UUID)(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, Unit]
}

object EventController {
given Conversion[WalletManagementServiceError, ErrorResponse] =
WalletManagementController.walletServiceErrorConversion
}

class EventControllerImpl(service: WalletManagementService) extends EventController {

import EventController.given

override def createWebhookNotification(
request: CreateWebhookNotification
)(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, WebhookNotification] = {
Expand All @@ -61,7 +53,7 @@ class EventControllerImpl(service: WalletManagementService) extends EventControl
// Return paginated result for consistency and to make it future-proof
val pagination = PaginationInput().toPagination
for {
items <- service.listWalletNotifications.mapError[ErrorResponse](e => e)
items <- service.listWalletNotifications
totalCount = items.length
stats = CollectionStats(totalCount = totalCount, filteredCount = totalCount)
} yield WebhookNotificationPage(
Expand All @@ -75,11 +67,8 @@ class EventControllerImpl(service: WalletManagementService) extends EventControl

override def deleteWebhookNotification(
id: UUID
)(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, Unit] = {
service
.deleteWalletNotification(id)
.mapError[ErrorResponse](e => e)
}
)(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, Unit] =
service.deleteWalletNotification(id)

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ case class ApiKeyAuthenticatorImpl(
for {
wallet <- walletManagementService
.createWallet(Wallet("Auto provisioned wallet", WalletId.random))
.mapError(cause => AuthenticationRepositoryError.UnexpectedError(cause))
.orDieAsUnmanagedFailure
.provide(ZLayer.succeed(WalletAdministrationContext.Admin()))
entityToCreate = Entity(name = "Auto provisioned entity", walletId = wallet.id.toUUID)
entity <- entityService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ case class KeycloakPermissionManagementService(
): ZIO[WalletAdministrationContext, PermissionManagement.Error, Unit] = {
for {
_ <- walletManagementService
.getWallet(walletId)
.mapError(wmse => ServiceError(wmse.toThrowable.getMessage))
.findWallet(walletId)
.someOrFail(WalletNotFoundById(walletId))

walletResourceOpt <- findWalletResource(walletId)
Expand Down Expand Up @@ -114,8 +113,7 @@ case class KeycloakPermissionManagementService(
val userId = entity.id
for {
_ <- walletManagementService
.getWallet(walletId)
.mapError(wmse => ServiceError(wmse.toThrowable.getMessage))
.findWallet(walletId)
.someOrFail(WalletNotFoundById(walletId))

walletResource <- findWalletResource(walletId)
Expand Down Expand Up @@ -185,7 +183,6 @@ case class KeycloakPermissionManagementService(
val walletIds = resourceIds.flatMap(id => Try(UUID.fromString(id)).toOption).map(WalletId.fromUUID)
walletManagementService
.getWallets(walletIds)
.mapError(e => Error.UnexpectedError(e.toThrowable))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.hyperledger.identus.iam.wallet.http

import org.hyperledger.identus.api.http.{EndpointOutputs, ErrorResponse, RequestContext}
import org.hyperledger.identus.api.http.model.PaginationInput
import org.hyperledger.identus.api.http.EndpointOutputs.FailureVariant
import org.hyperledger.identus.iam.authentication.admin.AdminApiKeyCredentials
import org.hyperledger.identus.iam.authentication.admin.AdminApiKeySecurityLogic.adminApiKeyHeader
import org.hyperledger.identus.iam.authentication.apikey.ApiKeyCredentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,6 @@ trait WalletManagementController {
}

object WalletManagementController {
given walletServiceErrorConversion: Conversion[WalletManagementServiceError, ErrorResponse] = {
case WalletManagementServiceError.UnexpectedStorageError(cause) =>
ErrorResponse.internalServerError(detail = Some(cause.getMessage()))
case WalletManagementServiceError.TooManyWebhookError(limit, actual) =>
ErrorResponse.conflict(detail = Some(s"Too many webhook created for a wallet. (limit $limit, actual $actual)"))
case WalletManagementServiceError.DuplicatedWalletId(id) =>
ErrorResponse.badRequest(s"Wallet id $id is not unique.")
case WalletManagementServiceError.DuplicatedWalletSeed(id) =>
ErrorResponse.badRequest(s"Wallet id $id cannot be created. The seed value is not unique.")
case TooManyPermittedWallet() =>
ErrorResponse.badRequest(
s"The operation is not allowed because wallet access already exists for the current user."
)
}

given permissionManagementErrorConversion: Conversion[PermissionManagement.Error, ErrorResponse] = {
case e: PermissionManagement.Error.PermissionNotFoundById => ErrorResponse.badRequest(detail = Some(e.message))
case e: PermissionManagement.Error.ServiceError => ErrorResponse.internalServerError(detail = Some(e.message))
Expand Down Expand Up @@ -85,7 +70,6 @@ class WalletManagementControllerImpl(
for {
pageResult <- walletService
.listWallets(offset = paginationInput.offset, limit = paginationInput.limit)
.mapError[ErrorResponse](e => e)
(items, totalCount) = pageResult
stats = CollectionStats(totalCount = totalCount, filteredCount = totalCount)
} yield WalletDetailPage(
Expand All @@ -102,8 +86,7 @@ class WalletManagementControllerImpl(
)(implicit rc: RequestContext): ZIO[WalletAdministrationContext, ErrorResponse, WalletDetail] = {
for {
wallet <- walletService
.getWallet(WalletId.fromUUID(walletId))
.mapError[ErrorResponse](e => e)
.findWallet(WalletId.fromUUID(walletId))
.someOrFail(ErrorResponse.notFound(detail = Some(s"Wallet id $walletId does not exist.")))
} yield wallet
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import zio.*

class WalletSecretStorageInMemory(storeRef: Ref[Map[WalletId, WalletSeed]]) extends WalletSecretStorage {

override def setWalletSeed(seed: WalletSeed): RIO[WalletAccessContext, Unit] = {
override def setWalletSeed(seed: WalletSeed): URIO[WalletAccessContext, Unit] = {
for {
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
_ <- storeRef.update(_.updated(walletId, seed))
} yield ()
}

override def getWalletSeed: RIO[WalletAccessContext, Option[WalletSeed]] = {
override def getWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]] = {
for {
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
seed <- storeRef.get.map(_.get(walletId))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,65 +1,71 @@
package org.hyperledger.identus.agent.walletapi.service

import org.hyperledger.identus.agent.walletapi.model.{Wallet, WalletSeed}
import org.hyperledger.identus.agent.walletapi.storage.WalletNonSecretStorageError
import org.hyperledger.identus.agent.walletapi.service.WalletManagementServiceError.{
DuplicatedWalletId,
DuplicatedWalletSeed,
TooManyPermittedWallet,
TooManyWebhookError
}
import org.hyperledger.identus.event.notification.EventNotificationConfig
import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext, WalletId}
import org.hyperledger.identus.shared.models.*
import zio.*

import java.util.UUID
import scala.language.implicitConversions

sealed trait WalletManagementServiceError {
final def toThrowable: Throwable = this
sealed trait WalletManagementServiceError(
val statusCode: StatusCode,
val userFacingMessage: String
) extends Failure {
override val namespace: String = "WalletManagementServiceError"
}

object WalletManagementServiceError {
final case class UnexpectedStorageError(cause: Throwable) extends WalletManagementServiceError
final case class TooManyWebhookError(limit: Int, actual: Int) extends WalletManagementServiceError
final case class DuplicatedWalletId(id: WalletId) extends WalletManagementServiceError
final case class DuplicatedWalletSeed(id: WalletId) extends WalletManagementServiceError
final case class TooManyPermittedWallet() extends WalletManagementServiceError

given Conversion[WalletNonSecretStorageError, WalletManagementServiceError] = {
case WalletNonSecretStorageError.TooManyWebhook(limit, actual) => TooManyWebhookError(limit, actual)
case WalletNonSecretStorageError.DuplicatedWalletId(id) => DuplicatedWalletId(id)
case WalletNonSecretStorageError.DuplicatedWalletSeed(id) => DuplicatedWalletSeed(id)
case WalletNonSecretStorageError.UnexpectedError(cause) => UnexpectedStorageError(cause)
}

given Conversion[WalletManagementServiceError, Throwable] = {
case UnexpectedStorageError(cause) => Exception(cause)
case TooManyWebhookError(limit, actual) =>
Exception(s"Too many webhook created for a wallet. Limit $limit, Actual $actual.")
case DuplicatedWalletId(id) => Exception(s"Duplicated wallet id: $id")
case DuplicatedWalletSeed(id) => Exception(s"Duplicated wallet seed for wallet id: $id")
case TooManyPermittedWallet() =>
Exception(s"The operation is not allowed because wallet access already exists for the current user.")
}
final case class TooManyWebhookError(walletId: WalletId, limit: Int)
extends WalletManagementServiceError(
StatusCode.UnprocessableContent,
s"The maximum number of webhooks has been reached for the wallet: walletId=$walletId, limit=$limit"
)

final case class DuplicatedWalletId(walletId: WalletId)
extends WalletManagementServiceError(
StatusCode.UnprocessableContent,
s"A wallet with the same ID already exist: walletId=$walletId"
)
final case class DuplicatedWalletSeed()
extends WalletManagementServiceError(
StatusCode.UnprocessableContent,
s"A wallet with the same seed already exist"
)
final case class TooManyPermittedWallet()
extends WalletManagementServiceError(
StatusCode.BadRequest,
s"The operation is not allowed because wallet access already exists for the current user"
)
}

trait WalletManagementService {
def createWallet(
wallet: Wallet,
seed: Option[WalletSeed] = None
): ZIO[WalletAdministrationContext, WalletManagementServiceError, Wallet]
): ZIO[WalletAdministrationContext, TooManyPermittedWallet | DuplicatedWalletId | DuplicatedWalletSeed, Wallet]

def getWallet(walletId: WalletId): ZIO[WalletAdministrationContext, WalletManagementServiceError, Option[Wallet]]
def findWallet(walletId: WalletId): URIO[WalletAdministrationContext, Option[Wallet]]

def getWallets(walletIds: Seq[WalletId]): ZIO[WalletAdministrationContext, WalletManagementServiceError, Seq[Wallet]]
def getWallets(walletIds: Seq[WalletId]): URIO[WalletAdministrationContext, Seq[Wallet]]

/** @return A tuple containing a list of items and a count of total items */
def listWallets(
offset: Option[Int] = None,
limit: Option[Int] = None
): ZIO[WalletAdministrationContext, WalletManagementServiceError, (Seq[Wallet], Int)]
): URIO[WalletAdministrationContext, (Seq[Wallet], Int)]

def listWalletNotifications: ZIO[WalletAccessContext, WalletManagementServiceError, Seq[EventNotificationConfig]]
def listWalletNotifications: URIO[WalletAccessContext, Seq[EventNotificationConfig]]

def createWalletNotification(
config: EventNotificationConfig
): ZIO[WalletAccessContext, WalletManagementServiceError, EventNotificationConfig]
): ZIO[WalletAccessContext, TooManyWebhookError, Unit]

def deleteWalletNotification(id: UUID): ZIO[WalletAccessContext, WalletManagementServiceError, Unit]
def deleteWalletNotification(id: UUID): URIO[WalletAccessContext, Unit]
}
Loading

0 comments on commit 8bc2018

Please sign in to comment.