Skip to content

Commit

Permalink
feat: improve ZIO failures and defects in credential definition (#1133)
Browse files Browse the repository at this point in the history
Signed-off-by: FabioPinheiro <fabiomgpinheiro@gmail.com>
  • Loading branch information
FabioPinheiro committed Jun 6, 2024
1 parent d8f4bbd commit d6dfb72
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import org.hyperledger.identus.pollux.sdjwt.SDJWT.*
import org.hyperledger.identus.pollux.vc.jwt.{DIDResolutionFailed, DIDResolutionSucceeded, ES256KSigner, EdSigner, *}
import org.hyperledger.identus.pollux.vc.jwt.{DidResolver as JwtDidResolver, Issuer as JwtIssuer}
import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey, KmpEd25519KeyOps}
import org.hyperledger.identus.shared.crypto.KmpEd25519KeyOps
import org.hyperledger.identus.shared.crypto.Secp256k1KeyPair
import org.hyperledger.identus.shared.crypto.X25519KeyPair
import org.hyperledger.identus.shared.models.WalletAccessContext
import zio.{ZIO, ZLayer}

Expand All @@ -22,7 +25,7 @@ trait BackgroundJobsHelper {
def getLongForm(
did: PrismDID,
allowUnpublishedIssuingDID: Boolean = false
): ZIO[ManagedDIDService & WalletAccessContext, Throwable, LongFormPrismDID] = {
): ZIO[ManagedDIDService & WalletAccessContext, RuntimeException, LongFormPrismDID] = {
for {
managedDIDService <- ZIO.service[ManagedDIDService]
didState <- managedDIDService
Expand All @@ -40,7 +43,7 @@ trait BackgroundJobsHelper {
def createJwtIssuer(
jwtIssuerDID: PrismDID,
verificationRelationship: VerificationRelationship
): ZIO[DIDService & ManagedDIDService & WalletAccessContext, Throwable, JwtIssuer] = {
): ZIO[DIDService & ManagedDIDService & WalletAccessContext, RuntimeException, JwtIssuer] = {
for {
managedDIDService <- ZIO.service[ManagedDIDService]
didService <- ZIO.service[DIDService]
Expand All @@ -53,18 +56,34 @@ trait BackgroundJobsHelper {
.someOrFail(
RuntimeException(s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID")
)
ecKeyPair <- managedDIDService
.javaKeyPairWithDID(jwtIssuerDID.asCanonical, issuingKeyId)
.mapError(e => RuntimeException(s"Error occurred while getting issuer key-pair: ${e.toString}"))
.someOrFail(
RuntimeException(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId")
)
(privateKey, publicKey) = ecKeyPair
jwtIssuer = JwtIssuer(
org.hyperledger.identus.pollux.vc.jwt.DID(jwtIssuerDID.toString),
ES256KSigner(privateKey),
publicKey
)
jwtIssuer <- managedDIDService
.findDIDKeyPair(jwtIssuerDID.asCanonical, issuingKeyId)
.flatMap {
case None =>
ZIO.fail(
RuntimeException(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId")
)
case Some(Ed25519KeyPair(publicKey, privateKey)) =>
ZIO.fail(
RuntimeException(
s"Issuer key-pair '$issuingKeyId' is of the type Ed25519. It's not supported by this feature in this version"
)
)
case Some(X25519KeyPair(publicKey, privateKey)) =>
ZIO.fail(
RuntimeException(
s"Issuer key-pair '$issuingKeyId' is of the type X25519. It's not supported by this feature in this version"
)
)
case Some(Secp256k1KeyPair(publicKey, privateKey)) =>
ZIO.succeed(
JwtIssuer(
org.hyperledger.identus.pollux.vc.jwt.DID(jwtIssuerDID.toString),
ES256KSigner(privateKey.toJavaPrivateKey),
publicKey.toJavaPublicKey
)
)
}
} yield jwtIssuer
}

Expand Down Expand Up @@ -93,7 +112,7 @@ trait BackgroundJobsHelper {
def getEd25519SigningKeyPair(
jwtIssuerDID: PrismDID,
verificationRelationship: VerificationRelationship
): ZIO[DIDService & ManagedDIDService & WalletAccessContext, Throwable, Ed25519KeyPair] = {
): ZIO[DIDService & ManagedDIDService & WalletAccessContext, RuntimeException, Ed25519KeyPair] = {
for {
managedDIDService <- ZIO.service[ManagedDIDService]
didService <- ZIO.service[DIDService]
Expand All @@ -108,7 +127,6 @@ trait BackgroundJobsHelper {
ed25519keyPair <- managedDIDService
.findDIDKeyPair(jwtIssuerDID.asCanonical, issuingKeyId)
.map(_.collect { case keyPair: Ed25519KeyPair => keyPair })
.mapError(e => RuntimeException(s"Error occurred while getting issuer key-pair: ${e.toString}"))
.someOrFail(
RuntimeException(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId")
)
Expand All @@ -127,7 +145,7 @@ trait BackgroundJobsHelper {
def getSDJwtIssuer(
jwtIssuerDID: PrismDID,
verificationRelationship: VerificationRelationship
): ZIO[DIDService & ManagedDIDService & WalletAccessContext, Throwable, JwtIssuer] = {
): ZIO[DIDService & ManagedDIDService & WalletAccessContext, RuntimeException, JwtIssuer] = {
for {
ed25519keyPair <- getEd25519SigningKeyPair(jwtIssuerDID, verificationRelationship)
} yield {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ class CredentialDefinitionRegistryServerEndpoints(
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {
def throwableToInternalServerError(throwable: Throwable) =
ZIO.fail[ErrorResponse](ErrorResponse.internalServerError(detail = Option(throwable.getMessage)))

val createCredentialDefinitionServerEndpoint: ZServerEndpoint[Any, Any] =
createCredentialDefinitionEndpoint
Expand Down Expand Up @@ -53,12 +51,7 @@ class CredentialDefinitionRegistryServerEndpoints(
.zServerSecurityLogic(SecurityLogic.authorizeWalletAccessWith(_)(authenticator, authorizer))
.serverLogic {
case wac => {
case (
ctx: RequestContext,
filter: FilterInput,
paginationInput: PaginationInput,
order: Option[Order]
) =>
case (ctx: RequestContext, filter: FilterInput, paginationInput: PaginationInput, order: Option[Order]) =>
credentialDefinitionController
.lookupCredentialDefinitions(
filter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ object CredentialDefinitionController {
error match {
case RepositoryError(cause: Throwable) =>
ErrorResponse.internalServerError("RepositoryError", detail = Option(cause.toString))
case NotFoundError(_, _, message) =>
ErrorResponse.notFound(detail = Option(message))
case error: GuidNotFoundError =>
ErrorResponse.notFound(detail = Option(error.message))
case error: IdNotFoundError =>
ErrorResponse.notFound(detail = Option(error.message))
case UpdateError(id, version, author, message) =>
ErrorResponse.badRequest(
title = "CredentialDefinitionUpdateError",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class CredentialDefinitionControllerImpl(service: CredentialDefinitionService, m
for {
authorDID <- ZIO
.fromEither(PrismDID.fromString(author))
.mapError(_ => ErrorResponse.badRequest(detail = Some(s"Unable to parse as a Prism DID: ${author}")))
.mapError(ex => ErrorResponse.badRequest(detail = Some(s"Unable to parse Prism DID from '${author}' due: $ex")))
longFormPrismDID <- getLongForm(authorDID, true)
} yield longFormPrismDID

Expand All @@ -110,7 +110,7 @@ class CredentialDefinitionControllerImpl(service: CredentialDefinitionService, m
.getManagedDIDState(did.asCanonical)
.mapError(e =>
ErrorResponse.internalServerError(detail =
Some(s"Error occurred while getting did from wallet: ${e.toString}")
Some(s"Error occurred while getting DID from wallet: ${e.toString}")
)
)
.someOrFail(ErrorResponse.notFound(detail = Some(s"Issuer DID does not exist in the wallet: $did")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ case class CredentialDefinitionControllerLogic(
stats: CollectionStats
) {

def composeNextUri(uri: Uri): Option[Uri] =
private def composeNextUri(uri: Uri): Option[Uri] =
PaginationUtils.composeNextUri(uri, page.contents, pagination, stats)

def composePreviousUri(uri: Uri): Option[Uri] =
private def composePreviousUri(uri: Uri): Option[Uri] =
PaginationUtils.composePreviousUri(uri, page.contents, pagination, stats)

def result: CredentialDefinitionResponsePage = {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import org.hyperledger.identus.castor.core.model.error.DIDOperationError
sealed trait GetManagedDIDError

object GetManagedDIDError {
final case class WalletStorageError(cause: Throwable) extends GetManagedDIDError
final case class WalletStorageError(cause: Throwable) extends GetManagedDIDError // TODO override def toString
final case class OperationError(cause: DIDOperationError) extends GetManagedDIDError
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ trait ManagedDIDService {
def javaKeyPairWithDID(
did: CanonicalPrismDID,
keyId: String
): ZIO[WalletAccessContext, GetKeyError, Option[(JavaPrivateKey, JavaPublicKey)]]
): URIO[WalletAccessContext, Option[(JavaPrivateKey, JavaPublicKey)]]

def findDIDKeyPair(
did: CanonicalPrismDID,
keyId: String
): ZIO[WalletAccessContext, GetKeyError, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]]
): URIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]]

def getManagedDIDState(did: CanonicalPrismDID): ZIO[WalletAccessContext, GetManagedDIDError, Option[ManagedDIDState]]

Expand Down Expand Up @@ -60,9 +60,13 @@ trait ManagedDIDService {
): ZIO[WalletAccessContext, UpdateManagedDIDError, ScheduleDIDOperationOutcome]

/** PeerDID related methods */
def createAndStorePeerDID(serviceEndpoint: java.net.URL): URIO[WalletAccessContext, PeerDID]
def createAndStorePeerDID(
serviceEndpoint: java.net.URL
): URIO[WalletAccessContext, PeerDID]

def getPeerDID(didId: DidId): ZIO[WalletAccessContext, DIDSecretStorageError.KeyNotFoundError, PeerDID]
def getPeerDID(
didId: DidId
): ZIO[WalletAccessContext, DIDSecretStorageError.KeyNotFoundError, PeerDID]

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ManagedDIDServiceImpl private[walletapi] (
def javaKeyPairWithDID(
did: CanonicalPrismDID,
keyId: String
): ZIO[WalletAccessContext, GetKeyError, Option[(JavaPrivateKey, JavaPublicKey)]] = {
): URIO[WalletAccessContext, Option[(JavaPrivateKey, JavaPublicKey)]] = {
findDIDKeyPair(did, keyId)
.flatMap {
case None => ZIO.none
Expand All @@ -75,7 +75,7 @@ class ManagedDIDServiceImpl private[walletapi] (
override def findDIDKeyPair(
did: CanonicalPrismDID,
keyId: String
): ZIO[WalletAccessContext, GetKeyError, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] =
): URIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] =
nonSecretStorage
.getManagedDIDState(did)
.flatMap {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object MockManagedDIDService extends Mock[ManagedDIDService] {

object GetManagedDIDState extends Effect[CanonicalPrismDID, GetManagedDIDError, Option[ManagedDIDState]]
object JavaKeyPairWithDID
extends Effect[(CanonicalPrismDID, String), GetKeyError, Option[(JavaPrivateKey, JavaPublicKey)]]
extends Effect[(CanonicalPrismDID, String), Nothing, Option[(JavaPrivateKey, JavaPublicKey)]]

override val compose: URLayer[mock.Proxy, ManagedDIDService] =
ZLayer {
Expand All @@ -38,13 +38,13 @@ object MockManagedDIDService extends Mock[ManagedDIDService] {
override def javaKeyPairWithDID(
did: CanonicalPrismDID,
keyId: String
): IO[GetKeyError, Option[(JavaPrivateKey, JavaPublicKey)]] =
): UIO[Option[(JavaPrivateKey, JavaPublicKey)]] =
proxy(JavaKeyPairWithDID, did, keyId)

override def findDIDKeyPair(
did: CanonicalPrismDID,
keyId: String
): IO[GetKeyError, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = ???
): UIO[Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = ???

override def getManagedDIDState(
did: CanonicalPrismDID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import zio.{IO, ZIO}
import java.util.UUID

trait CredentialDefinitionService {
type Result[T] = ZIO[WalletAccessContext, CredentialDefinitionService.Error, T]
private[service] type Result[T] = ZIO[WalletAccessContext, CredentialDefinitionService.Error, T]

/** @param in
* CredentialDefinition form for creating the instance
Expand Down Expand Up @@ -38,15 +38,22 @@ object CredentialDefinitionService {

final case class RepositoryError(cause: Throwable) extends Error

final case class NotFoundError(guid: Option[UUID] = None, id: Option[UUID] = None, message: String) extends Error
final case class GuidNotFoundError(guid: UUID) extends Error {
def message = s"Credential Definition record cannot be found by `guid`=$guid"
}
final case class IdNotFoundError(id: UUID) extends Error {
def message = s"Credential Definition record cannot be found by `id`=$id"
}

object NotFoundError {
def byGuid(guid: UUID): NotFoundError =
NotFoundError(guid = Option(guid), message = s"Credential Definition record cannot be found by `guid`=$guid")
// final case class NotFoundError(guid: Option[UUID] = None, id: Option[UUID] = None, message: String) extends Error

def byId(id: UUID): NotFoundError =
NotFoundError(id = Option(id), message = s"Credential Definition record cannot be found by `id`=$id")
}
// object NotFoundError {
// def byGuid(guid: UUID): NotFoundError =
// NotFoundError(guid = Option(guid), message = s"Credential Definition record cannot be found by `guid`=$guid")

// def byId(id: UUID): NotFoundError =
// NotFoundError(id = Option(id), message = s"Credential Definition record cannot be found by `id`=$id")
// }

final case class UpdateError(id: UUID, version: String, author: String, message: String) extends Error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.hyperledger.identus.pollux.core.service.serdes.{
PublicCredentialDefinitionSerDesV1
}
import org.hyperledger.identus.pollux.core.service.CredentialDefinitionService.Error.*
import zio.{IO, URLayer, ZIO, ZLayer}
import zio.*
import zio.ZIO.getOrFailWith

import java.net.URI
Expand All @@ -33,7 +33,12 @@ class CredentialDefinitionServiceImpl(

override def create(in: CredentialDefinition.Input): Result[CredentialDefinition] = {
for {
uri <- ZIO.attempt(new URI(in.schemaId))
uri <-
ZIO
.attempt(new URI(in.schemaId))
.catchNonFatalOrDie { ex =>
ZIO.fail(CredentialDefinitionValidationError(URISyntaxError(ex.getMessage())))
}
content <- uriDereferencer.dereference(uri)
anoncredSchema <- AnoncredSchemaSerDesV1.schemaSerDes.deserialize(content)
anoncredLibSchema =
Expand All @@ -55,7 +60,7 @@ class CredentialDefinitionServiceImpl(
)
).toEither
)
.mapError((t: Throwable) => CredentialDefinitionCreationError(t.getMessage))
.orDie
publicCredentialDefinitionJson <-
PublicCredentialDefinitionSerDesV1.schemaSerDes.deserializeAsJson(
anoncredLibCredentialDefinition.cd.data
Expand All @@ -76,26 +81,28 @@ class CredentialDefinitionServiceImpl(
ProofKeyCredentialDefinitionSchemaSerDesV1.version,
proofKeyCredentialDefinitionJson
)
createdCredentialDefinition <- credentialDefinitionRepository.create(cd)
createdCredentialDefinition <- credentialDefinitionRepository.create(cd).orDie
_ <- genericSecretStorage
.set(
createdCredentialDefinition.guid,
CredentialDefinitionSecret(privateCredentialDefinitionJson)
)
.orDie
} yield createdCredentialDefinition
}.mapError {
case u: URIDereferencerError => CredentialDefinitionValidationError(URISyntaxError(u.error))
case j: JsonSchemaError => CredentialDefinitionValidationError(SchemaError(j))
case t: Throwable => RepositoryError(t)
case e: CredentialDefinitionCreationError => e
case u: URIDereferencerError => CredentialDefinitionValidationError(URISyntaxError(u.error))
case j: JsonSchemaError => CredentialDefinitionValidationError(SchemaError(j))
case e: CredentialDefinitionValidationError => e
}

override def delete(guid: UUID): Result[CredentialDefinition] =
for {
deleted_row_opt <- credentialDefinitionRepository
.delete(guid)
.mapError(RepositoryError.apply)
deleted_row <- getOrFailWith(NotFoundError.byGuid(guid))(deleted_row_opt)
deleted_row <- deleted_row_opt match
case None => ZIO.fail(GuidNotFoundError(guid))
case Some(value) => ZIO.succeed(value)
} yield deleted_row

override def lookup(filter: CredentialDefinition.Filter, skip: Int, limit: Int): Result[FilteredEntries] = {
Expand All @@ -108,9 +115,10 @@ class CredentialDefinitionServiceImpl(
credentialDefinitionRepository
.getByGuid(guid)
.mapError[CredentialDefinitionService.Error](t => RepositoryError(t))
.flatMap(
getOrFailWith(NotFoundError.byGuid(guid))(_)
)
.flatMap {
case None => ZIO.fail(GuidNotFoundError(guid))
case Some(value) => ZIO.succeed(value)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,6 @@ private class CredentialServiceImpl(
)
ecKeyPair <- managedDIDService
.javaKeyPairWithDID(jwtIssuerDID.asCanonical, issuingKeyId)
.mapError(e => UnexpectedError(s"Error occurred while getting issuer key-pair: ${e.toString}"))
.someOrFail(
UnexpectedError(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId")
)
Expand Down Expand Up @@ -579,7 +578,6 @@ private class CredentialServiceImpl(
ed25519keyPair <- managedDIDService
.findDIDKeyPair(jwtIssuerDID.asCanonical, issuingKeyId)
.map(_.collect { case keyPair: Ed25519KeyPair => keyPair })
.mapError(e => UnexpectedError(s"Error occurred while getting issuer key-pair: ${e.toString}"))
.someOrFail(
UnexpectedError(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId")
)
Expand Down

0 comments on commit d6dfb72

Please sign in to comment.