diff --git a/src/main/kotlin/org/wfanet/measurement/common/api/BUILD.bazel b/src/main/kotlin/org/wfanet/measurement/common/api/BUILD.bazel index 0b978c0acf2..99c5bf8552e 100644 --- a/src/main/kotlin/org/wfanet/measurement/common/api/BUILD.bazel +++ b/src/main/kotlin/org/wfanet/measurement/common/api/BUILD.bazel @@ -56,3 +56,8 @@ kt_jvm_library( "@wfa_common_jvm//src/main/kotlin/org/wfanet/measurement/common/crypto:hashing", ], ) + +kt_jvm_library( + name = "resource_ids", + srcs = ["ResourceIds.kt"], +) diff --git a/src/main/kotlin/org/wfanet/measurement/common/api/ResourceIds.kt b/src/main/kotlin/org/wfanet/measurement/common/api/ResourceIds.kt new file mode 100644 index 00000000000..68c31d5f5cc --- /dev/null +++ b/src/main/kotlin/org/wfanet/measurement/common/api/ResourceIds.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2024 The Cross-Media Measurement Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wfanet.measurement.common.api + +object ResourceIds { + /** + * [Regex] matching the resource ID format defined in [AIP-122](https://google.aip.dev/122). + * + * This is the same as [RFC_1034_REGEX] without upper-case letters. + */ + val AIP_122_REGEX = Regex("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$") + + /** [Regex] matching RFC-1034 labels. */ + val RFC_1034_REGEX = Regex("^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$") +} diff --git a/src/main/kotlin/org/wfanet/measurement/common/grpc/ErrorInfo.kt b/src/main/kotlin/org/wfanet/measurement/common/grpc/ErrorInfo.kt index f34cf367933..00d347feec8 100644 --- a/src/main/kotlin/org/wfanet/measurement/common/grpc/ErrorInfo.kt +++ b/src/main/kotlin/org/wfanet/measurement/common/grpc/ErrorInfo.kt @@ -16,7 +16,9 @@ package org.wfanet.measurement.common.grpc +import com.google.protobuf.Any import com.google.rpc.ErrorInfo +import com.google.rpc.status import io.grpc.Metadata import io.grpc.Status import io.grpc.StatusException @@ -28,11 +30,42 @@ val StatusException.errorInfo: ErrorInfo? get() { return getErrorInfo(this.status, this.trailers) } + +/** [ErrorInfo] from status details. */ val StatusRuntimeException.errorInfo: ErrorInfo? get() { return getErrorInfo(this.status, this.trailers) } +object Errors { + /** Builds a [StatusRuntimeException] with the specified [errorInfo]. */ + fun buildStatusRuntimeException( + code: Status.Code, + message: String, + errorInfo: ErrorInfo, + cause: Throwable? = null, + ): StatusRuntimeException { + val statusProto = status { + this.code = code.value() + this.message = message + details += Any.pack(errorInfo) + } + + // Unpack exception to add cause. + // TODO(grpc/grpc-java#10230): Use new API when available. + val exception = StatusProto.toStatusRuntimeException(statusProto) + return exception.status + .run { + if (cause != null) { + withCause(cause) + } else { + this + } + } + .asRuntimeException(exception.trailers) + } +} + private fun getErrorInfo(status: Status, trailers: Metadata?): ErrorInfo? { val errorInfoFullName = ErrorInfo.getDescriptor().fullName val errorInfoPacked = diff --git a/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/BUILD.bazel b/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/BUILD.bazel index 9baf93c1146..6bcaa753ca9 100644 --- a/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/BUILD.bazel +++ b/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/BUILD.bazel @@ -11,6 +11,7 @@ kt_jvm_library( srcs = glob(["*.kt"]), deps = [ "//src/main/kotlin/org/wfanet/measurement/common/api:etags", + "//src/main/kotlin/org/wfanet/measurement/common/grpc:error_info", "//src/main/kotlin/org/wfanet/measurement/common/identity", "//src/main/proto/google/rpc:error_details_kt_jvm_proto", "//src/main/proto/google/rpc:status_kt_jvm_proto", diff --git a/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/KingdomInternalException.kt b/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/KingdomInternalException.kt index f8702f66119..c1c21fc9a6c 100644 --- a/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/KingdomInternalException.kt +++ b/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/gcloud/spanner/common/KingdomInternalException.kt @@ -14,13 +14,11 @@ package org.wfanet.measurement.kingdom.deploy.gcloud.spanner.common -import com.google.protobuf.Any import com.google.rpc.errorInfo -import com.google.rpc.status import com.google.type.Date import io.grpc.Status import io.grpc.StatusRuntimeException -import io.grpc.protobuf.StatusProto +import org.wfanet.measurement.common.grpc.Errors import org.wfanet.measurement.common.identity.ExternalId import org.wfanet.measurement.common.identity.InternalId import org.wfanet.measurement.common.toLocalDate @@ -48,23 +46,12 @@ sealed class KingdomInternalException : Exception { statusCode: Status.Code, message: String = this.message!!, ): StatusRuntimeException { - val statusProto = status { - code = statusCode.value() - this.message = message - details += - Any.pack( - errorInfo { - reason = this@KingdomInternalException.code.toString() - domain = ErrorCode.getDescriptor().fullName - metadata.putAll(context) - } - ) + val errorInfo = errorInfo { + reason = this@KingdomInternalException.code.toString() + domain = ErrorCode.getDescriptor().fullName + metadata.putAll(context) } - - // Unpack exception to add cause. - // TODO(grpc/grpc-java#10230): Use new API when available. - val exception = StatusProto.toStatusRuntimeException(statusProto) - return exception.status.withCause(this).asRuntimeException(exception.trailers) + return Errors.buildStatusRuntimeException(statusCode, message, errorInfo, this) } override fun toString(): String { diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/BUILD.bazel b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/BUILD.bazel index fc750cdc976..4eceaab637f 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/BUILD.bazel +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/BUILD.bazel @@ -183,6 +183,7 @@ kt_jvm_library( "//src/main/kotlin/org/wfanet/measurement/api:api_key_constants", "//src/main/kotlin/org/wfanet/measurement/api:public_api_version", "//src/main/kotlin/org/wfanet/measurement/api/v2alpha:packed_messages", + "//src/main/kotlin/org/wfanet/measurement/common/api:resource_ids", "//src/main/kotlin/org/wfanet/measurement/measurementconsumer/stats:measurement_statistics", "//src/main/kotlin/org/wfanet/measurement/measurementconsumer/stats:metric_statistics", "//src/main/kotlin/org/wfanet/measurement/measurementconsumer/stats:variances", @@ -236,6 +237,7 @@ kt_jvm_library( ":reporting_principal", ":resource_key", "//imports/java/org/projectnessie/cel", + "//src/main/kotlin/org/wfanet/measurement/common/api:resource_ids", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha:metric_spec_defaults", "//src/main/proto/wfa/measurement/internal/reporting/v2:metric_calculation_specs_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/reporting/v2alpha:metric_calculation_specs_service_kt_jvm_grpc_proto", @@ -251,6 +253,7 @@ kt_jvm_library( srcs = ["ReportingSetsService.kt"], deps = [ "//src/main/kotlin/org/wfanet/measurement/api:public_api_version", + "//src/main/kotlin/org/wfanet/measurement/common/api:resource_ids", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha:principal_server_interceptor", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha:proto_conversions", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha:reporting_principal", @@ -275,6 +278,7 @@ kt_jvm_library( "//imports/java/org/projectnessie/cel", "//src/main/kotlin/org/wfanet/measurement/api:api_key_constants", "//src/main/kotlin/org/wfanet/measurement/api:public_api_version", + "//src/main/kotlin/org/wfanet/measurement/common/api:resource_ids", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api:encryption_key_pair_store", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api:submit_batch_requests", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha:metric_spec_defaults", @@ -315,6 +319,7 @@ kt_jvm_library( "//imports/java/org/projectnessie/cel", "//src/main/kotlin/org/wfanet/measurement/api:api_key_constants", "//src/main/kotlin/org/wfanet/measurement/api:public_api_version", + "//src/main/kotlin/org/wfanet/measurement/common/api:resource_ids", "//src/main/kotlin/org/wfanet/measurement/reporting/service/api:submit_batch_requests", "//src/main/proto/wfa/measurement/api/v2alpha:data_providers_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/api/v2alpha:event_groups_service_kt_jvm_grpc_proto", diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricCalculationSpecsService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricCalculationSpecsService.kt index 36e4366ddf1..a90e927ae59 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricCalculationSpecsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricCalculationSpecsService.kt @@ -22,6 +22,7 @@ import io.grpc.StatusException import kotlin.random.Random import org.projectnessie.cel.Env import org.wfanet.measurement.api.v2alpha.MeasurementConsumerKey +import org.wfanet.measurement.common.api.ResourceIds import org.wfanet.measurement.common.base64UrlDecode import org.wfanet.measurement.common.base64UrlEncode import org.wfanet.measurement.common.grpc.failGrpc @@ -287,7 +288,7 @@ class MetricCalculationSpecsService( } companion object { - private val RESOURCE_ID_REGEX = Regex("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$") + private val RESOURCE_ID_REGEX = ResourceIds.AIP_122_REGEX private const val MIN_PAGE_SIZE = 1 private const val DEFAULT_PAGE_SIZE = 50 private const val MAX_PAGE_SIZE = 1000 diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt index f80d523fb87..c16882f34f2 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt @@ -95,6 +95,7 @@ import org.wfanet.measurement.api.v2alpha.requisitionSpec import org.wfanet.measurement.api.v2alpha.unpack import org.wfanet.measurement.api.withAuthenticationKey import org.wfanet.measurement.common.LoadingCache +import org.wfanet.measurement.common.api.ResourceIds import org.wfanet.measurement.common.base64UrlDecode import org.wfanet.measurement.common.base64UrlEncode import org.wfanet.measurement.common.crypto.Hashing @@ -1717,7 +1718,7 @@ class MetricsService( } companion object { - private val RESOURCE_ID_REGEX = Regex("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$") + private val RESOURCE_ID_REGEX = ResourceIds.AIP_122_REGEX private val logger: Logger = Logger.getLogger(this::class.java.name) } } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportSchedulesService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportSchedulesService.kt index 2b6391e54cf..03a0cbfe09b 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportSchedulesService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportSchedulesService.kt @@ -45,6 +45,7 @@ import org.wfanet.measurement.api.v2alpha.MeasurementConsumerKey import org.wfanet.measurement.api.v2alpha.getDataProviderRequest import org.wfanet.measurement.api.v2alpha.getEventGroupRequest import org.wfanet.measurement.api.withAuthenticationKey +import org.wfanet.measurement.common.api.ResourceIds import org.wfanet.measurement.common.base64UrlDecode import org.wfanet.measurement.common.base64UrlEncode import org.wfanet.measurement.common.grpc.failGrpc @@ -465,7 +466,7 @@ class ReportSchedulesService( companion object { private val ENV: Env = buildCelEnvironment(ReportSchedule.getDefaultInstance()) - private val RESOURCE_ID_REGEX = Regex("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$") + private val RESOURCE_ID_REGEX = ResourceIds.AIP_122_REGEX private val REQUEST_ID_REGEX = Regex("^[a-zA-Z0-9]{1,36}$") private const val MIN_PAGE_SIZE = 1 diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportingSetsService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportingSetsService.kt index 44bf6bad965..2f6205d522b 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportingSetsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportingSetsService.kt @@ -22,6 +22,7 @@ import kotlin.math.min import kotlinx.coroutines.flow.toList import org.wfanet.measurement.api.v2alpha.EventGroupKey as CmmsEventGroupKey import org.wfanet.measurement.api.v2alpha.MeasurementConsumerKey +import org.wfanet.measurement.common.api.ResourceIds import org.wfanet.measurement.common.base64UrlDecode import org.wfanet.measurement.common.base64UrlEncode import org.wfanet.measurement.common.grpc.failGrpc @@ -406,7 +407,7 @@ class ReportingSetsService(private val internalReportingSetsStub: ReportingSetsC } companion object { - private val RESOURCE_ID_REGEX = Regex("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$") + private val RESOURCE_ID_REGEX = ResourceIds.AIP_122_REGEX } } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsService.kt index 7fa258021e1..af7e83f8f6e 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsService.kt @@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import org.projectnessie.cel.Env import org.wfanet.measurement.api.v2alpha.MeasurementConsumerKey +import org.wfanet.measurement.common.api.ResourceIds import org.wfanet.measurement.common.base64UrlDecode import org.wfanet.measurement.common.base64UrlEncode import org.wfanet.measurement.common.grpc.failGrpc @@ -860,7 +861,7 @@ class ReportsService( } companion object { - private val RESOURCE_ID_REGEX = Regex("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$") + private val RESOURCE_ID_REGEX = ResourceIds.AIP_122_REGEX private val ENV: Env = buildCelEnvironment(Report.getDefaultInstance()) } }