Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DPP-618][Self-service error codes] Adapt error codes in ApiPackageService #11284

Merged
merged 10 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
tudor-da marked this conversation as resolved.
Show resolved Hide resolved
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] =
tudor-da marked this conversation as resolved.
Show resolved Hide resolved
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,
)
tudor-da marked this conversation as resolved.
Show resolved Hide resolved
}

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)
}
}
}