Skip to content

Commit 1fcceaf

Browse files
atrakhConvex, Inc.
authored andcommitted
add conflict error code (#37832)
Add support for using 409 Conflict in http errors GitOrigin-RevId: 10cc9636eda6fda7defd1dcaf09a749e537bdddd
1 parent bccf6b9 commit 1fcceaf

File tree

3 files changed

+32
-3
lines changed

3 files changed

+32
-3
lines changed

crates/errors/src/lib.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub struct ErrorMetadata {
4747
#[derive(Debug, Clone, PartialEq, Eq)]
4848
pub enum ErrorCode {
4949
BadRequest,
50+
Conflict,
5051
Unauthenticated,
5152
AuthUpdateFailed,
5253
Forbidden,
@@ -99,6 +100,23 @@ impl ErrorMetadata {
99100
}
100101
}
101102

103+
/// Conflict. Maps to 409 in HTTP.
104+
///
105+
/// The short_msg should be a CapitalCamelCased describing the error (eg
106+
/// DuplicateInstallation). The msg should be a descriptive message targeted
107+
/// toward the developer.
108+
pub fn conflict(
109+
short_msg: impl Into<Cow<'static, str>>,
110+
msg: impl Into<Cow<'static, str>>,
111+
) -> Self {
112+
Self {
113+
code: ErrorCode::Conflict,
114+
short_msg: short_msg.into(),
115+
msg: msg.into(),
116+
source: None,
117+
}
118+
}
119+
102120
/// Resource not found. Maps to 404 in HTTP. This is not considered
103121
/// a deterministic user error. It should typically be used when the
104122
/// resource can't be currently found, e.g. the backend is not currently
@@ -440,6 +458,7 @@ impl ErrorMetadata {
440458
pub fn is_deterministic_user_error(&self) -> bool {
441459
match self.code {
442460
ErrorCode::BadRequest
461+
| ErrorCode::Conflict
443462
| ErrorCode::PaginationLimit
444463
| ErrorCode::Unauthenticated
445464
| ErrorCode::AuthUpdateFailed
@@ -473,6 +492,7 @@ impl ErrorMetadata {
473492
match self.code {
474493
ErrorCode::ClientDisconnect => None,
475494
ErrorCode::BadRequest
495+
| ErrorCode::Conflict
476496
| ErrorCode::NotFound
477497
| ErrorCode::PaginationLimit
478498
| ErrorCode::Forbidden
@@ -505,6 +525,7 @@ impl ErrorMetadata {
505525
fn metric_server_error_label_value(&self) -> Option<&'static str> {
506526
match self.code {
507527
ErrorCode::BadRequest
528+
| ErrorCode::Conflict
508529
| ErrorCode::PaginationLimit
509530
| ErrorCode::Unauthenticated
510531
| ErrorCode::AuthUpdateFailed
@@ -529,6 +550,7 @@ impl ErrorMetadata {
529550
pub fn custom_metric(&self) -> Option<&'static IntCounter> {
530551
match self.code {
531552
ErrorCode::BadRequest => Some(&crate::metrics::BAD_REQUEST_ERROR_TOTAL),
553+
ErrorCode::Conflict => None,
532554
ErrorCode::ClientDisconnect => Some(&crate::metrics::CLIENT_DISCONNECT_ERROR_TOTAL),
533555
ErrorCode::RateLimited => Some(&crate::metrics::RATE_LIMITED_ERROR_TOTAL),
534556
ErrorCode::Unauthenticated | ErrorCode::AuthUpdateFailed => {
@@ -561,9 +583,10 @@ impl ErrorMetadata {
561583
ErrorCode::OperationalInternalServerError => Some(CloseCode::Error),
562584
// These ones are client errors - so no close code - the client
563585
// will handle and close the connection instead.
564-
ErrorCode::BadRequest | ErrorCode::Unauthenticated | ErrorCode::AuthUpdateFailed => {
565-
None
566-
},
586+
ErrorCode::BadRequest
587+
| ErrorCode::Unauthenticated
588+
| ErrorCode::AuthUpdateFailed
589+
| ErrorCode::Conflict => None,
567590
}?;
568591
// According to the WebSocket protocol specification (RFC 6455), the reason
569592
// string (if present) is limited to 123 bytes. This is because the
@@ -582,6 +605,7 @@ impl ErrorCode {
582605
fn http_status_code(&self) -> StatusCode {
583606
match self {
584607
ErrorCode::BadRequest | ErrorCode::PaginationLimit => StatusCode::BAD_REQUEST,
608+
ErrorCode::Conflict => StatusCode::CONFLICT,
585609
// HTTP has the unfortunate naming of 401 as unauthorized when it's
586610
// really about authentication.
587611
// https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses
@@ -602,6 +626,7 @@ impl ErrorCode {
602626
pub fn grpc_status_code(&self) -> tonic::Code {
603627
match self {
604628
ErrorCode::BadRequest => tonic::Code::InvalidArgument,
629+
ErrorCode::Conflict => tonic::Code::AlreadyExists,
605630
ErrorCode::Unauthenticated | ErrorCode::AuthUpdateFailed => {
606631
tonic::Code::Unauthenticated
607632
},
@@ -940,6 +965,7 @@ mod proptest {
940965
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
941966
any::<ErrorCode>().prop_map(|ec| match ec {
942967
ErrorCode::BadRequest => ErrorMetadata::bad_request("bad", "request"),
968+
ErrorCode::Conflict => ErrorMetadata::conflict("conflict", "conflict"),
943969
ErrorCode::NotFound => ErrorMetadata::not_found("not", "found"),
944970
ErrorCode::PaginationLimit => {
945971
ErrorMetadata::pagination_limit("pagination", "limit")

crates/pb/protos/errors.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ enum ErrorCode {
1717
RATE_LIMITED = 11;
1818
MISDIRECTED_REQUEST = 12;
1919
AUTH_UPDATE_FAILED = 13;
20+
CONFLICT = 14;
2021
}
2122

2223
message OccInfo {

crates/pb/src/error_metadata.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ impl From<ErrorCode> for ErrorCodeProto {
1818
fn from(code: ErrorCode) -> Self {
1919
match code {
2020
ErrorCode::BadRequest => ErrorCodeProto::BadRequest,
21+
ErrorCode::Conflict => ErrorCodeProto::Conflict,
2122
ErrorCode::Unauthenticated => ErrorCodeProto::Unauthenticated,
2223
ErrorCode::AuthUpdateFailed => ErrorCodeProto::AuthUpdateFailed,
2324
ErrorCode::Forbidden => ErrorCodeProto::Forbidden,
@@ -41,6 +42,7 @@ impl ErrorCodeProto {
4142
fn into_rust_type(self, occ_info: OccInfoProto) -> ErrorCode {
4243
match self {
4344
ErrorCodeProto::BadRequest => ErrorCode::BadRequest,
45+
ErrorCodeProto::Conflict => ErrorCode::Conflict,
4446
ErrorCodeProto::Unauthenticated => ErrorCode::Unauthenticated,
4547
ErrorCodeProto::AuthUpdateFailed => ErrorCode::AuthUpdateFailed,
4648
ErrorCodeProto::Forbidden => ErrorCode::Forbidden,

0 commit comments

Comments
 (0)