diff --git a/Sources/GRPCProtobuf/Errors/ErrorDetails+CustomStringConvertible.swift b/Sources/GRPCProtobuf/Errors/ErrorDetails+CustomStringConvertible.swift index ee1ed3e..f2d19a4 100644 --- a/Sources/GRPCProtobuf/Errors/ErrorDetails+CustomStringConvertible.swift +++ b/Sources/GRPCProtobuf/Errors/ErrorDetails+CustomStringConvertible.swift @@ -64,7 +64,7 @@ extension ErrorDetails.DebugInfo: CustomStringConvertible { @available(gRPCSwiftProtobuf 2.0, *) extension ErrorDetails.QuotaFailure.Violation: CustomStringConvertible { public var description: String { - "\(Self.self)(subject: \"\(self.subject)\", violationDescription: \"\(self.violationDescription)\")" + "\(Self.self)(subject: \"\(self.subject)\", violationDescription: \"\(self.violationDescription)\", apiService: \"\(self.apiService)\", quotaMetric: \"\(self.quotaMetric)\", quotaID: \"\(self.quotaID)\", quotaDimensions: [\(self.quotaDimensions.map({ "\"\($0)\": \"\($1)\"" }).joined(separator: ", "))], quotaValue: \(self.quotaValue), futureQuotaValue: \(self.futureQuotaValue.map({ String(describing: $0) }) ?? "nil"))" } } @@ -78,7 +78,7 @@ extension ErrorDetails.PreconditionFailure.Violation: CustomStringConvertible { @available(gRPCSwiftProtobuf 2.0, *) extension ErrorDetails.BadRequest.FieldViolation: CustomStringConvertible { public var description: String { - "\(Self.self)(field: \"\(self.field)\", violationDescription: \"\(self.violationDescription)\")" + "\(Self.self)(field: \"\(self.field)\", violationDescription: \"\(self.violationDescription)\", reason: \"\(self.reason)\", localizedMessage: \(self.localizedMessage.map({ String(describing: $0) }) ?? "nil"))" } } diff --git a/Sources/GRPCProtobuf/Errors/ErrorDetails+Types.swift b/Sources/GRPCProtobuf/Errors/ErrorDetails+Types.swift index 28ba92c..7a48055 100644 --- a/Sources/GRPCProtobuf/Errors/ErrorDetails+Types.swift +++ b/Sources/GRPCProtobuf/Errors/ErrorDetails+Types.swift @@ -205,14 +205,123 @@ extension ErrorDetails { /// exceeded". public var violationDescription: String - public init(subject: String, description: 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". + @available(gRPCSwiftProtobuf 2.1, *) + public var apiService: 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". + @available(gRPCSwiftProtobuf 2.1, *) + public var quotaMetric: 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". + @available(gRPCSwiftProtobuf 2.1, *) + public var quotaID: 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. + @available(gRPCSwiftProtobuf 2.1, *) + public var quotaDimensions: [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. + @available(gRPCSwiftProtobuf 2.1, *) + public var quotaValue: Int + + /// 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. + @available(gRPCSwiftProtobuf 2.1, *) + public var futureQuotaValue: Int? + + public init( + subject: String, + description: String + ) { self.subject = subject self.violationDescription = description + self.apiService = "" + self.quotaMetric = "" + self.quotaID = "" + self.quotaDimensions = [:] + self.quotaValue = 0 + self.futureQuotaValue = nil + } + + @available(gRPCSwiftProtobuf 2.1, *) + public init( + subject: String, + description: String, + apiService: String, + quotaMetric: String, + quotaID: String, + quotaDimensions: [String: String], + quotaValue: Int, + futureQuotaValue: Int? + ) { + self.subject = subject + self.violationDescription = description + self.apiService = apiService + self.quotaMetric = quotaMetric + self.quotaID = quotaID + self.quotaDimensions = quotaDimensions + self.quotaValue = quotaValue + self.futureQuotaValue = futureQuotaValue } init(_ storage: Google_Rpc_QuotaFailure.Violation) { self.subject = storage.subject self.violationDescription = storage.description_p + self.apiService = storage.apiService + self.quotaMetric = storage.quotaMetric + self.quotaID = storage.quotaID + self.quotaDimensions = storage.quotaDimensions + self.quotaValue = Int(storage.quotaValue) + if storage.hasFutureQuotaValue { + self.futureQuotaValue = Int(storage.futureQuotaValue) + } } } @@ -225,6 +334,14 @@ extension ErrorDetails { .with { $0.subject = violation.subject $0.description_p = violation.violationDescription + $0.apiService = violation.apiService + $0.quotaMetric = violation.quotaMetric + $0.quotaID = violation.quotaID + $0.quotaDimensions = violation.quotaDimensions + $0.quotaValue = Int64(violation.quotaValue) + if let futureQuotaValue = violation.futureQuotaValue { + $0.futureQuotaValue = Int64(futureQuotaValue) + } } } } @@ -368,14 +485,52 @@ extension ErrorDetails { /// A description of why the request element is bad. public var violationDescription: String - public init(field: String, description: 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. + @available(gRPCSwiftProtobuf 2.1, *) + public var reason: String + + /// Provides a localized error message for field-level errors that is safe to + /// return to the API consumer. + @available(gRPCSwiftProtobuf 2.1, *) + public var localizedMessage: LocalizedMessage? + + public init( + field: String, + description: String + ) { self.field = field self.violationDescription = description + self.reason = "" + self.localizedMessage = nil + } + + @available(gRPCSwiftProtobuf 2.1, *) + public init( + field: String, + description: String, + reason: String, + localizedMessage: LocalizedMessage? + ) { + self.field = field + self.violationDescription = description + self.reason = reason + self.localizedMessage = localizedMessage } init(_ storage: Google_Rpc_BadRequest.FieldViolation) { self.field = storage.field self.violationDescription = storage.description_p + self.reason = storage.reason + if storage.hasLocalizedMessage { + self.localizedMessage = LocalizedMessage(storage: storage.localizedMessage) + } else { + self.localizedMessage = nil + } } } @@ -388,6 +543,10 @@ extension ErrorDetails { .with { $0.field = violation.field $0.description_p = violation.violationDescription + $0.reason = violation.reason + if let localizedMessage = violation.localizedMessage { + $0.localizedMessage = localizedMessage.storage + } } } } diff --git a/Sources/GRPCProtobuf/Errors/Generated/code.pb.swift b/Sources/GRPCProtobuf/Errors/Generated/code.pb.swift index a115ac9..23111e8 100644 --- a/Sources/GRPCProtobuf/Errors/Generated/code.pb.swift +++ b/Sources/GRPCProtobuf/Errors/Generated/code.pb.swift @@ -8,7 +8,7 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/GRPCProtobuf/Errors/Generated/error_details.pb.swift b/Sources/GRPCProtobuf/Errors/Generated/error_details.pb.swift index bef4ad4..c529899 100644 --- a/Sources/GRPCProtobuf/Errors/Generated/error_details.pb.swift +++ b/Sources/GRPCProtobuf/Errors/Generated/error_details.pb.swift @@ -8,7 +8,7 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -80,11 +80,12 @@ struct Google_Rpc_ErrorInfo: Sendable { /// Additional structured details about this error. /// - /// Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in + /// 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 + /// `{"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. var metadata: Dictionary = [:] @@ -187,9 +188,83 @@ struct Google_Rpc_QuotaFailure: Sendable { /// exceeded". var description_p: 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". + var apiService: 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". + var quotaMetric: 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". + var quotaID: 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. + var quotaDimensions: Dictionary = [:] + + /// 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. + var quotaValue: Int64 = 0 + + /// 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. + var futureQuotaValue: Int64 { + get {return _futureQuotaValue ?? 0} + set {_futureQuotaValue = newValue} + } + /// Returns true if `futureQuotaValue` has been explicitly set. + var hasFutureQuotaValue: Bool {return self._futureQuotaValue != nil} + /// Clears the value of `futureQuotaValue`. Subsequent reads from it will return its default value. + mutating func clearFutureQuotaValue() {self._futureQuotaValue = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} + + fileprivate var _futureQuotaValue: Int64? = nil } init() {} @@ -300,9 +375,30 @@ struct Google_Rpc_BadRequest: Sendable { /// A description of why the request element is bad. var description_p: 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. + var reason: String = String() + + /// Provides a localized error message for field-level errors that is safe to + /// return to the API consumer. + var localizedMessage: Google_Rpc_LocalizedMessage { + get {return _localizedMessage ?? Google_Rpc_LocalizedMessage()} + set {_localizedMessage = newValue} + } + /// Returns true if `localizedMessage` has been explicitly set. + var hasLocalizedMessage: Bool {return self._localizedMessage != nil} + /// Clears the value of `localizedMessage`. Subsequent reads from it will return its default value. + mutating func clearLocalizedMessage() {self._localizedMessage = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} + + fileprivate var _localizedMessage: Google_Rpc_LocalizedMessage? = nil } init() {} @@ -574,6 +670,12 @@ extension Google_Rpc_QuotaFailure.Violation: SwiftProtobuf.Message, SwiftProtobu static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "subject"), 2: .same(proto: "description"), + 3: .standard(proto: "api_service"), + 4: .standard(proto: "quota_metric"), + 5: .standard(proto: "quota_id"), + 6: .standard(proto: "quota_dimensions"), + 7: .standard(proto: "quota_value"), + 8: .standard(proto: "future_quota_value"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -584,24 +686,58 @@ extension Google_Rpc_QuotaFailure.Violation: SwiftProtobuf.Message, SwiftProtobu switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.subject) }() case 2: try { try decoder.decodeSingularStringField(value: &self.description_p) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.apiService) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.quotaMetric) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.quotaID) }() + case 6: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.quotaDimensions) }() + case 7: try { try decoder.decodeSingularInt64Field(value: &self.quotaValue) }() + case 8: try { try decoder.decodeSingularInt64Field(value: &self._futureQuotaValue) }() default: break } } } func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.subject.isEmpty { try visitor.visitSingularStringField(value: self.subject, fieldNumber: 1) } if !self.description_p.isEmpty { try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 2) } + if !self.apiService.isEmpty { + try visitor.visitSingularStringField(value: self.apiService, fieldNumber: 3) + } + if !self.quotaMetric.isEmpty { + try visitor.visitSingularStringField(value: self.quotaMetric, fieldNumber: 4) + } + if !self.quotaID.isEmpty { + try visitor.visitSingularStringField(value: self.quotaID, fieldNumber: 5) + } + if !self.quotaDimensions.isEmpty { + try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.quotaDimensions, fieldNumber: 6) + } + if self.quotaValue != 0 { + try visitor.visitSingularInt64Field(value: self.quotaValue, fieldNumber: 7) + } + try { if let v = self._futureQuotaValue { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 8) + } }() try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Google_Rpc_QuotaFailure.Violation, rhs: Google_Rpc_QuotaFailure.Violation) -> Bool { if lhs.subject != rhs.subject {return false} if lhs.description_p != rhs.description_p {return false} + if lhs.apiService != rhs.apiService {return false} + if lhs.quotaMetric != rhs.quotaMetric {return false} + if lhs.quotaID != rhs.quotaID {return false} + if lhs.quotaDimensions != rhs.quotaDimensions {return false} + if lhs.quotaValue != rhs.quotaValue {return false} + if lhs._futureQuotaValue != rhs._futureQuotaValue {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -720,6 +856,8 @@ extension Google_Rpc_BadRequest.FieldViolation: SwiftProtobuf.Message, SwiftProt static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "field"), 2: .same(proto: "description"), + 3: .same(proto: "reason"), + 4: .standard(proto: "localized_message"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -730,24 +868,38 @@ extension Google_Rpc_BadRequest.FieldViolation: SwiftProtobuf.Message, SwiftProt switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.field) }() case 2: try { try decoder.decodeSingularStringField(value: &self.description_p) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.reason) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._localizedMessage) }() default: break } } } func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.field.isEmpty { try visitor.visitSingularStringField(value: self.field, fieldNumber: 1) } if !self.description_p.isEmpty { try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 2) } + if !self.reason.isEmpty { + try visitor.visitSingularStringField(value: self.reason, fieldNumber: 3) + } + try { if let v = self._localizedMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Google_Rpc_BadRequest.FieldViolation, rhs: Google_Rpc_BadRequest.FieldViolation) -> Bool { if lhs.field != rhs.field {return false} if lhs.description_p != rhs.description_p {return false} + if lhs.reason != rhs.reason {return false} + if lhs._localizedMessage != rhs._localizedMessage {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/GRPCProtobuf/Errors/Generated/status.pb.swift b/Sources/GRPCProtobuf/Errors/Generated/status.pb.swift index f0f8d6b..0638345 100644 --- a/Sources/GRPCProtobuf/Errors/Generated/status.pb.swift +++ b/Sources/GRPCProtobuf/Errors/Generated/status.pb.swift @@ -8,7 +8,7 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/GRPCProtobufTests/Errors/DetailedErrorTests.swift b/Tests/GRPCProtobufTests/Errors/DetailedErrorTests.swift index 1c9e3dc..f9f94a6 100644 --- a/Tests/GRPCProtobufTests/Errors/DetailedErrorTests.swift +++ b/Tests/GRPCProtobufTests/Errors/DetailedErrorTests.swift @@ -77,7 +77,7 @@ struct DetailedErrorTests { (.debugInfo(.testValue), #"DebugInfo(stack: ["foo.foo()", "foo.bar()"], detail: "detail")"#), ( .quotaFailure(.testValue), - #"QuotaFailure(violations: [Violation(subject: "s", violationDescription: "d")])"# + #"QuotaFailure(violations: [Violation(subject: "s", violationDescription: "d", apiService: "a", quotaMetric: "m", quotaID: "i", quotaDimensions: ["k": "v"], quotaValue: 1, futureQuotaValue: 2)])"# ), ( .preconditionFailure(.testValue), @@ -85,7 +85,7 @@ struct DetailedErrorTests { ), ( .badRequest(.testValue), - #"BadRequest(violations: [FieldViolation(field: "f", violationDescription: "d")])"# + #"BadRequest(violations: [FieldViolation(field: "f", violationDescription: "d", reason: "r", localizedMessage: LocalizedMessage(locale: "l", message: "m"))])"# ), (.requestInfo(.testValue), #"RequestInfo(requestID: "id", servingData: "d")"#), ( @@ -209,7 +209,16 @@ extension ErrorDetails.DebugInfo { extension ErrorDetails.QuotaFailure { fileprivate static let testValue = Self( violations: [ - Violation(subject: "s", description: "d") + Violation( + subject: "s", + description: "d", + apiService: "a", + quotaMetric: "m", + quotaID: "i", + quotaDimensions: ["k": "v"], + quotaValue: 1, + futureQuotaValue: 2 + ) ] ) } @@ -227,7 +236,12 @@ extension ErrorDetails.PreconditionFailure { extension ErrorDetails.BadRequest { fileprivate static let testValue = Self( violations: [ - FieldViolation(field: "f", description: "d") + FieldViolation( + field: "f", + description: "d", + reason: "r", + localizedMessage: .init(locale: "l", message: "m") + ) ] ) } diff --git a/dev/protos/upstream/google/rpc/code.proto b/dev/protos/upstream/google/rpc/code.proto index ba8f2bf..aa6ce15 100644 --- a/dev/protos/upstream/google/rpc/code.proto +++ b/dev/protos/upstream/google/rpc/code.proto @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/dev/protos/upstream/google/rpc/error_details.proto b/dev/protos/upstream/google/rpc/error_details.proto index c06afd4..4f9ecff 100644 --- a/dev/protos/upstream/google/rpc/error_details.proto +++ b/dev/protos/upstream/google/rpc/error_details.proto @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -66,11 +66,12 @@ message ErrorInfo { // Additional structured details about this error. // - // Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in + // 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 + // `{"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; } @@ -130,6 +131,71 @@ message QuotaFailure { // 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. @@ -211,6 +277,18 @@ message BadRequest { // 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. diff --git a/dev/protos/upstream/google/rpc/status.proto b/dev/protos/upstream/google/rpc/status.proto index 90b70dd..dc14c94 100644 --- a/dev/protos/upstream/google/rpc/status.proto +++ b/dev/protos/upstream/google/rpc/status.proto @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.