Skip to content

Commit

Permalink
[DPP-618][Self-service error codes] Adapt error codes in ApiPackageSe…
Browse files Browse the repository at this point in the history
…rvice (#11284)

CHANGELOG_BEGIN
CHANGELOG_END
  • Loading branch information
pbatko-da authored Oct 20, 2021
1 parent 50ea92f commit 8a3abce
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import com.daml.error.definitions.ErrorGroups.ParticipantErrorGroup.TransactionE
import com.daml.lf.data.Ref
import com.daml.lf.data.Ref.PackageId
import com.daml.lf.engine.Error.Validation.ReplayMismatch
import com.daml.lf.engine.{Error => LfError}
import com.daml.lf.interpretation.{Error => LfInterpretationError}
import com.daml.lf.language.{LanguageVersion, LookupError, Reference}
import com.daml.lf.transaction.GlobalKey
import com.daml.lf.value.Value
import com.daml.lf.{VersionRange, language}
import com.daml.lf.engine.{Error => LfError}
import com.daml.lf.interpretation.{Error => LfInterpretationError}

object LedgerApiErrors extends LedgerApiErrorGroup {

Expand All @@ -33,6 +33,44 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
}

object ReadErrors extends ErrorGroup() {

@Explanation("This rejection is given when a package id is malformed.")
@Resolution("Make sure the package id provided in the request has correct form.")
// TODO error codes: Consider using `LedgerApiErrors.CommandValidation.InvalidArgument`
object MalformedPackageId
extends ErrorCode(
id = "MALFORMED_PACKAGE_ID",
ErrorCategory.InvalidIndependentOfSystemState,
) {
case class Reject(message: String)(implicit
loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl(
cause = message
)
}

@Explanation(
"This rejection is given when a read request tries to access a package which does not exist on the ledger."
)
@Resolution("Use a package id pertaining to a package existing on the ledger.")
// TODO error codes: Possible duplicate of `LedgerApiErrors.Package.MissingPackage`
object PackageNotFound
extends ErrorCode(
id = "PACKAGE_NOT_FOUND",
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) {
case class Reject(packageId: String)(implicit
loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl(
cause = "Could not find package."
) {

override def resources: Seq[(ErrorResource, String)] = {
super.resources :+ ((ErrorResource.DalfPackage, packageId))
}
}
}

@Explanation("This rejection is given when a read request tries to access pruned data.")
@Resolution("Use an offset that is after the pruning offset.")
object ParticipantPrunedDataAccessed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ object ValidationLogger {
t
}

def logFailureWithContext[Request](request: Request, t: Throwable)(implicit
def logFailureWithContext[Request, T <: Throwable](request: Request, t: T)(implicit
logger: ContextualizedLogger,
loggingContext: LoggingContext,
): Throwable = {
): T = {
logger.debug(s"Request validation failed for $request. Message: ${t.getMessage}")
logger.info(t.getMessage)
t
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import com.daml.error.definitions.LedgerApiErrors
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher}
import com.daml.ledger.api.domain.LedgerId
import com.daml.ledger.grpc.GrpcStatuses
import com.daml.platform.server.api.ApiException
import com.daml.logging.{ContextualizedLogger, LoggingContext}
import com.daml.platform.server.api.validation.ErrorFactories.{
addDefiniteAnswerDetails,
definiteAnswers,
}
import com.daml.platform.server.api.{ApiException, ValidationLogger}
import com.google.protobuf.{Any => AnyProto}
import com.google.rpc.{ErrorInfo, Status}
import io.grpc.Status.Code
Expand All @@ -21,6 +22,35 @@ import scalaz.syntax.tag._

class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitcher) {

def malformedPackageId[Request](request: Request, message: String)(implicit
contextualizedErrorLogger: ContextualizedErrorLogger,
logger: ContextualizedLogger,
loggingContext: LoggingContext,
): StatusRuntimeException = {
errorCodesVersionSwitcher.choose(
v1 = ValidationLogger.logFailureWithContext(
request,
io.grpc.Status.INVALID_ARGUMENT
.withDescription(message)
.asRuntimeException(),
),
v2 = LedgerApiErrors.ReadErrors.MalformedPackageId
.Reject(
message = message
)
.asGrpcError,
)
}

def packageNotFound(packageId: String)(implicit
contextualizedErrorLogger: ContextualizedErrorLogger
): StatusRuntimeException = {
errorCodesVersionSwitcher.choose(
v1 = io.grpc.Status.NOT_FOUND.asRuntimeException(),
v2 = LedgerApiErrors.ReadErrors.PackageNotFound.Reject(packageId = packageId).asGrpcError,
)
}

def duplicateCommandException(implicit
contextualizedErrorLogger: ContextualizedErrorLogger
): StatusRuntimeException =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,42 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope
ErrorDetails.RequestInfoDetail("trace-id")

"ErrorFactories" should {

"return malformedPackageId" in {
assertVersionedError(
_.malformedPackageId(request = "request123", message = "message123")(
contextualizedErrorLogger = contextualizedErrorLogger,
logger = logger,
loggingContext = loggingContext,
)
)(
v1_code = Code.INVALID_ARGUMENT,
v1_message = "message123",
v1_details = Seq.empty,
v2_code = Code.INVALID_ARGUMENT,
v2_message = s"MALFORMED_PACKAGE_ID(8,$correlationId): message123",
v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("MALFORMED_PACKAGE_ID"),
DefaultTraceIdRequestInfo,
),
)
}

"return packageNotFound" in {
assertVersionedError(_.packageNotFound("packageId123"))(
v1_code = Code.NOT_FOUND,
v1_message = "",
v1_details = Seq.empty,
v2_code = Code.NOT_FOUND,
v2_message = s"PACKAGE_NOT_FOUND(11,$correlationId): Could not find package.",
v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("PACKAGE_NOT_FOUND"),
DefaultTraceIdRequestInfo,
ErrorDetails.ResourceInfoDetail("PACKAGE", "packageId123"),
),
)
}

"return the DuplicateCommandException" in {
assertVersionedError(_.duplicateCommandException)(
v1_code = Code.ALREADY_EXISTS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ private[daml] object ApiServices {
val apiVersionService =
ApiVersionService.create()

val apiPackageService = ApiPackageService.create(ledgerId, packagesService)
val apiPackageService =
ApiPackageService.create(ledgerId, packagesService, errorsVersionsSwitcher)

val apiConfigurationService =
ApiLedgerConfigurationService.create(ledgerId, configurationService)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,31 @@
package com.daml.platform.apiserver.services

import com.daml.daml_lf_dev.DamlLf.{Archive, HashFunction}
import com.daml.error.{DamlContextualizedErrorLogger, ErrorCodesVersionSwitcher}
import com.daml.ledger.api.domain.LedgerId
import com.daml.ledger.api.v1.package_service.HashFunction.{
SHA256 => APISHA256,
Unrecognized => APIUnrecognized,
}
import com.daml.ledger.api.v1.package_service.PackageServiceGrpc.PackageService
import com.daml.ledger.api.v1.package_service.{HashFunction => APIHashFunction, _}
import com.daml.ledger.participant.state.index.v2.IndexPackagesService
import com.daml.lf.data.Ref
import com.daml.logging.LoggingContext.withEnrichedLoggingContext
import com.daml.logging.{ContextualizedLogger, LoggingContext}
import com.daml.platform.api.grpc.GrpcApiService
import com.daml.platform.server.api.ValidationLogger
import com.daml.platform.server.api.validation.PackageServiceValidation
import io.grpc.{BindableService, ServerServiceDefinition, Status}
import com.daml.platform.server.api.validation.{ErrorFactories, PackageServiceValidation}
import io.grpc.{BindableService, ServerServiceDefinition}

import scala.concurrent.{ExecutionContext, Future}

private[apiserver] final class ApiPackageService private (
backend: IndexPackagesService
backend: IndexPackagesService,
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext)
extends PackageService
with GrpcApiService {

private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)

private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)

override def bindService(): ServerServiceDefinition =
PackageServiceGrpc.bindService(this, executionContext)

Expand All @@ -49,11 +48,15 @@ private[apiserver] final class ApiPackageService private (
withValidatedPackageId(request.packageId, request) { packageId =>
backend
.getLfArchive(packageId)
.flatMap(
_.fold(Future.failed[GetPackageResponse](Status.NOT_FOUND.asRuntimeException()))(
archive => Future.successful(toGetPackageResponse(archive))
)
)
.flatMap {
case None =>
Future.failed[GetPackageResponse](
errorFactories.packageNotFound(packageId = packageId)(
createContextualizedErrorLogger
)
)
case Some(archive) => Future.successful(toGetPackageResponse(archive))
}
.andThen(logger.logErrorsOnCall[GetPackageResponse])
}
}
Expand All @@ -80,42 +83,59 @@ private[apiserver] final class ApiPackageService private (

private def withValidatedPackageId[T, R](packageId: String, request: R)(
block: Ref.PackageId => Future[T]
) =
): Future[T] =
Ref.PackageId
.fromString(packageId)
.fold(
error =>
errorMessage =>
Future.failed[T](
ValidationLogger.logFailureWithContext(
request,
Status.INVALID_ARGUMENT
.withDescription(error)
.asRuntimeException(),
errorFactories.malformedPackageId(request = request, message = errorMessage)(
createContextualizedErrorLogger,
logger,
loggingContext,
)
),
pId => block(pId),
packageId => block(packageId),
)

private def toGetPackageResponse(archive: Archive): GetPackageResponse = {
val hashF: APIHashFunction = archive.getHashFunction match {
case HashFunction.SHA256 => APISHA256
case _ => APIUnrecognized(-1)
val hashFunction = archive.getHashFunction match {
case HashFunction.SHA256 => APIHashFunction.SHA256
case _ => APIHashFunction.Unrecognized(-1)
}
GetPackageResponse(hashF, archive.getPayload, archive.getHash)
GetPackageResponse(
hashFunction = hashFunction,
archivePayload = archive.getPayload,
hash = archive.getHash,
)
}

private def createContextualizedErrorLogger(implicit
loggingContext: LoggingContext
): DamlContextualizedErrorLogger =
new DamlContextualizedErrorLogger(logger, loggingContext, None)

}

private[platform] object ApiPackageService {
def create(
ledgerId: LedgerId,
backend: IndexPackagesService,
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
)(implicit
executionContext: ExecutionContext,
loggingContext: LoggingContext,
): PackageService with GrpcApiService =
new PackageServiceValidation(new ApiPackageService(backend), ledgerId) with BindableService {
): PackageService with GrpcApiService = {
val service = new ApiPackageService(
backend = backend,
errorCodesVersionSwitcher = errorCodesVersionSwitcher,
)
new PackageServiceValidation(
service = service,
ledgerId = ledgerId,
) with BindableService {
override def bindService(): ServerServiceDefinition =
PackageServiceGrpc.bindService(this, executionContext)
}
}
}

0 comments on commit 8a3abce

Please sign in to comment.