diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala index 82ac38d92931..b86a255fb354 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala @@ -155,12 +155,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch ): StatusRuntimeException = errorCodesVersionSwitcher.choose( v1 = { - val statusBuilder = Status - .newBuilder() - .setCode(Code.INVALID_ARGUMENT.value()) - .setMessage(s"Invalid argument: $message") - addDefiniteAnswerDetails(definiteAnswer, statusBuilder) - grpcError(statusBuilder.build()) + invalidArgumentV1(definiteAnswer, message) }, // TODO error codes: This error group is confusing for this generic error as it can be dispatched // from call-sites that do not involve command validation (e.g. ApiTransactionService). @@ -189,6 +184,37 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch .asGrpcError, ) + // TODO error codes: Reconcile with com.daml.platform.server.api.validation.ErrorFactories.offsetAfterLedgerEnd + def readingOffsetAfterLedgerEnd_was_invalidArgument( + definiteAnswer: Option[Boolean] + )(message: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = { + invalidArgumentV1(definiteAnswer, message) + }, + v2 = LedgerApiErrors.ReadErrors.RequestedOffsetAfterLedgerEnd + .Reject(message) + .asGrpcError, + ) + + def nonHexOffset( + definiteAnswer: Option[Boolean] + )(fieldName: String, offsetValue: String, message: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = invalidArgumentV1(definiteAnswer, message), + v2 = LedgerApiErrors.NonHexOffset + .Error( + fieldName = fieldName, + offsetValue = offsetValue, + message = message, + ) + .asGrpcError, + ) + /** @param fieldName An invalid field's name. * @param message A status' message. * @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication. @@ -378,6 +404,18 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch def grpcError(status: Status): StatusRuntimeException = new NoStackTraceApiException( StatusProto.toStatusRuntimeException(status) ) + + private def invalidArgumentV1( + definiteAnswer: Option[Boolean], + message: String, + ): StatusRuntimeException = { + val statusBuilder = Status + .newBuilder() + .setCode(Code.INVALID_ARGUMENT.value()) + .setMessage(s"Invalid argument: $message") + addDefiniteAnswerDetails(definiteAnswer, statusBuilder) + grpcError(statusBuilder.build()) + } } /** Object exposing the legacy error factories. diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala index a23384f8ab5e..9d6fcf8e4970 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala @@ -146,6 +146,41 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope ) } + "return a nonHexOffset error" in { + assertVersionedError( + _.nonHexOffset(None)( + fieldName = "fieldName123", + offsetValue = "offsetValue123", + message = "message123", + ) + )( + v1_code = Code.INVALID_ARGUMENT, + v1_message = "Invalid argument: message123", + v1_details = Seq.empty, + v2_code = Code.INVALID_ARGUMENT, + v2_message = + s"NON_HEXADECIMAL_OFFSET(8,$correlationId): Offset in fieldName123 not specified in hexadecimal: offsetValue123: message123", + v2_details = Seq[ErrorDetails.ErrorDetail]( + ErrorDetails.ErrorInfoDetail("NON_HEXADECIMAL_OFFSET"), + DefaultTraceIdRequestInfo, + ), + ) + } + + "return a readingOffsetAfterLedgerEnd error" in { + assertVersionedError(_.readingOffsetAfterLedgerEnd_was_invalidArgument(None)("message123"))( + v1_code = Code.INVALID_ARGUMENT, + v1_message = "Invalid argument: message123", + v1_details = Seq.empty, + v2_code = Code.OUT_OF_RANGE, + v2_message = s"REQUESTED_OFFSET_OUT_OF_RANGE(12,$correlationId): message123", + v2_details = Seq[ErrorDetails.ErrorDetail]( + ErrorDetails.ErrorInfoDetail("REQUESTED_OFFSET_OUT_OF_RANGE"), + DefaultTraceIdRequestInfo, + ), + ) + } + "return an unauthenticatedMissingJwtToken error" in { assertVersionedError(_.unauthenticatedMissingJwtToken())( v1_code = Code.UNAUTHENTICATED, diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala index 9607a38be132..2ff7a8e39efd 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala @@ -293,7 +293,11 @@ private[daml] object ApiServices { ) val apiParticipantPruningService = - ApiParticipantPruningService.createApiService(indexService, writeService) + ApiParticipantPruningService.createApiService( + indexService, + writeService, + errorsVersionsSwitcher, + ) List( new CommandSubmissionServiceAuthorization(apiSubmissionService, authorizer), diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala index 1c480b6a0658..b03fe151dd0e 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala @@ -3,7 +3,11 @@ package com.daml.platform.apiserver.services.admin -import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} +import com.daml.error.{ + ContextualizedErrorLogger, + DamlContextualizedErrorLogger, + ErrorCodesVersionSwitcher, +} import java.util.UUID import com.daml.ledger.api.v1.admin.participant_pruning_service.{ @@ -30,30 +34,34 @@ import scala.concurrent.{ExecutionContext, Future} final class ApiParticipantPruningService private ( readBackend: IndexParticipantPruningService with LedgerEndService, writeBackend: state.WriteParticipantPruningService, -)(implicit executionContext: ExecutionContext, logCtx: LoggingContext) + errorCodesVersionSwitcher: ErrorCodesVersionSwitcher, +)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends ParticipantPruningServiceGrpc.ParticipantPruningService with GrpcApiService { private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) - private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = - new DamlContextualizedErrorLogger(logger, logCtx, None) + private val errorFactories = ErrorFactories(errorCodesVersionSwitcher) override def bindService(): ServerServiceDefinition = ParticipantPruningServiceGrpc.bindService(this, executionContext) + override def close(): Unit = () + override def prune(request: PruneRequest): Future[PruneResponse] = { val submissionIdOrErr = Ref.SubmissionId .fromString( if (request.submissionId.nonEmpty) request.submissionId else UUID.randomUUID().toString ) .left - .map(err => ErrorFactories.invalidArgument(None)(s"submission_id $err")) + .map(err => + errorFactories.invalidArgument(None)(s"submission_id $err")(contextualizedErrorLogger) + ) submissionIdOrErr.fold( t => Future.failed(ValidationLogger.logFailure(request, t)), submissionId => LoggingContext.withEnrichedLoggingContext(logging.submissionId(submissionId)) { - implicit logCtx => + implicit loggingContext => logger.info(s"Pruning up to ${request.pruneUpTo}") (for { @@ -75,7 +83,7 @@ final class ApiParticipantPruningService private ( private def validateRequest( request: PruneRequest - )(implicit logCtx: LoggingContext): Future[Offset] = { + )(implicit loggingContext: LoggingContext): Future[Offset] = { (for { pruneUpToString <- checkOffsetIsSpecified(request.pruneUpTo) pruneUpTo <- checkOffsetIsHexadecimal(pruneUpToString) @@ -91,7 +99,7 @@ final class ApiParticipantPruningService private ( submissionId: Ref.SubmissionId, pruneAllDivulgedContracts: Boolean, )(implicit - logCtx: LoggingContext + loggingContext: LoggingContext ): Future[Unit] = { import state.PruningResult._ logger.info( @@ -110,7 +118,7 @@ final class ApiParticipantPruningService private ( private def pruneLedgerApiServerIndex( pruneUpTo: Offset, pruneAllDivulgedContracts: Boolean, - )(implicit logCtx: LoggingContext): Future[PruneResponse] = { + )(implicit loggingContext: LoggingContext): Future[PruneResponse] = { logger.info(s"About to prune ledger api server index to ${pruneUpTo.toApiString} inclusively") readBackend .prune(pruneUpTo, pruneAllDivulgedContracts) @@ -120,45 +128,51 @@ final class ApiParticipantPruningService private ( } } - private def checkOffsetIsSpecified(offset: String): Either[StatusRuntimeException, String] = + private def checkOffsetIsSpecified( + offset: String + )(implicit loggingContext: LoggingContext): Either[StatusRuntimeException, String] = Either.cond( offset.nonEmpty, offset, - ErrorFactories.invalidArgument(None)("prune_up_to not specified"), + errorFactories.invalidArgument(None)("prune_up_to not specified")(contextualizedErrorLogger), ) private def checkOffsetIsHexadecimal( pruneUpToString: String - ): Either[StatusRuntimeException, Offset] = + )(implicit loggingContext: LoggingContext): Either[StatusRuntimeException, Offset] = ApiOffset .fromString(pruneUpToString) .toEither .left .map(t => - // TODO error codes: Use LedgerApiErrors.NonHexOffset - ErrorFactories.invalidArgument(None)( - s"prune_up_to needs to be a hexadecimal string and not $pruneUpToString: ${t.getMessage}" - ) + errorFactories.nonHexOffset(None)( + fieldName = "prune_up_to", + offsetValue = pruneUpToString, + message = + s"prune_up_to needs to be a hexadecimal string and not $pruneUpToString: ${t.getMessage}", + )(contextualizedErrorLogger) ) private def checkOffsetIsBeforeLedgerEnd( pruneUpToProto: Offset, pruneUpToString: String, - )(implicit logCtx: LoggingContext): Future[Offset] = + )(implicit loggingContext: LoggingContext): Future[Offset] = for { ledgerEnd <- readBackend.currentLedgerEnd() _ <- if (pruneUpToString < ledgerEnd.value) Future.successful(()) else Future.failed( - // TODO error codes: Use LedgerApiErrors.ReadErrors.requestedOffsetAfterLedgerEnd - ErrorFactories.invalidArgument(None)( + errorFactories.readingOffsetAfterLedgerEnd_was_invalidArgument(None)( s"prune_up_to needs to be before ledger end ${ledgerEnd.value}" - ) + )(contextualizedErrorLogger) ) } yield pruneUpToProto - override def close(): Unit = () + private def contextualizedErrorLogger(implicit + loggingContext: LoggingContext + ): ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) } @@ -166,10 +180,11 @@ object ApiParticipantPruningService { def createApiService( readBackend: IndexParticipantPruningService with LedgerEndService, writeBackend: state.WriteParticipantPruningService, + errorCodesVersionSwitcher: ErrorCodesVersionSwitcher, )(implicit executionContext: ExecutionContext, - logCtx: LoggingContext, + loggingContext: LoggingContext, ): ParticipantPruningServiceGrpc.ParticipantPruningService with GrpcApiService = - new ApiParticipantPruningService(readBackend, writeBackend) + new ApiParticipantPruningService(readBackend, writeBackend, errorCodesVersionSwitcher) }