mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 16:57:40 +03:00
[Self-service error codes] Do not return error code id and definite_answer in metadata for security sensitive errors (#11828)
* [Self-service error codes] Do not return error code id in metadata CHANGELOG_BEGIN CHANGELOG_END * Do not propagate definite_answer * Additionally, remove redundant context info from error codes * Fix tests * Create IndexDbException as a specialization * Used to globally define error codes returned by the persistence layer that are logging
This commit is contained in:
parent
026b92a8b1
commit
cf3ac011ca
@ -67,18 +67,20 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit
|
||||
getStatusInfo(err)
|
||||
|
||||
// Provide error id and context via ErrorInfo
|
||||
val errInfoBld = com.google.rpc.ErrorInfo.newBuilder().setReason(id)
|
||||
if (!code.category.securitySensitive) {
|
||||
contextMap.foreach { case (k, v) => errInfoBld.putMetadata(k, v) }
|
||||
}
|
||||
val maybeErrInfo =
|
||||
if (!code.category.securitySensitive) {
|
||||
val errInfoBld = com.google.rpc.ErrorInfo.newBuilder()
|
||||
contextMap.foreach { case (k, v) => errInfoBld.putMetadata(k, v) }
|
||||
errInfoBld.setReason(id)
|
||||
|
||||
// TODO error codes: Resolve dependency and use constant
|
||||
// val definiteAnswerKey = com.daml.ledger.grpc.GrpcStatuses.DefiniteAnswerKey
|
||||
val definiteAnswerKey = "definite_answer"
|
||||
err.definiteAnswerO.foreach { definiteAnswer =>
|
||||
errInfoBld.putMetadata(definiteAnswerKey, definiteAnswer.toString)
|
||||
}
|
||||
val errInfo = com.google.protobuf.Any.pack(errInfoBld.build())
|
||||
// TODO error codes: Resolve dependency and use constant
|
||||
// val definiteAnswerKey = com.daml.ledger.grpc.GrpcStatuses.DefiniteAnswerKey
|
||||
val definiteAnswerKey = "definite_answer"
|
||||
err.definiteAnswerO.foreach { definiteAnswer =>
|
||||
errInfoBld.putMetadata(definiteAnswerKey, definiteAnswer.toString)
|
||||
}
|
||||
Some(com.google.protobuf.Any.pack(errInfoBld.build()))
|
||||
} else None
|
||||
|
||||
// Build retry info
|
||||
val retryInfo = err.retryable.map { ri =>
|
||||
@ -124,7 +126,7 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit
|
||||
.setCode(codeGrpc.value())
|
||||
.setMessage(message)
|
||||
|
||||
(Seq(errInfo) ++ retryInfo.toList ++ requestInfo.toList ++ resourceInfo)
|
||||
(maybeErrInfo.toList ++ retryInfo.toList ++ requestInfo.toList ++ resourceInfo)
|
||||
.foldLeft(statusBuilder) { case (acc, item) =>
|
||||
acc.addDetails(item)
|
||||
}
|
||||
|
@ -3,10 +3,9 @@
|
||||
|
||||
package com.daml.error.definitions
|
||||
|
||||
import com.daml.error.ErrorCode.LoggingApiException
|
||||
import com.daml.error._
|
||||
import com.daml.error.definitions.ErrorGroups.ParticipantErrorGroup.IndexErrorGroup
|
||||
import com.daml.error.utils.ErrorDetails
|
||||
import io.grpc.StatusRuntimeException
|
||||
|
||||
@Explanation("Errors raised by the Participant Index persistence layer.")
|
||||
object IndexErrors extends IndexErrorGroup {
|
||||
@ -19,11 +18,10 @@ object IndexErrors extends IndexErrorGroup {
|
||||
extends ErrorCode(
|
||||
id = "INDEX_DB_SQL_TRANSIENT_ERROR",
|
||||
ErrorCategory.TransientServerFailure,
|
||||
)
|
||||
with HasUnapply {
|
||||
) {
|
||||
case class Reject(throwable: Throwable)(implicit
|
||||
val loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
) extends DbError(
|
||||
cause =
|
||||
s"Processing the request failed due to a transient database error: ${throwable.getMessage}",
|
||||
throwableO = Some(throwable),
|
||||
@ -38,11 +36,10 @@ object IndexErrors extends IndexErrorGroup {
|
||||
extends ErrorCode(
|
||||
id = "INDEX_DB_SQL_NON_TRANSIENT_ERROR",
|
||||
ErrorCategory.SystemInternalAssumptionViolated,
|
||||
)
|
||||
with HasUnapply {
|
||||
) {
|
||||
case class Reject(throwable: Throwable)(implicit
|
||||
val loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
) extends DbError(
|
||||
cause =
|
||||
s"Processing the request failed due to a non-transient database error: ${throwable.getMessage}",
|
||||
throwableO = Some(throwable),
|
||||
@ -57,21 +54,34 @@ object IndexErrors extends IndexErrorGroup {
|
||||
extends ErrorCode(
|
||||
id = "INDEX_DB_INVALID_RESULT_SET",
|
||||
ErrorCategory.SystemInternalAssumptionViolated,
|
||||
)
|
||||
with HasUnapply {
|
||||
) {
|
||||
case class Reject(message: String)(implicit
|
||||
val loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
) extends DbError(
|
||||
cause = message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
trait HasUnapply {
|
||||
this: ErrorCode =>
|
||||
// TODO error codes: Create a generic unapply for ErrorCode that returns the ErrorCode instance
|
||||
// and match against that one.
|
||||
def unapply(exception: StatusRuntimeException): Option[Unit] =
|
||||
if (ErrorDetails.isErrorCode(exception)(errorCode = this)) Some(()) else None
|
||||
// Decorator that returns a specialized StatusRuntimeException (IndexDbException)
|
||||
// that can be used for precise matching of persistence exceptions (e.g. for index initialization failures that need retrying).
|
||||
// Without this specialization, internal errors just appear as StatusRuntimeExceptions (see INDEX_DB_SQL_NON_TRANSIENT_ERROR)
|
||||
// without any marker, impeding us to assert whether they are emitted by the persistence layer or not.
|
||||
abstract class DbError(
|
||||
override val cause: String,
|
||||
override val throwableO: Option[Throwable] = None,
|
||||
)(implicit
|
||||
code: ErrorCode,
|
||||
loggingContext: ContextualizedErrorLogger,
|
||||
) extends LoggingTransactionErrorImpl(cause, throwableO) {
|
||||
override def asGrpcErrorFromContext(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): IndexDbException = {
|
||||
val err = super.asGrpcErrorFromContext(contextualizedErrorLogger)
|
||||
IndexDbException(err.getStatus, err.getTrailers)
|
||||
}
|
||||
}
|
||||
|
||||
case class IndexDbException(status: io.grpc.Status, metadata: io.grpc.Metadata)
|
||||
extends LoggingApiException(status, metadata)
|
||||
}
|
||||
|
@ -54,10 +54,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "REQUEST_TIME_OUT",
|
||||
ErrorCategory.DeadlineExceededRequestStateUnknown,
|
||||
) {
|
||||
case class Reject(message: String, override val definiteAnswer: Boolean)(implicit
|
||||
case class Reject(_message: String, _definiteAnswer: Boolean)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = message
|
||||
cause = _message,
|
||||
definiteAnswer = _definiteAnswer,
|
||||
)
|
||||
}
|
||||
|
||||
@ -275,17 +276,23 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "SERVICE_NOT_RUNNING",
|
||||
ErrorCategory.TransientServerFailure,
|
||||
) {
|
||||
case class Reject(serviceName: String)(implicit
|
||||
case class Reject(_serviceName: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = s"$serviceName has been shut down."
|
||||
)
|
||||
cause = s"${_serviceName} has been shut down."
|
||||
) {
|
||||
override def context: Map[String, String] =
|
||||
super.context ++ Map("service_name" -> _serviceName)
|
||||
}
|
||||
|
||||
case class ServiceReset(serviceName: String)(implicit
|
||||
case class ServiceReset(_serviceName: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = s"$serviceName is currently being reset."
|
||||
)
|
||||
cause = s"${_serviceName} is currently being reset."
|
||||
) {
|
||||
override def context: Map[String, String] =
|
||||
super.context ++ Map("service_name" -> _serviceName)
|
||||
}
|
||||
}
|
||||
|
||||
@Explanation("Authentication errors.")
|
||||
@ -355,14 +362,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "PACKAGE_NOT_FOUND",
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
|
||||
) {
|
||||
case class Reject(packageId: String)(implicit
|
||||
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))
|
||||
super.resources :+ ((ErrorResource.DalfPackage, _packageId))
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,10 +395,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
|
||||
) {
|
||||
|
||||
case class Reject(transactionId: String)(implicit loggingContext: ContextualizedErrorLogger)
|
||||
extends LoggingTransactionErrorImpl(cause = "Transaction not found, or not visible.") {
|
||||
case class Reject(_transactionId: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(cause = "Transaction not found, or not visible.") {
|
||||
override def resources: Seq[(ErrorResource, String)] = Seq(
|
||||
(ErrorResource.TransactionId, transactionId)
|
||||
(ErrorResource.TransactionId, _transactionId)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -412,10 +420,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
cause = "The ledger configuration could not be retrieved."
|
||||
)
|
||||
|
||||
case class RejectWithMessage(message: String)(implicit
|
||||
case class RejectWithMessage(_message: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = s"The ledger configuration could not be retrieved: $message."
|
||||
cause = s"The ledger configuration could not be retrieved: ${_message}."
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -427,11 +435,9 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "PARTICIPANT_PRUNED_DATA_ACCESSED",
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateOther,
|
||||
) {
|
||||
case class Reject(message: String)(implicit
|
||||
case class Reject(override val cause: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = message
|
||||
)
|
||||
) extends LoggingTransactionErrorImpl(cause = cause)
|
||||
}
|
||||
|
||||
@Explanation(
|
||||
@ -443,10 +449,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "OFFSET_AFTER_LEDGER_END",
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateSeekAfterEnd,
|
||||
) {
|
||||
case class Reject(offsetType: String, requestedOffset: String, ledgerEnd: String)(implicit
|
||||
case class Reject(_offsetType: String, _requestedOffset: String, _ledgerEnd: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = s"$offsetType offset ($requestedOffset) is after ledger end ($ledgerEnd)"
|
||||
cause =
|
||||
s"${_offsetType} offset (${_requestedOffset}) is after ledger end (${_ledgerEnd})"
|
||||
)
|
||||
}
|
||||
|
||||
@ -459,9 +466,9 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "OFFSET_OUT_OF_RANGE",
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateOther,
|
||||
) {
|
||||
case class Reject(message: String)(implicit
|
||||
case class Reject(_message: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(cause = message)
|
||||
) extends LoggingTransactionErrorImpl(cause = _message)
|
||||
}
|
||||
|
||||
@Explanation(
|
||||
@ -488,11 +495,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
@Resolution("Inspect the reason given and correct your application.")
|
||||
object MissingField
|
||||
extends ErrorCode(id = "MISSING_FIELD", ErrorCategory.InvalidIndependentOfSystemState) {
|
||||
case class Reject(missingField: String)(implicit
|
||||
case class Reject(_missingField: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = s"The submitted command is missing a mandatory field: $missingField"
|
||||
)
|
||||
cause = s"The submitted command is missing a mandatory field: ${_missingField}"
|
||||
) {
|
||||
override def context: Map[String, String] =
|
||||
super.context ++ Map("field_name" -> _missingField)
|
||||
}
|
||||
}
|
||||
|
||||
@Explanation(
|
||||
@ -550,14 +560,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
ErrorCategory.InvalidIndependentOfSystemState,
|
||||
) {
|
||||
case class Error(
|
||||
fieldName: String,
|
||||
offsetValue: String,
|
||||
message: String,
|
||||
_fieldName: String,
|
||||
_offsetValue: String,
|
||||
_message: String,
|
||||
)(implicit
|
||||
override val loggingContext: ContextualizedErrorLogger
|
||||
) extends BaseError.Impl(
|
||||
cause =
|
||||
s"Offset in ${fieldName} not specified in hexadecimal: ${offsetValue}: ${message}"
|
||||
s"Offset in ${_fieldName} not specified in hexadecimal: ${_offsetValue}: ${_message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -633,10 +643,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "CONFIGURATION_ENTRY_REJECTED",
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateOther,
|
||||
) {
|
||||
case class Reject(message: String)(implicit
|
||||
case class Reject(_message: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = message
|
||||
cause = _message
|
||||
)
|
||||
}
|
||||
|
||||
@ -647,10 +657,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "PACKAGE_UPLOAD_REJECTED",
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateOther,
|
||||
) {
|
||||
case class Reject(message: String)(implicit
|
||||
case class Reject(_message: String)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = message
|
||||
cause = _message
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -673,15 +683,16 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
) {
|
||||
|
||||
case class Reject(
|
||||
override val definiteAnswer: Boolean = false,
|
||||
existingCommandSubmissionId: Option[String],
|
||||
_definiteAnswer: Boolean = false,
|
||||
_existingCommandSubmissionId: Option[String],
|
||||
)(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = "A command with the given command id has already been successfully processed"
|
||||
cause = "A command with the given command id has already been successfully processed",
|
||||
definiteAnswer = _definiteAnswer,
|
||||
) {
|
||||
override def context: Map[String, String] =
|
||||
super.context ++ existingCommandSubmissionId.map("existing_submission_id" -> _).toList
|
||||
super.context ++ _existingCommandSubmissionId.map("existing_submission_id" -> _).toList
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,13 +742,13 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
|
||||
) {
|
||||
|
||||
case class MultipleContractsNotFound(notFoundContractIds: Set[String])(implicit
|
||||
case class MultipleContractsNotFound(_notFoundContractIds: Set[String])(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(
|
||||
cause = s"Unknown contracts: ${notFoundContractIds.mkString("[", ", ", "]")}"
|
||||
cause = s"Unknown contracts: ${_notFoundContractIds.mkString("[", ", ", "]")}"
|
||||
) {
|
||||
override def resources: Seq[(ErrorResource, String)] = Seq(
|
||||
(ErrorResource.ContractId, notFoundContractIds.mkString("[", ", ", "]"))
|
||||
(ErrorResource.ContractId, _notFoundContractIds.mkString("[", ", ", "]"))
|
||||
)
|
||||
}
|
||||
|
||||
@ -808,19 +819,17 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateOther, // It may succeed at a later time
|
||||
) {
|
||||
case class RejectEnriched(
|
||||
message: String,
|
||||
override val cause: String,
|
||||
ledger_time: Instant,
|
||||
ledger_time_lower_bound: Instant,
|
||||
ledger_time_upper_bound: Instant,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger)
|
||||
extends LoggingTransactionErrorImpl(
|
||||
cause = s"Invalid ledger time: $message"
|
||||
)
|
||||
extends LoggingTransactionErrorImpl(cause = cause)
|
||||
|
||||
case class RejectSimple(
|
||||
details: String
|
||||
override val cause: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger)
|
||||
extends LoggingTransactionErrorImpl(cause = s"Invalid ledger time: $details")
|
||||
extends LoggingTransactionErrorImpl(cause = cause)
|
||||
}
|
||||
}
|
||||
|
||||
@ -860,12 +869,12 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
|
||||
id = "PARTY_NOT_KNOWN_ON_LEDGER",
|
||||
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
|
||||
) {
|
||||
case class Reject(parties: Set[String])(implicit
|
||||
case class Reject(_parties: Set[String])(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
) extends LoggingTransactionErrorImpl(cause = s"Parties not known on ledger: ${parties
|
||||
) extends LoggingTransactionErrorImpl(cause = s"Parties not known on ledger: ${_parties
|
||||
.mkString("[", ",", "]")}") {
|
||||
override def resources: Seq[(ErrorResource, String)] =
|
||||
parties.map((ErrorResource.Party, _)).toSeq
|
||||
_parties.map((ErrorResource.Party, _)).toSeq
|
||||
}
|
||||
|
||||
@deprecated
|
||||
|
@ -3,11 +3,8 @@
|
||||
|
||||
package com.daml.error.utils
|
||||
|
||||
import com.daml.error.ErrorCode
|
||||
import com.google.protobuf
|
||||
import com.google.rpc.{ErrorInfo, RequestInfo, ResourceInfo, RetryInfo}
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.grpc.protobuf.StatusProto
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
@ -15,7 +12,8 @@ object ErrorDetails {
|
||||
sealed trait ErrorDetail extends Product with Serializable
|
||||
|
||||
final case class ResourceInfoDetail(name: String, typ: String) extends ErrorDetail
|
||||
final case class ErrorInfoDetail(reason: String) extends ErrorDetail
|
||||
final case class ErrorInfoDetail(reason: String, metadata: Map[String, String])
|
||||
extends ErrorDetail
|
||||
final case class RetryInfoDetail(retryDelayInSeconds: Long) extends ErrorDetail
|
||||
final case class RequestInfoDetail(requestId: String) extends ErrorDetail
|
||||
|
||||
@ -26,7 +24,7 @@ object ErrorDetails {
|
||||
|
||||
case any if any.is(classOf[ErrorInfo]) =>
|
||||
val v = any.unpack(classOf[ErrorInfo])
|
||||
ErrorInfoDetail(v.getReason)
|
||||
ErrorInfoDetail(v.getReason, v.getMetadataMap.asScala.toMap)
|
||||
|
||||
case any if any.is(classOf[RetryInfo]) =>
|
||||
val v = any.unpack(classOf[RetryInfo])
|
||||
@ -38,16 +36,4 @@ object ErrorDetails {
|
||||
|
||||
case any => throw new IllegalStateException(s"Could not unpack value of: |$any|")
|
||||
}
|
||||
|
||||
def isErrorCode(exception: StatusRuntimeException)(errorCode: ErrorCode): Boolean = {
|
||||
val rpcStatus =
|
||||
StatusProto.fromStatusAndTrailers(exception.getStatus, exception.getTrailers)
|
||||
|
||||
ErrorDetails
|
||||
.from(rpcStatus.getDetailsList.asScala.toSeq)
|
||||
.exists {
|
||||
case ErrorInfoDetail(reason) => reason == errorCode.id
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,8 @@ class ErrorCodeSpec extends AnyFlatSpec with Matchers with BeforeAndAfter {
|
||||
}
|
||||
|
||||
s"$className.asGrpcErrorFromContext" should "output a GRPC error with correct status, message and metadata" in {
|
||||
val error = NotSoSeriousError.Error("some error cause")
|
||||
val contextMetadata = Map("some key" -> "some value", "another key" -> "another value")
|
||||
val error = NotSoSeriousError.Error("some error cause", contextMetadata)
|
||||
val correlationId = "12345678"
|
||||
|
||||
val actualGrpcError = error.asGrpcErrorFromContext(errorLoggingContext(Some(correlationId)))
|
||||
@ -78,13 +79,38 @@ class ErrorCodeSpec extends AnyFlatSpec with Matchers with BeforeAndAfter {
|
||||
actualGrpcError.getMessage shouldBe expectedErrorMessage
|
||||
|
||||
errorDetails should contain theSameElementsAs Seq(
|
||||
ErrorDetails.ErrorInfoDetail(NotSoSeriousError.id),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
NotSoSeriousError.id,
|
||||
Map("category" -> "1") ++ contextMetadata ++ Map("definite_answer" -> "true"),
|
||||
),
|
||||
ErrorDetails.RetryInfoDetail(TransientServerFailure.retryable.get.duration.toSeconds),
|
||||
ErrorDetails.RequestInfoDetail(correlationId),
|
||||
ErrorDetails.ResourceInfoDetail(error.resources.head._1.asString, error.resources.head._2),
|
||||
)
|
||||
}
|
||||
|
||||
s"$className.asGrpcErrorFromContext" should "not propagate security sensitive information in gRPC statuses" in {
|
||||
val error =
|
||||
SeriousError.Error("some cause", Map("some sensitive key" -> "some sensitive value"))
|
||||
val correlationId = "12345678"
|
||||
|
||||
val actualGrpcError = error.asGrpcErrorFromContext(errorLoggingContext(Some(correlationId)))
|
||||
val expectedErrorMessage =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $correlationId"
|
||||
|
||||
val actualStatus = actualGrpcError.getStatus
|
||||
val actualTrailers = actualGrpcError.getTrailers
|
||||
val actualRpcStatus = StatusProto.fromStatusAndTrailers(actualStatus, actualTrailers)
|
||||
|
||||
val errorDetails =
|
||||
ErrorDetails.from(actualRpcStatus.getDetailsList.asScala.toSeq)
|
||||
|
||||
actualStatus.getCode shouldBe io.grpc.Status.Code.INTERNAL
|
||||
actualGrpcError.getStatus.getDescription shouldBe expectedErrorMessage
|
||||
|
||||
errorDetails should contain theSameElementsAs Seq(ErrorDetails.RequestInfoDetail(correlationId))
|
||||
}
|
||||
|
||||
private def logSeriousError(
|
||||
cause: String = "the error argument",
|
||||
extra: Map[String, String] = Map.empty,
|
||||
|
@ -13,7 +13,13 @@ case object SeriousError
|
||||
extends ErrorCode("BLUE_SCREEN", ErrorCategory.SystemInternalAssumptionViolated)(
|
||||
ErrorClass.root()
|
||||
) {
|
||||
case class Error(cause: String)(implicit val loggingContext: LoggingContext) extends BaseError {
|
||||
case class Error(
|
||||
cause: String,
|
||||
override val context: Map[String, String] = Map.empty,
|
||||
override val definiteAnswerO: Option[Boolean] = Some(true),
|
||||
)(implicit
|
||||
val loggingContext: LoggingContext
|
||||
) extends BaseError {
|
||||
|
||||
/** The error code, usually passed in as implicit where the error class is defined */
|
||||
override def code: ErrorCode = SeriousError.code
|
||||
|
@ -19,7 +19,11 @@ object MildErrors
|
||||
"TEST_ROUTINE_FAILURE_PLEASE_IGNORE",
|
||||
ErrorCategory.TransientServerFailure,
|
||||
) {
|
||||
case class Error(someErrArg: String)(implicit val loggingContext: LoggingContext)
|
||||
case class Error(
|
||||
someErrArg: String,
|
||||
override val context: Map[String, String],
|
||||
override val definiteAnswerO: Option[Boolean] = Some(true),
|
||||
)(implicit val loggingContext: LoggingContext)
|
||||
extends BaseError {
|
||||
|
||||
override def code: ErrorCode = NotSoSeriousError.code
|
||||
|
@ -5,7 +5,6 @@ package com.daml.ledger.api.auth
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
|
||||
import com.google.rpc.ErrorInfo
|
||||
import io.grpc.protobuf.StatusProto
|
||||
import io.grpc.{Metadata, ServerCall, Status}
|
||||
import org.mockito.captor.ArgCaptor
|
||||
@ -42,9 +41,7 @@ class AuthorizationInterceptorSpec
|
||||
actualStatus.getDescription shouldBe "An error occurred. Please contact the operator and inquire about the request <no-correlation-id>"
|
||||
|
||||
val actualRpcStatus = StatusProto.fromStatusAndTrailers(actualStatus, actualMetadata)
|
||||
actualRpcStatus.getDetailsList.size() shouldBe 1
|
||||
val errorInfo = actualRpcStatus.getDetailsList.get(0).unpack(classOf[ErrorInfo])
|
||||
errorInfo.getReason shouldBe "INTERNAL_AUTHORIZATION_ERROR"
|
||||
actualRpcStatus.getDetailsList.size() shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
v2 = LedgerApiErrors.RequestTimeOut
|
||||
.Reject(
|
||||
"Timed out while awaiting for a completion corresponding to a command submission.",
|
||||
definiteAnswer = false,
|
||||
_definiteAnswer = false,
|
||||
)
|
||||
.asGrpcStatusFromContext,
|
||||
)
|
||||
@ -149,7 +149,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = io.grpc.Status.NOT_FOUND.asRuntimeException(),
|
||||
v2 = LedgerApiErrors.RequestValidation.NotFound.Package
|
||||
.Reject(packageId = packageId)
|
||||
.Reject(_packageId = packageId)
|
||||
.asGrpcError,
|
||||
)
|
||||
|
||||
@ -184,7 +184,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
exception
|
||||
},
|
||||
v2 = LedgerApiErrors.ConsistencyErrors.DuplicateCommand
|
||||
.Reject(existingCommandSubmissionId = existingSubmissionId)
|
||||
.Reject(_existingCommandSubmissionId = existingSubmissionId)
|
||||
.asGrpcError,
|
||||
)
|
||||
|
||||
@ -309,9 +309,9 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
v1 = invalidArgumentV1(definiteAnswer, message),
|
||||
v2 = LedgerApiErrors.RequestValidation.NonHexOffset
|
||||
.Error(
|
||||
fieldName = fieldName,
|
||||
offsetValue = offsetValue,
|
||||
message = message,
|
||||
_fieldName = fieldName,
|
||||
_offsetValue = offsetValue,
|
||||
_message = message,
|
||||
)
|
||||
.asGrpcError,
|
||||
)
|
||||
@ -385,11 +385,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = aborted(message, definiteAnswer),
|
||||
v2 = LedgerApiErrors.RequestTimeOut
|
||||
.Reject(
|
||||
message,
|
||||
// TODO error codes: How to handle None definiteAnswer?
|
||||
definiteAnswer.getOrElse(false),
|
||||
)
|
||||
.Reject(message, definiteAnswer.getOrElse(false))
|
||||
.asGrpcError,
|
||||
)
|
||||
}
|
||||
|
@ -50,16 +50,18 @@ class ErrorFactoriesSpec
|
||||
val failureReason = "some db transient failure"
|
||||
val someSqlTransientException = new SQLTransientException(failureReason)
|
||||
assertV2Error(
|
||||
SelfServiceErrorCodeFactories
|
||||
.sqlTransientException(someSqlTransientException)
|
||||
SelfServiceErrorCodeFactories.sqlTransientException(someSqlTransientException)
|
||||
)(
|
||||
expectedCode = Code.UNAVAILABLE,
|
||||
expectedMessage =
|
||||
s"INDEX_DB_SQL_TRANSIENT_ERROR(1,$truncatedCorrelationId): Processing the request failed due to a transient database error: $failureReason",
|
||||
expectedDetails = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("INDEX_DB_SQL_TRANSIENT_ERROR"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.RetryInfoDetail(1),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"INDEX_DB_SQL_TRANSIENT_ERROR",
|
||||
Map("category" -> "1", "definite_answer" -> "false"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -73,19 +75,12 @@ class ErrorFactoriesSpec
|
||||
expectedCode = Code.INTERNAL,
|
||||
expectedMessage =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
|
||||
expectedDetails = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("INDEX_DB_SQL_NON_TRANSIENT_ERROR"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
expectedDetails = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
}
|
||||
|
||||
"TrackerErrors" should {
|
||||
val errorDetails = com.google.protobuf.Any.pack[ErrorInfo](
|
||||
ErrorInfo
|
||||
.newBuilder()
|
||||
.build()
|
||||
)
|
||||
val errorDetails = com.google.protobuf.Any.pack[ErrorInfo](ErrorInfo.newBuilder().build())
|
||||
|
||||
"return failedToEnqueueCommandSubmission" in {
|
||||
val t = new Exception("message123")
|
||||
@ -100,10 +95,7 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.INTERNAL,
|
||||
v2_message =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
}
|
||||
|
||||
@ -118,7 +110,14 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"PARTICIPANT_BACKPRESSURE(2,$truncatedCorrelationId): The participant is overloaded: Some buffer is full",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("PARTICIPANT_BACKPRESSURE"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"PARTICIPANT_BACKPRESSURE",
|
||||
Map(
|
||||
"category" -> "2",
|
||||
"definite_answer" -> "false",
|
||||
"reason" -> "Some buffer is full",
|
||||
),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.RetryInfoDetail(1),
|
||||
),
|
||||
@ -138,7 +137,14 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): Some service has been shut down.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("SERVICE_NOT_RUNNING"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"SERVICE_NOT_RUNNING",
|
||||
Map(
|
||||
"category" -> "1",
|
||||
"definite_answer" -> "false",
|
||||
"service_name" -> "Some service",
|
||||
),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.RetryInfoDetail(1),
|
||||
),
|
||||
@ -158,7 +164,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): Timed out while awaiting for a completion corresponding to a command submission.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("REQUEST_TIME_OUT"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"REQUEST_TIME_OUT",
|
||||
Map("category" -> "3", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.RetryInfoDetail(1),
|
||||
),
|
||||
@ -176,10 +185,7 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.INTERNAL,
|
||||
v2_message =
|
||||
s"An error occurred. Please contact the operator and inquire about the request cor-id-12345679",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
|
||||
}
|
||||
@ -194,7 +200,10 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.NOT_FOUND,
|
||||
v2_message = s"PACKAGE_NOT_FOUND(11,$truncatedCorrelationId): Could not find package.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("PACKAGE_NOT_FOUND"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"PACKAGE_NOT_FOUND",
|
||||
Map("category" -> "11", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.ResourceInfoDetail("PACKAGE", "packageId123"),
|
||||
),
|
||||
@ -209,10 +218,7 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.INTERNAL,
|
||||
v2_message =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
}
|
||||
|
||||
@ -224,7 +230,10 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.FAILED_PRECONDITION,
|
||||
v2_message = s"CONFIGURATION_ENTRY_REJECTED(9,$truncatedCorrelationId): message123",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("CONFIGURATION_ENTRY_REJECTED"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"CONFIGURATION_ENTRY_REJECTED",
|
||||
Map("category" -> "9", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -239,7 +248,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"TRANSACTION_NOT_FOUND(11,$truncatedCorrelationId): Transaction not found, or not visible.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("TRANSACTION_NOT_FOUND"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"TRANSACTION_NOT_FOUND",
|
||||
Map("category" -> "11", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.ResourceInfoDetail("TRANSACTION_ID", "tId"),
|
||||
),
|
||||
@ -255,7 +267,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"DUPLICATE_COMMAND(10,$truncatedCorrelationId): A command with the given command id has already been successfully processed",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("DUPLICATE_COMMAND"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"DUPLICATE_COMMAND",
|
||||
Map("category" -> "10", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -269,10 +284,7 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.PERMISSION_DENIED,
|
||||
v2_message =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("PERMISSION_DENIED"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
}
|
||||
|
||||
@ -286,7 +298,10 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.DEADLINE_EXCEEDED,
|
||||
v2_message = s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): message123",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("REQUEST_TIME_OUT"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"REQUEST_TIME_OUT",
|
||||
Map("category" -> "3", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.RetryInfoDetail(1),
|
||||
),
|
||||
@ -308,7 +323,7 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"NON_HEXADECIMAL_OFFSET(8,$truncatedCorrelationId): Offset in fieldName123 not specified in hexadecimal: offsetValue123: message123",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("NON_HEXADECIMAL_OFFSET"),
|
||||
ErrorDetails.ErrorInfoDetail("NON_HEXADECIMAL_OFFSET", Map("category" -> "8")),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -323,7 +338,10 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.OUT_OF_RANGE,
|
||||
v2_message = s"OFFSET_AFTER_LEDGER_END(12,$truncatedCorrelationId): $expectedMessage",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("OFFSET_AFTER_LEDGER_END"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"OFFSET_AFTER_LEDGER_END",
|
||||
Map("category" -> "12", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -337,7 +355,10 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.FAILED_PRECONDITION,
|
||||
v2_message = s"OFFSET_OUT_OF_RANGE(9,$truncatedCorrelationId): message123",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("OFFSET_OUT_OF_RANGE"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"OFFSET_OUT_OF_RANGE",
|
||||
Map("category" -> "9", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -351,10 +372,7 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.UNAUTHENTICATED,
|
||||
v2_message =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("UNAUTHENTICATED"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
}
|
||||
|
||||
@ -368,10 +386,7 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.INTERNAL,
|
||||
v2_message =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("INTERNAL_AUTHORIZATION_ERROR"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
}
|
||||
|
||||
@ -393,7 +408,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"LEDGER_CONFIGURATION_NOT_FOUND(11,$truncatedCorrelationId): The ledger configuration could not be retrieved.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("LEDGER_CONFIGURATION_NOT_FOUND"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"LEDGER_CONFIGURATION_NOT_FOUND",
|
||||
Map("category" -> "11", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -419,8 +437,9 @@ class ErrorFactoriesSpec
|
||||
"return an invalid deduplication period error" in {
|
||||
val errorDetailMessage = "message"
|
||||
val field = "field"
|
||||
val maxDeduplicationDuration = Duration.ofSeconds(5)
|
||||
assertVersionedError(
|
||||
_.invalidDeduplicationDuration(field, errorDetailMessage, None, Duration.ofSeconds(5))
|
||||
_.invalidDeduplicationDuration(field, errorDetailMessage, None, maxDeduplicationDuration)
|
||||
)(
|
||||
v1_code = Code.INVALID_ARGUMENT,
|
||||
v1_message = s"Invalid field $field: $errorDetailMessage",
|
||||
@ -429,7 +448,14 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"INVALID_DEDUPLICATION_PERIOD(9,$truncatedCorrelationId): The submitted command had an invalid deduplication period: $errorDetailMessage",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("INVALID_DEDUPLICATION_PERIOD"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"INVALID_DEDUPLICATION_PERIOD",
|
||||
Map(
|
||||
"category" -> "9",
|
||||
"definite_answer" -> "false",
|
||||
"max_deduplication_duration" -> maxDeduplicationDuration.toString,
|
||||
),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -442,16 +468,20 @@ class ErrorFactoriesSpec
|
||||
(Some(false), Seq(definiteAnswers(false))),
|
||||
)
|
||||
|
||||
val fieldName = "my field"
|
||||
forEvery(testCases) { (definiteAnswer, expectedDetails) =>
|
||||
assertVersionedError(_.invalidField("my field", "my message", definiteAnswer))(
|
||||
assertVersionedError(_.invalidField(fieldName, "my message", definiteAnswer))(
|
||||
v1_code = Code.INVALID_ARGUMENT,
|
||||
v1_message = "Invalid field my field: my message",
|
||||
v1_message = "Invalid field " + fieldName + ": my message",
|
||||
v1_details = expectedDetails,
|
||||
v2_code = Code.INVALID_ARGUMENT,
|
||||
v2_message =
|
||||
s"INVALID_FIELD(8,$truncatedCorrelationId): The submitted command has a field with invalid value: Invalid field my field: my message",
|
||||
s"INVALID_FIELD(8,$truncatedCorrelationId): The submitted command has a field with invalid value: Invalid field $fieldName: my message",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("INVALID_FIELD"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"INVALID_FIELD",
|
||||
Map("category" -> "8", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -476,7 +506,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"LEDGER_ID_MISMATCH(11,$truncatedCorrelationId): Ledger ID 'received' not found. Actual Ledger ID is 'expected'.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("LEDGER_ID_MISMATCH"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"LEDGER_ID_MISMATCH",
|
||||
Map("category" -> "11", "definite_answer" -> "true"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -499,7 +532,10 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.FAILED_PRECONDITION,
|
||||
v2_message = s"PARTICIPANT_PRUNED_DATA_ACCESSED(9,$truncatedCorrelationId): my message",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("PARTICIPANT_PRUNED_DATA_ACCESSED"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"PARTICIPANT_PRUNED_DATA_ACCESSED",
|
||||
Map("category" -> "9", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -513,10 +549,7 @@ class ErrorFactoriesSpec
|
||||
v2_code = Code.INTERNAL,
|
||||
v2_message =
|
||||
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
|
||||
)
|
||||
}
|
||||
|
||||
@ -537,7 +570,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName has been shut down.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("SERVICE_NOT_RUNNING"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"SERVICE_NOT_RUNNING",
|
||||
Map("category" -> "1", "definite_answer" -> "false", "service_name" -> serviceName),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.RetryInfoDetail(1),
|
||||
),
|
||||
@ -557,7 +593,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName is currently being reset.",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("SERVICE_NOT_RUNNING"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"SERVICE_NOT_RUNNING",
|
||||
Map("category" -> "1", "definite_answer" -> "false", "service_name" -> serviceName),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
ErrorDetails.RetryInfoDetail(1),
|
||||
),
|
||||
@ -565,6 +604,8 @@ class ErrorFactoriesSpec
|
||||
}
|
||||
|
||||
"return a missingField error" in {
|
||||
val fieldName = "my field"
|
||||
|
||||
val testCases = Table(
|
||||
("definite answer", "expected details"),
|
||||
(None, Seq.empty),
|
||||
@ -572,15 +613,18 @@ class ErrorFactoriesSpec
|
||||
)
|
||||
|
||||
forEvery(testCases) { (definiteAnswer, expectedDetails) =>
|
||||
assertVersionedError(_.missingField("my field", definiteAnswer))(
|
||||
assertVersionedError(_.missingField(fieldName, definiteAnswer))(
|
||||
v1_code = Code.INVALID_ARGUMENT,
|
||||
v1_message = "Missing field: my field",
|
||||
v1_message = "Missing field: " + fieldName,
|
||||
v1_details = expectedDetails,
|
||||
v2_code = Code.INVALID_ARGUMENT,
|
||||
v2_message =
|
||||
s"MISSING_FIELD(8,$truncatedCorrelationId): The submitted command is missing a mandatory field: my field",
|
||||
s"MISSING_FIELD(8,$truncatedCorrelationId): The submitted command is missing a mandatory field: $fieldName",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("MISSING_FIELD"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"MISSING_FIELD",
|
||||
Map("category" -> "8", "definite_answer" -> "false", "field_name" -> fieldName),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -603,7 +647,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("INVALID_ARGUMENT"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"INVALID_ARGUMENT",
|
||||
Map("category" -> "8", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
@ -626,7 +673,10 @@ class ErrorFactoriesSpec
|
||||
v2_message =
|
||||
s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message",
|
||||
v2_details = Seq[ErrorDetails.ErrorDetail](
|
||||
ErrorDetails.ErrorInfoDetail("INVALID_ARGUMENT"),
|
||||
ErrorDetails.ErrorInfoDetail(
|
||||
"INVALID_ARGUMENT",
|
||||
Map("category" -> "8", "definite_answer" -> "false"),
|
||||
),
|
||||
expectedCorrelationIdRequestInfo,
|
||||
),
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ import akka.Done
|
||||
import akka.actor.Cancellable
|
||||
import akka.stream._
|
||||
import akka.stream.scaladsl.{Keep, Sink, Source}
|
||||
import com.daml.error.definitions.IndexErrors
|
||||
import com.daml.error.definitions.IndexErrors.IndexDbException
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.health.HealthStatus
|
||||
import com.daml.ledger.offset.Offset
|
||||
@ -147,12 +147,11 @@ private[platform] object ReadOnlySqlLedger {
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): Future[LedgerId] = {
|
||||
// If the index database is not yet fully initialized,
|
||||
// querying for the ledger ID will throw different errors,
|
||||
// depending on the database, and how far the initialization is.
|
||||
val isRetryable: PartialFunction[Throwable, Boolean] = {
|
||||
// If the index database is not yet fully initialized,
|
||||
// querying for the ledger ID will throw different errors,
|
||||
// depending on the database, and how far the initialization is.
|
||||
case IndexErrors.DatabaseErrors.SqlTransientError(_) => true
|
||||
case IndexErrors.DatabaseErrors.SqlNonTransientError(_) => true
|
||||
case _: IndexDbException => true
|
||||
case _: LedgerIdNotFoundException => true
|
||||
case _: MismatchException.LedgerId => false
|
||||
case _ => false
|
||||
|
@ -132,7 +132,7 @@ class ConversionsSpec extends AsyncWordSpec with Matchers {
|
||||
v1expectedCode = Status.Code.ABORTED.value(),
|
||||
v1expectedMessage = "Invalid ledger time: Too late.",
|
||||
v2expectedCode = Status.Code.FAILED_PRECONDITION.value(),
|
||||
v2expectedMessage = "INVALID_LEDGER_TIME(9,0): Invalid ledger time: Too late.",
|
||||
v2expectedMessage = "INVALID_LEDGER_TIME(9,0): Too late.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ class RejectionSpec extends AnyWordSpec with Matchers {
|
||||
errorInfo shouldBe com.google.rpc.error_details.ErrorInfo(
|
||||
reason = "LEDGER_CONFIGURATION_NOT_FOUND",
|
||||
metadata = Map(
|
||||
"message" -> "Cannot validate ledger time",
|
||||
"category" -> "11",
|
||||
"definite_answer" -> "false",
|
||||
),
|
||||
@ -104,7 +103,7 @@ class RejectionSpec extends AnyWordSpec with Matchers {
|
||||
Rejection.InvalidLedgerTime(outOfRange).toStateRejectionReason(errorFactoriesV2)
|
||||
|
||||
actualRejectionReason.code shouldBe Status.Code.FAILED_PRECONDITION.value()
|
||||
actualRejectionReason.message shouldBe "INVALID_LEDGER_TIME(9,12345678): Invalid ledger time: Ledger time 2021-07-20T09:30:00Z outside of range [2021-07-20T09:00:00Z, 2021-07-20T09:10:00Z]"
|
||||
actualRejectionReason.message shouldBe "INVALID_LEDGER_TIME(9,12345678): Ledger time 2021-07-20T09:30:00Z outside of range [2021-07-20T09:00:00Z, 2021-07-20T09:10:00Z]"
|
||||
|
||||
val (errorInfo, requestInfo) = extractDetails(actualRejectionReason.status.details)
|
||||
|
||||
@ -115,7 +114,6 @@ class RejectionSpec extends AnyWordSpec with Matchers {
|
||||
"ledger_time_lower_bound" -> "2021-07-20T09:00:00Z",
|
||||
"ledger_time_upper_bound" -> "2021-07-20T09:10:00Z",
|
||||
"category" -> "9",
|
||||
"message" -> "Ledger time 2021-07-20T09:30:00Z outside of range [2021-07-20T09:00:00Z, 2021-07-20T09:10:00Z]",
|
||||
"definite_answer" -> "false",
|
||||
),
|
||||
)
|
||||
|
@ -383,7 +383,7 @@ final class SqlLedgerSpec
|
||||
case Seq(Completion(`commandId1`, Some(status), _, _, _, _, _)) =>
|
||||
status.code should be(Status.Code.FAILED_PRECONDITION.value)
|
||||
status.message should be(
|
||||
s"INVALID_LEDGER_TIME(9,$submissionId): Invalid ledger time: Ledger time 2021-09-01T18:05:00Z outside of range [2021-09-01T17:59:50Z, 2021-09-01T18:00:30Z]"
|
||||
s"INVALID_LEDGER_TIME(9,$submissionId): Ledger time 2021-09-01T18:05:00Z outside of range [2021-09-01T17:59:50Z, 2021-09-01T18:00:30Z]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user