diff --git a/BUILD.bazel b/BUILD.bazel index a60441b09..f9a482dea 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -114,7 +114,6 @@ test_suite( "//nativelink-config:doc_test", "//nativelink-error:doc_test", "//nativelink-macro:doc_test", - "//nativelink-proto:doc_test", "//nativelink-scheduler:doc_test", "//nativelink-service:doc_test", "//nativelink-store:doc_test", diff --git a/nativelink-proto/BUILD.bazel b/nativelink-proto/BUILD.bazel index 494061740..e621d0227 100644 --- a/nativelink-proto/BUILD.bazel +++ b/nativelink-proto/BUILD.bazel @@ -4,7 +4,6 @@ load( "@rules_rust//rust:defs.bzl", "rust_binary", "rust_doc", - "rust_doc_test", "rust_library", ) @@ -98,6 +97,7 @@ genrule( "google/protobuf/empty.proto", "google/protobuf/timestamp.proto", "google/protobuf/wrappers.proto", + "google/rpc/error_details.proto", "google/rpc/status.proto", "src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto", "src/main/java/com/google/devtools/build/lib/packages/metrics/package_load_metrics.proto", @@ -186,9 +186,3 @@ rust_doc( crate = ":nativelink-proto", visibility = ["//visibility:public"], ) - -rust_doc_test( - name = "doc_test", - timeout = "short", - crate = ":nativelink-proto", -) diff --git a/nativelink-proto/Cargo.toml b/nativelink-proto/Cargo.toml index 8933e924c..e683b6e87 100644 --- a/nativelink-proto/Cargo.toml +++ b/nativelink-proto/Cargo.toml @@ -5,6 +5,7 @@ name = "nativelink-proto" version = "1.1.0" [lib] +doctest = false # because some of the generated protos have things that look like doctests but break name = "nativelink_proto" path = "genproto/lib.rs" diff --git a/nativelink-proto/genproto/google.rpc.pb.rs b/nativelink-proto/genproto/google.rpc.pb.rs index efe259aa9..184db6250 100644 --- a/nativelink-proto/genproto/google.rpc.pb.rs +++ b/nativelink-proto/genproto/google.rpc.pb.rs @@ -35,3 +35,373 @@ pub struct Status { #[prost(message, repeated, tag = "3")] pub details: ::prost::alloc::vec::Vec<::prost_types::Any>, } +/// Describes the cause of the error with structured details. +/// +/// Example of an error when contacting the "pubsub.googleapis.com" API when it +/// is not enabled: +/// +/// { "reason": "API_DISABLED" +/// "domain": "googleapis.com" +/// "metadata": { +/// "resource": "projects/123", +/// "service": "pubsub.googleapis.com" +/// } +/// } +/// +/// This response indicates that the pubsub.googleapis.com API is not enabled. +/// +/// Example of an error that is returned when attempting to create a Spanner +/// instance in a region that is out of stock: +/// +/// { "reason": "STOCKOUT" +/// "domain": "spanner.googleapis.com", +/// "metadata": { +/// "availableRegions": "us-central1,us-east2" +/// } +/// } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ErrorInfo { + /// The reason of the error. This is a constant value that identifies the + /// proximate cause of the error. Error reasons are unique within a particular + /// domain of errors. This should be at most 63 characters and match a + /// regular expression of `[A-Z][A-Z0-9_]+\[A-Z0-9\]`, which represents + /// UPPER_SNAKE_CASE. + #[prost(string, tag = "1")] + pub reason: ::prost::alloc::string::String, + /// The logical grouping to which the "reason" belongs. The error domain + /// is typically the registered service name of the tool or product that + /// generates the error. Example: "pubsub.googleapis.com". If the error is + /// generated by some common infrastructure, the error domain must be a + /// globally unique value that identifies the infrastructure. For Google API + /// infrastructure, the error domain is "googleapis.com". + #[prost(string, tag = "2")] + pub domain: ::prost::alloc::string::String, + /// Additional structured details about this error. + /// + /// Keys must match a regular expression of `[a-z][a-zA-Z0-9-_]+` but should + /// ideally be lowerCamelCase. Also, they must be limited to 64 characters in + /// length. When identifying the current value of an exceeded limit, the units + /// should be contained in the key, not the value. For example, rather than + /// `{"instanceLimit": "100/request"}`, should be returned as, + /// `{"instanceLimitPerRequest": "100"}`, if the client exceeds the number of + /// instances that can be created in a single (batch) request. + #[prost(map = "string, string", tag = "3")] + pub metadata: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, +} +/// Describes when the clients can retry a failed request. Clients could ignore +/// the recommendation here or retry when this information is missing from error +/// responses. +/// +/// It's always recommended that clients should use exponential backoff when +/// retrying. +/// +/// Clients should wait until `retry_delay` amount of time has passed since +/// receiving the error response before retrying. If retrying requests also +/// fail, clients should use an exponential backoff scheme to gradually increase +/// the delay between retries based on `retry_delay`, until either a maximum +/// number of retries have been reached or a maximum retry delay cap has been +/// reached. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct RetryInfo { + /// Clients should wait at least this long between retrying the same request. + #[prost(message, optional, tag = "1")] + pub retry_delay: ::core::option::Option<::prost_types::Duration>, +} +/// Describes additional debugging info. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DebugInfo { + /// The stack trace entries indicating where the error occurred. + #[prost(string, repeated, tag = "1")] + pub stack_entries: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Additional debugging information provided by the server. + #[prost(string, tag = "2")] + pub detail: ::prost::alloc::string::String, +} +/// Describes how a quota check failed. +/// +/// For example if a daily limit was exceeded for the calling project, +/// a service could respond with a QuotaFailure detail containing the project +/// id and the description of the quota limit that was exceeded. If the +/// calling project hasn't enabled the service in the developer console, then +/// a service could respond with the project id and set `service_disabled` +/// to true. +/// +/// Also see RetryInfo and Help types for other details about handling a +/// quota failure. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QuotaFailure { + /// Describes all quota violations. + #[prost(message, repeated, tag = "1")] + pub violations: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `QuotaFailure`. +pub mod quota_failure { + /// A message type used to describe a single quota violation. For example, a + /// daily quota or a custom quota that was exceeded. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Violation { + /// The subject on which the quota check failed. + /// For example, "clientip:" or "project:". + #[prost(string, tag = "1")] + pub subject: ::prost::alloc::string::String, + /// A description of how the quota check failed. Clients can use this + /// description to find more about the quota configuration in the service's + /// public documentation, or find the relevant quota limit to adjust through + /// developer console. + /// + /// For example: "Service disabled" or "Daily Limit for read operations + /// exceeded". + #[prost(string, tag = "2")] + pub description: ::prost::alloc::string::String, + /// The API Service from which the `QuotaFailure.Violation` orginates. In + /// some cases, Quota issues originate from an API Service other than the one + /// that was called. In other words, a dependency of the called API Service + /// could be the cause of the `QuotaFailure`, and this field would have the + /// dependency API service name. + /// + /// For example, if the called API is Kubernetes Engine API + /// (container.googleapis.com), and a quota violation occurs in the + /// Kubernetes Engine API itself, this field would be + /// "container.googleapis.com". On the other hand, if the quota violation + /// occurs when the Kubernetes Engine API creates VMs in the Compute Engine + /// API (compute.googleapis.com), this field would be + /// "compute.googleapis.com". + #[prost(string, tag = "3")] + pub api_service: ::prost::alloc::string::String, + /// The metric of the violated quota. A quota metric is a named counter to + /// measure usage, such as API requests or CPUs. When an activity occurs in a + /// service, such as Virtual Machine allocation, one or more quota metrics + /// may be affected. + /// + /// For example, "compute.googleapis.com/cpus_per_vm_family", + /// "storage.googleapis.com/internet_egress_bandwidth". + #[prost(string, tag = "4")] + pub quota_metric: ::prost::alloc::string::String, + /// The id of the violated quota. Also know as "limit name", this is the + /// unique identifier of a quota in the context of an API service. + /// + /// For example, "CPUS-PER-VM-FAMILY-per-project-region". + #[prost(string, tag = "5")] + pub quota_id: ::prost::alloc::string::String, + /// The dimensions of the violated quota. Every non-global quota is enforced + /// on a set of dimensions. While quota metric defines what to count, the + /// dimensions specify for what aspects the counter should be increased. + /// + /// For example, the quota "CPUs per region per VM family" enforces a limit + /// on the metric "compute.googleapis.com/cpus_per_vm_family" on dimensions + /// "region" and "vm_family". And if the violation occurred in region + /// "us-central1" and for VM family "n1", the quota_dimensions would be, + /// + /// { + /// "region": "us-central1", + /// "vm_family": "n1", + /// } + /// + /// When a quota is enforced globally, the quota_dimensions would always be + /// empty. + #[prost(map = "string, string", tag = "6")] + pub quota_dimensions: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, + /// The enforced quota value at the time of the `QuotaFailure`. + /// + /// For example, if the enforced quota value at the time of the + /// `QuotaFailure` on the number of CPUs is "10", then the value of this + /// field would reflect this quantity. + #[prost(int64, tag = "7")] + pub quota_value: i64, + /// The new quota value being rolled out at the time of the violation. At the + /// completion of the rollout, this value will be enforced in place of + /// quota_value. If no rollout is in progress at the time of the violation, + /// this field is not set. + /// + /// For example, if at the time of the violation a rollout is in progress + /// changing the number of CPUs quota from 10 to 20, 20 would be the value of + /// this field. + #[prost(int64, optional, tag = "8")] + pub future_quota_value: ::core::option::Option, + } +} +/// Describes what preconditions have failed. +/// +/// For example, if an RPC failed because it required the Terms of Service to be +/// acknowledged, it could list the terms of service violation in the +/// PreconditionFailure message. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PreconditionFailure { + /// Describes all precondition violations. + #[prost(message, repeated, tag = "1")] + pub violations: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `PreconditionFailure`. +pub mod precondition_failure { + /// A message type used to describe a single precondition failure. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Violation { + /// The type of PreconditionFailure. We recommend using a service-specific + /// enum type to define the supported precondition violation subjects. For + /// example, "TOS" for "Terms of Service violation". + #[prost(string, tag = "1")] + pub r#type: ::prost::alloc::string::String, + /// The subject, relative to the type, that failed. + /// For example, "google.com/cloud" relative to the "TOS" type would indicate + /// which terms of service is being referenced. + #[prost(string, tag = "2")] + pub subject: ::prost::alloc::string::String, + /// A description of how the precondition failed. Developers can use this + /// description to understand how to fix the failure. + /// + /// For example: "Terms of service not accepted". + #[prost(string, tag = "3")] + pub description: ::prost::alloc::string::String, + } +} +/// Describes violations in a client request. This error type focuses on the +/// syntactic aspects of the request. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BadRequest { + /// Describes all violations in a client request. + #[prost(message, repeated, tag = "1")] + pub field_violations: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `BadRequest`. +pub mod bad_request { + /// A message type used to describe a single bad request field. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct FieldViolation { + /// A path that leads to a field in the request body. The value will be a + /// sequence of dot-separated identifiers that identify a protocol buffer + /// field. + /// + /// Consider the following: + /// + /// message CreateContactRequest { + /// message EmailAddress { + /// enum Type { + /// TYPE_UNSPECIFIED = 0; + /// HOME = 1; + /// WORK = 2; + /// } + /// + /// optional string email = 1; + /// repeated EmailType type = 2; + /// } + /// + /// string full_name = 1; + /// repeated EmailAddress email_addresses = 2; + /// } + /// + /// In this example, in proto `field` could take one of the following values: + /// + /// * `full_name` for a violation in the `full_name` value + /// * `email_addresses\[0\].email` for a violation in the `email` field of the + /// first `email_addresses` message + /// * `email_addresses\[2\].type\[1\]` for a violation in the second `type` + /// value in the third `email_addresses` message. + /// + /// In JSON, the same values are represented as: + /// + /// * `fullName` for a violation in the `fullName` value + /// * `emailAddresses\[0\].email` for a violation in the `email` field of the + /// first `emailAddresses` message + /// * `emailAddresses\[2\].type\[1\]` for a violation in the second `type` + /// value in the third `emailAddresses` message. + #[prost(string, tag = "1")] + pub field: ::prost::alloc::string::String, + /// A description of why the request element is bad. + #[prost(string, tag = "2")] + pub description: ::prost::alloc::string::String, + /// The reason of the field-level error. This is a constant value that + /// identifies the proximate cause of the field-level error. It should + /// uniquely identify the type of the FieldViolation within the scope of the + /// google.rpc.ErrorInfo.domain. This should be at most 63 + /// characters and match a regular expression of `[A-Z][A-Z0-9_]+\[A-Z0-9\]`, + /// which represents UPPER_SNAKE_CASE. + #[prost(string, tag = "3")] + pub reason: ::prost::alloc::string::String, + /// Provides a localized error message for field-level errors that is safe to + /// return to the API consumer. + #[prost(message, optional, tag = "4")] + pub localized_message: ::core::option::Option, + } +} +/// Contains metadata about the request that clients can attach when filing a bug +/// or providing other forms of feedback. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RequestInfo { + /// An opaque string that should only be interpreted by the service generating + /// it. For example, it can be used to identify requests in the service's logs. + #[prost(string, tag = "1")] + pub request_id: ::prost::alloc::string::String, + /// Any data that was used to serve this request. For example, an encrypted + /// stack trace that can be sent back to the service provider for debugging. + #[prost(string, tag = "2")] + pub serving_data: ::prost::alloc::string::String, +} +/// Describes the resource that is being accessed. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResourceInfo { + /// A name for the type of resource being accessed, e.g. "sql table", + /// "cloud storage bucket", "file", "Google calendar"; or the type URL + /// of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic". + #[prost(string, tag = "1")] + pub resource_type: ::prost::alloc::string::String, + /// The name of the resource being accessed. For example, a shared calendar + /// name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current + /// error is + /// [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED]. + #[prost(string, tag = "2")] + pub resource_name: ::prost::alloc::string::String, + /// The owner of the resource (optional). + /// For example, "user:" or "project:". + #[prost(string, tag = "3")] + pub owner: ::prost::alloc::string::String, + /// Describes what error is encountered when accessing this resource. + /// For example, updating a cloud project may require the `writer` permission + /// on the developer console project. + #[prost(string, tag = "4")] + pub description: ::prost::alloc::string::String, +} +/// Provides links to documentation or for performing an out of band action. +/// +/// For example, if a quota check failed with an error indicating the calling +/// project hasn't enabled the accessed service, this can contain a URL pointing +/// directly to the right place in the developer console to flip the bit. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Help { + /// URL(s) pointing to additional information on handling the current error. + #[prost(message, repeated, tag = "1")] + pub links: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `Help`. +pub mod help { + /// Describes a URL link. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Link { + /// Describes what the link offers. + #[prost(string, tag = "1")] + pub description: ::prost::alloc::string::String, + /// The URL of the link. + #[prost(string, tag = "2")] + pub url: ::prost::alloc::string::String, + } +} +/// Provides a localized error message that is safe to return to the user +/// which can be attached to an RPC error. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LocalizedMessage { + /// The locale used following the specification defined at + /// + /// Examples are: "en-US", "fr-CH", "es-MX" + #[prost(string, tag = "1")] + pub locale: ::prost::alloc::string::String, + /// The localized error message in the above locale. + #[prost(string, tag = "2")] + pub message: ::prost::alloc::string::String, +} diff --git a/nativelink-proto/google/rpc/error_details.proto b/nativelink-proto/google/rpc/error_details.proto new file mode 100644 index 000000000..43b7d04d2 --- /dev/null +++ b/nativelink-proto/google/rpc/error_details.proto @@ -0,0 +1,363 @@ +// Copyright 2026 Google LLC +// +// 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. + +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/duration.proto"; + +option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails"; +option java_multiple_files = true; +option java_outer_classname = "ErrorDetailsProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// Describes the cause of the error with structured details. +// +// Example of an error when contacting the "pubsub.googleapis.com" API when it +// is not enabled: +// +// { "reason": "API_DISABLED" +// "domain": "googleapis.com" +// "metadata": { +// "resource": "projects/123", +// "service": "pubsub.googleapis.com" +// } +// } +// +// This response indicates that the pubsub.googleapis.com API is not enabled. +// +// Example of an error that is returned when attempting to create a Spanner +// instance in a region that is out of stock: +// +// { "reason": "STOCKOUT" +// "domain": "spanner.googleapis.com", +// "metadata": { +// "availableRegions": "us-central1,us-east2" +// } +// } +message ErrorInfo { + // The reason of the error. This is a constant value that identifies the + // proximate cause of the error. Error reasons are unique within a particular + // domain of errors. This should be at most 63 characters and match a + // regular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents + // UPPER_SNAKE_CASE. + string reason = 1; + + // The logical grouping to which the "reason" belongs. The error domain + // is typically the registered service name of the tool or product that + // generates the error. Example: "pubsub.googleapis.com". If the error is + // generated by some common infrastructure, the error domain must be a + // globally unique value that identifies the infrastructure. For Google API + // infrastructure, the error domain is "googleapis.com". + string domain = 2; + + // Additional structured details about this error. + // + // Keys must match a regular expression of `[a-z][a-zA-Z0-9-_]+` but should + // ideally be lowerCamelCase. Also, they must be limited to 64 characters in + // length. When identifying the current value of an exceeded limit, the units + // should be contained in the key, not the value. For example, rather than + // `{"instanceLimit": "100/request"}`, should be returned as, + // `{"instanceLimitPerRequest": "100"}`, if the client exceeds the number of + // instances that can be created in a single (batch) request. + map metadata = 3; +} + +// Describes when the clients can retry a failed request. Clients could ignore +// the recommendation here or retry when this information is missing from error +// responses. +// +// It's always recommended that clients should use exponential backoff when +// retrying. +// +// Clients should wait until `retry_delay` amount of time has passed since +// receiving the error response before retrying. If retrying requests also +// fail, clients should use an exponential backoff scheme to gradually increase +// the delay between retries based on `retry_delay`, until either a maximum +// number of retries have been reached or a maximum retry delay cap has been +// reached. +message RetryInfo { + // Clients should wait at least this long between retrying the same request. + google.protobuf.Duration retry_delay = 1; +} + +// Describes additional debugging info. +message DebugInfo { + // The stack trace entries indicating where the error occurred. + repeated string stack_entries = 1; + + // Additional debugging information provided by the server. + string detail = 2; +} + +// Describes how a quota check failed. +// +// For example if a daily limit was exceeded for the calling project, +// a service could respond with a QuotaFailure detail containing the project +// id and the description of the quota limit that was exceeded. If the +// calling project hasn't enabled the service in the developer console, then +// a service could respond with the project id and set `service_disabled` +// to true. +// +// Also see RetryInfo and Help types for other details about handling a +// quota failure. +message QuotaFailure { + // A message type used to describe a single quota violation. For example, a + // daily quota or a custom quota that was exceeded. + message Violation { + // The subject on which the quota check failed. + // For example, "clientip:" or "project:". + string subject = 1; + + // A description of how the quota check failed. Clients can use this + // description to find more about the quota configuration in the service's + // public documentation, or find the relevant quota limit to adjust through + // developer console. + // + // For example: "Service disabled" or "Daily Limit for read operations + // exceeded". + string description = 2; + + // The API Service from which the `QuotaFailure.Violation` orginates. In + // some cases, Quota issues originate from an API Service other than the one + // that was called. In other words, a dependency of the called API Service + // could be the cause of the `QuotaFailure`, and this field would have the + // dependency API service name. + // + // For example, if the called API is Kubernetes Engine API + // (container.googleapis.com), and a quota violation occurs in the + // Kubernetes Engine API itself, this field would be + // "container.googleapis.com". On the other hand, if the quota violation + // occurs when the Kubernetes Engine API creates VMs in the Compute Engine + // API (compute.googleapis.com), this field would be + // "compute.googleapis.com". + string api_service = 3; + + // The metric of the violated quota. A quota metric is a named counter to + // measure usage, such as API requests or CPUs. When an activity occurs in a + // service, such as Virtual Machine allocation, one or more quota metrics + // may be affected. + // + // For example, "compute.googleapis.com/cpus_per_vm_family", + // "storage.googleapis.com/internet_egress_bandwidth". + string quota_metric = 4; + + // The id of the violated quota. Also know as "limit name", this is the + // unique identifier of a quota in the context of an API service. + // + // For example, "CPUS-PER-VM-FAMILY-per-project-region". + string quota_id = 5; + + // The dimensions of the violated quota. Every non-global quota is enforced + // on a set of dimensions. While quota metric defines what to count, the + // dimensions specify for what aspects the counter should be increased. + // + // For example, the quota "CPUs per region per VM family" enforces a limit + // on the metric "compute.googleapis.com/cpus_per_vm_family" on dimensions + // "region" and "vm_family". And if the violation occurred in region + // "us-central1" and for VM family "n1", the quota_dimensions would be, + // + // { + // "region": "us-central1", + // "vm_family": "n1", + // } + // + // When a quota is enforced globally, the quota_dimensions would always be + // empty. + map quota_dimensions = 6; + + // The enforced quota value at the time of the `QuotaFailure`. + // + // For example, if the enforced quota value at the time of the + // `QuotaFailure` on the number of CPUs is "10", then the value of this + // field would reflect this quantity. + int64 quota_value = 7; + + // The new quota value being rolled out at the time of the violation. At the + // completion of the rollout, this value will be enforced in place of + // quota_value. If no rollout is in progress at the time of the violation, + // this field is not set. + // + // For example, if at the time of the violation a rollout is in progress + // changing the number of CPUs quota from 10 to 20, 20 would be the value of + // this field. + optional int64 future_quota_value = 8; + } + + // Describes all quota violations. + repeated Violation violations = 1; +} + +// Describes what preconditions have failed. +// +// For example, if an RPC failed because it required the Terms of Service to be +// acknowledged, it could list the terms of service violation in the +// PreconditionFailure message. +message PreconditionFailure { + // A message type used to describe a single precondition failure. + message Violation { + // The type of PreconditionFailure. We recommend using a service-specific + // enum type to define the supported precondition violation subjects. For + // example, "TOS" for "Terms of Service violation". + string type = 1; + + // The subject, relative to the type, that failed. + // For example, "google.com/cloud" relative to the "TOS" type would indicate + // which terms of service is being referenced. + string subject = 2; + + // A description of how the precondition failed. Developers can use this + // description to understand how to fix the failure. + // + // For example: "Terms of service not accepted". + string description = 3; + } + + // Describes all precondition violations. + repeated Violation violations = 1; +} + +// Describes violations in a client request. This error type focuses on the +// syntactic aspects of the request. +message BadRequest { + // A message type used to describe a single bad request field. + message FieldViolation { + // A path that leads to a field in the request body. The value will be a + // sequence of dot-separated identifiers that identify a protocol buffer + // field. + // + // Consider the following: + // + // message CreateContactRequest { + // message EmailAddress { + // enum Type { + // TYPE_UNSPECIFIED = 0; + // HOME = 1; + // WORK = 2; + // } + // + // optional string email = 1; + // repeated EmailType type = 2; + // } + // + // string full_name = 1; + // repeated EmailAddress email_addresses = 2; + // } + // + // In this example, in proto `field` could take one of the following values: + // + // * `full_name` for a violation in the `full_name` value + // * `email_addresses[0].email` for a violation in the `email` field of the + // first `email_addresses` message + // * `email_addresses[2].type[1]` for a violation in the second `type` + // value in the third `email_addresses` message. + // + // In JSON, the same values are represented as: + // + // * `fullName` for a violation in the `fullName` value + // * `emailAddresses[0].email` for a violation in the `email` field of the + // first `emailAddresses` message + // * `emailAddresses[2].type[1]` for a violation in the second `type` + // value in the third `emailAddresses` message. + string field = 1; + + // A description of why the request element is bad. + string description = 2; + + // The reason of the field-level error. This is a constant value that + // identifies the proximate cause of the field-level error. It should + // uniquely identify the type of the FieldViolation within the scope of the + // google.rpc.ErrorInfo.domain. This should be at most 63 + // characters and match a regular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, + // which represents UPPER_SNAKE_CASE. + string reason = 3; + + // Provides a localized error message for field-level errors that is safe to + // return to the API consumer. + LocalizedMessage localized_message = 4; + } + + // Describes all violations in a client request. + repeated FieldViolation field_violations = 1; +} + +// Contains metadata about the request that clients can attach when filing a bug +// or providing other forms of feedback. +message RequestInfo { + // An opaque string that should only be interpreted by the service generating + // it. For example, it can be used to identify requests in the service's logs. + string request_id = 1; + + // Any data that was used to serve this request. For example, an encrypted + // stack trace that can be sent back to the service provider for debugging. + string serving_data = 2; +} + +// Describes the resource that is being accessed. +message ResourceInfo { + // A name for the type of resource being accessed, e.g. "sql table", + // "cloud storage bucket", "file", "Google calendar"; or the type URL + // of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic". + string resource_type = 1; + + // The name of the resource being accessed. For example, a shared calendar + // name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current + // error is + // [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED]. + string resource_name = 2; + + // The owner of the resource (optional). + // For example, "user:" or "project:". + string owner = 3; + + // Describes what error is encountered when accessing this resource. + // For example, updating a cloud project may require the `writer` permission + // on the developer console project. + string description = 4; +} + +// Provides links to documentation or for performing an out of band action. +// +// For example, if a quota check failed with an error indicating the calling +// project hasn't enabled the accessed service, this can contain a URL pointing +// directly to the right place in the developer console to flip the bit. +message Help { + // Describes a URL link. + message Link { + // Describes what the link offers. + string description = 1; + + // The URL of the link. + string url = 2; + } + + // URL(s) pointing to additional information on handling the current error. + repeated Link links = 1; +} + +// Provides a localized error message that is safe to return to the user +// which can be attached to an RPC error. +message LocalizedMessage { + // The locale used following the specification defined at + // https://www.rfc-editor.org/rfc/bcp/bcp47.txt. + // Examples are: "en-US", "fr-CH", "es-MX" + string locale = 1; + + // The localized error message in the above locale. + string message = 2; +} diff --git a/nativelink-service/src/execution_server.rs b/nativelink-service/src/execution_server.rs index 8041b3641..dfdd38806 100644 --- a/nativelink-service/src/execution_server.rs +++ b/nativelink-service/src/execution_server.rs @@ -36,18 +36,20 @@ use nativelink_proto::google::longrunning::{ CancelOperationRequest, DeleteOperationRequest, GetOperationRequest, ListOperationsRequest, ListOperationsResponse, Operation, WaitOperationRequest, }; -use nativelink_proto::google::rpc::Status as GrpcStatusProto; +use nativelink_proto::google::rpc::{ + PreconditionFailure, Status as GrpcStatusProto, precondition_failure, +}; use nativelink_store::ac_utils::get_and_decode_digest; use nativelink_store::store_manager::StoreManager; use nativelink_util::action_messages::{ ActionInfo, ActionUniqueKey, ActionUniqueQualifier, DEFAULT_EXECUTION_PRIORITY, OperationId, + TypeUrl, }; -use nativelink_util::common::DigestInfo; +use nativelink_util::common::{self, DigestInfo}; use nativelink_util::digest_hasher::{DigestHasherFunc, make_ctx_for_hash_func}; use nativelink_util::operation_state_manager::{ ActionStateResult, ClientStateManager, OperationFilter, }; -use nativelink_util::precondition_failure; use nativelink_util::store_trait::{Store, StoreLike}; use opentelemetry::context::FutureExt; use prost::Message as _; @@ -84,11 +86,11 @@ fn missing_blobs_failed_precondition( missing: &[(DigestInfo, &'static str)], summary: &str, ) -> Status { - let pf = precondition_failure::PreconditionFailure { + let pf = PreconditionFailure { violations: missing .iter() .map(|(d, ctx)| precondition_failure::Violation { - r#type: precondition_failure::VIOLATION_TYPE_MISSING.to_string(), + r#type: common::VIOLATION_TYPE_MISSING.to_string(), // Per REv2, the subject for a missing-blob violation is // `blobs//` so the client knows exactly // which digest to re-upload. @@ -103,7 +105,7 @@ fn missing_blobs_failed_precondition( pf.encode(&mut pf_buf) .expect("encoding prost message into Vec cannot fail"); let any = prost_types::Any { - type_url: precondition_failure::TYPE_URL.to_string(), + type_url: PreconditionFailure::TYPE_URL.to_string(), value: pf_buf, }; diff --git a/nativelink-service/tests/execution_server_test.rs b/nativelink-service/tests/execution_server_test.rs index a0cb43c0b..8db7930c2 100644 --- a/nativelink-service/tests/execution_server_test.rs +++ b/nativelink-service/tests/execution_server_test.rs @@ -31,14 +31,14 @@ use nativelink_proto::google::longrunning::{ CancelOperationRequest, DeleteOperationRequest, GetOperationRequest, ListOperationsRequest, WaitOperationRequest, }; -use nativelink_proto::google::rpc::Status as GrpcStatusProto; +use nativelink_proto::google::rpc::{PreconditionFailure, Status as GrpcStatusProto}; use nativelink_scheduler::mock_scheduler::MockActionScheduler; use nativelink_service::execution_server::ExecutionServer; use nativelink_store::ac_utils::serialize_and_upload_message; use nativelink_store::default_store_factory::store_factory; use nativelink_store::store_manager::StoreManager; use nativelink_util::action_messages::{ - ActionInfo, ActionResult, ActionStage, ActionState, OperationId, + ActionInfo, ActionResult, ActionStage, ActionState, OperationId, TypeUrl, }; use nativelink_util::common::DigestInfo; use nativelink_util::digest_hasher::DigestHasherFunc; @@ -46,7 +46,6 @@ use nativelink_util::operation_state_manager::{ ActionStateResult, ActionStateResultStream, ClientStateManager, }; use nativelink_util::origin_event::OriginMetadata; -use nativelink_util::precondition_failure; use nativelink_util::store_trait::StoreLike; use prost::Message as _; use tonic::{Code as TonicCode, Request}; @@ -346,7 +345,7 @@ fn blob_subject(d: &DigestInfo) -> String { /// in `grpc-status-details-bin`. Returns the inner `PreconditionFailure`. fn decode_precondition_failure( status: &tonic::Status, -) -> Result> { +) -> Result> { let outer = GrpcStatusProto::decode(status.details())?; assert_eq!( outer.code, @@ -356,12 +355,10 @@ fn decode_precondition_failure( assert_eq!(outer.details.len(), 1, "expected exactly one detail"); assert_eq!( outer.details[0].type_url, - precondition_failure::TYPE_URL, + PreconditionFailure::TYPE_URL, "detail type_url must match PreconditionFailure", ); - Ok(precondition_failure::PreconditionFailure::decode( - &*outer.details[0].value, - )?) + Ok(PreconditionFailure::decode(&*outer.details[0].value)?) } async fn upload_action( diff --git a/nativelink-util/BUILD.bazel b/nativelink-util/BUILD.bazel index d6ce41876..935a5253f 100644 --- a/nativelink-util/BUILD.bazel +++ b/nativelink-util/BUILD.bazel @@ -31,7 +31,6 @@ rust_library( "src/origin_event.rs", "src/origin_event_publisher.rs", "src/platform_properties.rs", - "src/precondition_failure.rs", "src/proto_stream_utils.rs", "src/resource_info.rs", "src/retry.rs", diff --git a/nativelink-util/src/action_messages.rs b/nativelink-util/src/action_messages.rs index b701642ce..4494005d0 100644 --- a/nativelink-util/src/action_messages.rs +++ b/nativelink-util/src/action_messages.rs @@ -32,7 +32,7 @@ use nativelink_proto::build::bazel::remote::execution::v2::{ }; use nativelink_proto::google::longrunning::Operation; use nativelink_proto::google::longrunning::operation::Result as LongRunningResult; -use nativelink_proto::google::rpc::Status; +use nativelink_proto::google::rpc::{PreconditionFailure, Status, precondition_failure}; use prost::Message; use prost::bytes::Bytes; use prost_types::Any; @@ -41,7 +41,7 @@ use serde::{Deserialize, Serialize}; use tonic::Code; use uuid::Uuid; -use crate::common::{DigestInfo, HashMapExt, VecExt}; +use crate::common::{self, DigestInfo, HashMapExt, VecExt}; use crate::digest_hasher::DigestHasherFunc; /// Default priority remote execution jobs will get when not provided. @@ -843,8 +843,6 @@ impl From<&ActionStage> for execution_stage::Value { } } -use crate::precondition_failure; - /// Build a `google.rpc.Status` of code `FAILED_PRECONDITION` whose /// details carry a `PreconditionFailure` naming the missing blob. /// @@ -852,9 +850,9 @@ use crate::precondition_failure; /// `missing_blobs_failed_precondition` — both produce the `REv2` /// subject format `blobs/{hash}/{size}` that Bazel auto-retries on. fn missing_blob_failed_precondition_status(err: &Error, hash: &str, size: i64) -> Status { - let pf = precondition_failure::PreconditionFailure { + let pf = PreconditionFailure { violations: vec![precondition_failure::Violation { - r#type: precondition_failure::VIOLATION_TYPE_MISSING.to_string(), + r#type: common::VIOLATION_TYPE_MISSING.to_string(), // REv2-mandated subject format for missing-blob violations. subject: format!("blobs/{hash}/{size}"), description: err.message_string(), @@ -864,7 +862,7 @@ fn missing_blob_failed_precondition_status(err: &Error, hash: &str, size: i64) - pf.encode(&mut buf) .expect("encoding prost message into Vec cannot fail"); let any = Any { - type_url: precondition_failure::TYPE_URL.to_string(), + type_url: PreconditionFailure::TYPE_URL.to_string(), value: buf, }; Status { @@ -1114,7 +1112,7 @@ impl TryFrom for ActionStage { } // TODO: Should be able to remove this after tokio-rs/prost#299 -trait TypeUrl: Message { +pub trait TypeUrl: Message { const TYPE_URL: &'static str; } @@ -1128,6 +1126,10 @@ impl TypeUrl for ExecuteOperationMetadata { "type.googleapis.com/build.bazel.remote.execution.v2.ExecuteOperationMetadata"; } +impl TypeUrl for PreconditionFailure { + const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.PreconditionFailure"; +} + fn from_any(message: &Any) -> Result where T: TypeUrl + Default, diff --git a/nativelink-util/src/common.rs b/nativelink-util/src/common.rs index e209f6bce..1aef8e4a6 100644 --- a/nativelink-util/src/common.rs +++ b/nativelink-util/src/common.rs @@ -494,3 +494,6 @@ pub fn make_temp_path(data: &str) -> String { data ); } + +// Constant for PreconditionFailure +pub const VIOLATION_TYPE_MISSING: &str = "MISSING"; diff --git a/nativelink-util/src/lib.rs b/nativelink-util/src/lib.rs index aaab57a97..1a0c38c5f 100644 --- a/nativelink-util/src/lib.rs +++ b/nativelink-util/src/lib.rs @@ -32,7 +32,6 @@ pub mod operation_state_manager; pub mod origin_event; pub mod origin_event_publisher; pub mod platform_properties; -pub mod precondition_failure; pub mod proto_stream_utils; pub mod resource_info; pub mod retry; diff --git a/nativelink-util/src/precondition_failure.rs b/nativelink-util/src/precondition_failure.rs deleted file mode 100644 index 103bca31c..000000000 --- a/nativelink-util/src/precondition_failure.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2024 The NativeLink Authors. All rights reserved. -// -// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// See LICENSE file for details -// -// 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. - -//! Inline definition of `google.rpc.PreconditionFailure`. -//! -//! Bazel's `RemoteSpawnRunner` reads this proto out of an -//! `ExecuteResponse.status.details` (or, for synchronous Execute -//! errors, the gRPC `Status.details` carried via -//! `grpc-status-details-bin`) of a `FAILED_PRECONDITION` response -//! and, for violations of type `MISSING`, automatically re-uploads -//! the named blobs and retries the Execute call. Without this detail -//! Bazel surfaces the failure as a hard build error. - -/// `google.rpc.PreconditionFailure`. -#[derive(Clone, Eq, PartialEq, ::prost::Message)] -pub struct PreconditionFailure { - #[prost(message, repeated, tag = "1")] - pub violations: ::prost::alloc::vec::Vec, -} - -/// `google.rpc.PreconditionFailure.Violation`. -#[derive(Clone, Eq, PartialEq, ::prost::Message)] -pub struct Violation { - #[prost(string, tag = "1")] - pub r#type: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub subject: ::prost::alloc::string::String, - #[prost(string, tag = "3")] - pub description: ::prost::alloc::string::String, -} - -pub const TYPE_URL: &str = "type.googleapis.com/google.rpc.PreconditionFailure"; -pub const VIOLATION_TYPE_MISSING: &str = "MISSING"; diff --git a/nativelink-util/tests/action_messages_test.rs b/nativelink-util/tests/action_messages_test.rs index efff82bc4..f295ca08d 100644 --- a/nativelink-util/tests/action_messages_test.rs +++ b/nativelink-util/tests/action_messages_test.rs @@ -4,13 +4,13 @@ use std::time::SystemTime; use hex::FromHex; use nativelink_error::{Code, Error, ErrorContext, ResultExt, make_err}; use nativelink_macro::nativelink_test; +use nativelink_proto::google::rpc::PreconditionFailure; use nativelink_util::action_messages::{ ActionInfo, ActionResult, ActionUniqueKey, ActionUniqueQualifier, ExecutionMetadata, - INTERNAL_ERROR_EXIT_CODE, to_execute_response, + INTERNAL_ERROR_EXIT_CODE, TypeUrl, to_execute_response, }; use nativelink_util::common::DigestInfo; use nativelink_util::digest_hasher::DigestHasherFunc; -use nativelink_util::precondition_failure; use prost::Message as _; fn make_key() -> ActionUniqueKey { @@ -105,8 +105,8 @@ fn assert_missing_blob_status(status: &nativelink_proto::google::rpc::Status, di "missing-blob errors should be surfaced as FAILED_PRECONDITION", ); assert_eq!(status.details.len(), 1, "expected exactly one detail Any"); - assert_eq!(status.details[0].type_url, precondition_failure::TYPE_URL); - let pf = precondition_failure::PreconditionFailure::decode(&*status.details[0].value) + assert_eq!(status.details[0].type_url, PreconditionFailure::TYPE_URL); + let pf = PreconditionFailure::decode(&*status.details[0].value) .expect("decoding PreconditionFailure must succeed"); assert_eq!(pf.violations.len(), 1, "expected one MISSING violation"); assert_eq!(pf.violations[0].r#type, "MISSING");