1use axum::{
2 Json,
3 body::Body,
4 http::{HeaderMap, HeaderName, HeaderValue, StatusCode, header},
5 response::{IntoResponse, Response},
6};
7use serde::{Serialize, Serializer};
8
9use crate::context::RequestProtocol;
10
11#[derive(Clone, Copy, Debug, Serialize)]
13#[serde(rename_all = "snake_case")]
14pub enum Code {
15 Ok = 0,
16 Canceled = 1,
17 Unknown = 2,
18 InvalidArgument = 3,
19 DeadlineExceeded = 4,
20 NotFound = 5,
21 AlreadyExists = 6,
22 PermissionDenied = 7,
23 ResourceExhausted = 8,
24 FailedPrecondition = 9,
25 Aborted = 10,
26 OutOfRange = 11,
27 Unimplemented = 12,
28 Internal = 13,
29 Unavailable = 14,
30 DataLoss = 15,
31 Unauthenticated = 16,
32}
33
34#[derive(Clone, Debug)]
60pub struct ErrorDetail {
61 type_url: String,
63 value: Vec<u8>,
65}
66
67impl ErrorDetail {
68 pub fn new<S: Into<String>>(type_url: S, value: Vec<u8>) -> Self {
70 Self {
71 type_url: type_url.into(),
72 value,
73 }
74 }
75
76 pub fn type_url(&self) -> &str {
78 &self.type_url
79 }
80
81 pub fn value(&self) -> &[u8] {
83 &self.value
84 }
85}
86
87#[derive(Clone, Debug)]
90pub struct ConnectError {
91 code: Code,
92 message: Option<String>,
93 details: Vec<ErrorDetail>,
94 meta: Option<HeaderMap>,
95}
96
97impl ConnectError {
98 pub fn new<S: Into<String>>(code: Code, message: S) -> Self {
100 Self {
101 code,
102 message: Some(message.into()),
103 details: vec![],
104 meta: None,
105 }
106 }
107
108 pub fn from_code(code: Code) -> Self {
110 Self {
111 code,
112 message: None,
113 details: vec![],
114 meta: None,
115 }
116 }
117
118 pub fn new_unimplemented() -> Self {
120 Self {
121 code: Code::Unimplemented,
122 message: Some("The requested service has not been implemented.".to_string()),
123 details: vec![],
124 meta: None,
125 }
126 }
127
128 pub fn new_invalid_argument<S: Into<String>>(message: S) -> Self {
130 Self::new(Code::InvalidArgument, message)
131 }
132
133 pub fn new_not_found<S: Into<String>>(message: S) -> Self {
135 Self::new(Code::NotFound, message)
136 }
137
138 pub fn new_permission_denied<S: Into<String>>(message: S) -> Self {
140 Self::new(Code::PermissionDenied, message)
141 }
142
143 pub fn new_unauthenticated<S: Into<String>>(message: S) -> Self {
145 Self::new(Code::Unauthenticated, message)
146 }
147
148 pub fn new_internal<S: Into<String>>(message: S) -> Self {
150 Self::new(Code::Internal, message)
151 }
152
153 pub fn new_unavailable<S: Into<String>>(message: S) -> Self {
155 Self::new(Code::Unavailable, message)
156 }
157
158 pub fn new_already_exists<S: Into<String>>(message: S) -> Self {
160 Self::new(Code::AlreadyExists, message)
161 }
162
163 pub fn new_resource_exhausted<S: Into<String>>(message: S) -> Self {
165 Self::new(Code::ResourceExhausted, message)
166 }
167
168 pub fn new_failed_precondition<S: Into<String>>(message: S) -> Self {
170 Self::new(Code::FailedPrecondition, message)
171 }
172
173 pub fn new_aborted<S: Into<String>>(message: S) -> Self {
175 Self::new(Code::Aborted, message)
176 }
177
178 pub fn code(&self) -> Code {
180 self.code
181 }
182
183 pub fn message(&self) -> Option<&str> {
185 self.message.as_deref()
186 }
187
188 pub fn details(&self) -> &[ErrorDetail] {
190 &self.details
191 }
192
193 pub fn add_detail<S: Into<String>>(mut self, type_url: S, value: Vec<u8>) -> Self {
212 self.details.push(ErrorDetail::new(type_url, value));
213 self
214 }
215
216 pub fn add_error_detail(mut self, detail: ErrorDetail) -> Self {
218 self.details.push(detail);
219 self
220 }
221
222 pub fn meta(&self) -> Option<&HeaderMap> {
224 self.meta.as_ref()
225 }
226
227 pub fn meta_mut(&mut self) -> &mut HeaderMap {
230 self.meta.get_or_insert_with(HeaderMap::new)
231 }
232
233 pub fn with_meta<K, V>(mut self, key: K, value: V) -> Self
235 where
236 K: AsRef<str>,
237 V: AsRef<str>,
238 {
239 let key_str = key.as_ref();
240 let val_str = value.as_ref();
241
242 match HeaderName::from_bytes(key_str.as_bytes()) {
243 Ok(name) => match HeaderValue::from_str(val_str) {
244 Ok(val) => {
245 self.meta_mut().append(name, val);
246 }
247 Err(e) => {
248 tracing::debug!(
249 key = key_str,
250 value = val_str,
251 error = %e,
252 "invalid header value, metadata dropped"
253 );
254 }
255 },
256 Err(e) => {
257 tracing::debug!(
258 key = key_str,
259 error = %e,
260 "invalid header name, metadata dropped"
261 );
262 }
263 }
264 self
265 }
266
267 pub fn set_meta_from_headers(mut self, headers: &HeaderMap) -> Self {
269 self.meta = Some(headers.clone());
270 self
271 }
272}
273
274impl ConnectError {
275 pub(crate) fn into_response_with_protocol(self, protocol: RequestProtocol) -> Response {
280 if protocol.is_streaming() {
283 return self.into_streaming_error_response(protocol);
284 }
285
286 let status_code = self.http_status_code();
288
289 let error_body = ErrorResponseBody {
291 code: self.code,
292 message: self.message,
293 details: self.details,
294 };
295
296 let mut response = (status_code, Json(error_body)).into_response();
298
299 response.headers_mut().insert(
301 header::CONTENT_TYPE,
302 HeaderValue::from_static(protocol.error_content_type()),
303 );
304
305 if let Some(meta) = &self.meta {
307 let headers = response.headers_mut();
308 headers.extend(meta.iter().map(|(k, v)| (k.clone(), v.clone())));
309 }
310
311 response
312 }
313}
314
315impl IntoResponse for ConnectError {
316 fn into_response(self) -> Response {
317 self.into_response_with_protocol(RequestProtocol::default())
320 }
321}
322
323impl ConnectError {
324 fn http_status_code(&self) -> StatusCode {
326 match self.code {
327 Code::Ok => StatusCode::OK,
328 Code::Canceled => StatusCode::from_u16(499).unwrap(),
330 Code::Unknown => StatusCode::INTERNAL_SERVER_ERROR,
331 Code::InvalidArgument => StatusCode::BAD_REQUEST,
332 Code::DeadlineExceeded => StatusCode::GATEWAY_TIMEOUT,
334 Code::NotFound => StatusCode::NOT_FOUND,
335 Code::AlreadyExists => StatusCode::CONFLICT,
336 Code::PermissionDenied => StatusCode::FORBIDDEN,
337 Code::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
338 Code::FailedPrecondition => StatusCode::BAD_REQUEST,
339 Code::Aborted => StatusCode::CONFLICT,
340 Code::OutOfRange => StatusCode::BAD_REQUEST,
341 Code::Unimplemented => StatusCode::NOT_IMPLEMENTED,
342 Code::Internal => StatusCode::INTERNAL_SERVER_ERROR,
343 Code::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
344 Code::DataLoss => StatusCode::INTERNAL_SERVER_ERROR,
345 Code::Unauthenticated => StatusCode::UNAUTHORIZED,
346 }
347 }
348
349 pub fn into_streaming_response(self, use_proto: bool) -> Response {
360 let content_type = if use_proto {
361 "application/connect+proto"
362 } else {
363 "application/connect+json"
364 };
365 self.into_streaming_error_response_with_content_type(content_type)
366 }
367
368 fn into_streaming_error_response(self, protocol: crate::context::RequestProtocol) -> Response {
370 self.into_streaming_error_response_with_content_type(protocol.error_content_type())
371 }
372
373 fn into_streaming_error_response_with_content_type(
375 self,
376 content_type: &'static str,
377 ) -> Response {
378 let frame = crate::pipeline::build_end_stream_frame(Some(&self), None);
381
382 Response::builder()
384 .status(StatusCode::OK)
385 .header(header::CONTENT_TYPE, HeaderValue::from_static(content_type))
386 .body(Body::from(frame))
387 .unwrap_or_else(|_| internal_error_streaming_response(content_type))
388 }
389}
390
391impl From<std::convert::Infallible> for ConnectError {
394 fn from(infallible: std::convert::Infallible) -> Self {
395 match infallible {}
396 }
397}
398
399impl From<(StatusCode, String)> for ConnectError {
400 fn from(value: (StatusCode, String)) -> Self {
405 let (status, message) = value;
406 ConnectError::new(status.into(), message)
407 }
408}
409
410impl From<StatusCode> for Code {
411 fn from(status: StatusCode) -> Self {
412 match status {
413 StatusCode::OK => Code::Ok,
414 StatusCode::BAD_REQUEST => Code::InvalidArgument,
415 StatusCode::UNAUTHORIZED => Code::Unauthenticated,
416 StatusCode::FORBIDDEN => Code::PermissionDenied,
417 StatusCode::NOT_FOUND => Code::NotFound,
418 StatusCode::CONFLICT => Code::AlreadyExists,
419 StatusCode::REQUEST_TIMEOUT => Code::DeadlineExceeded,
420 StatusCode::TOO_MANY_REQUESTS => Code::ResourceExhausted,
421 StatusCode::NOT_IMPLEMENTED => Code::Unimplemented,
422 StatusCode::SERVICE_UNAVAILABLE => Code::Unavailable,
423 StatusCode::INTERNAL_SERVER_ERROR => Code::Internal,
424 _ => Code::Unknown,
425 }
426 }
427}
428
429#[derive(Serialize)]
431struct ErrorResponseBody {
432 code: Code,
433 #[serde(skip_serializing_if = "Option::is_none")]
434 message: Option<String>,
435 #[serde(skip_serializing_if = "Vec::is_empty")]
436 details: Vec<ErrorDetail>,
437}
438
439impl Serialize for ErrorDetail {
440 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
441 where
442 S: Serializer,
443 {
444 use base64::Engine;
445 use serde::ser::SerializeStruct;
446
447 let mut s = serializer.serialize_struct("ErrorDetail", 2)?;
448
449 let type_name = self
451 .type_url
452 .strip_prefix("type.googleapis.com/")
453 .unwrap_or(&self.type_url);
454 s.serialize_field("type", type_name)?;
455
456 let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(&self.value);
458 s.serialize_field("value", &encoded)?;
459
460 s.end()
461 }
462}
463
464impl Serialize for ConnectError {
465 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
466 where
467 S: Serializer,
468 {
469 ErrorResponseBody {
471 code: self.code,
472 message: self.message.clone(),
473 details: self.details.clone(),
474 }
475 .serialize(serializer)
476 }
477}
478
479#[cfg(feature = "tonic")]
481impl From<::tonic::Code> for Code {
482 fn from(code: ::tonic::Code) -> Self {
483 match code {
484 ::tonic::Code::Ok => Code::Ok,
485 ::tonic::Code::Cancelled => Code::Canceled,
486 ::tonic::Code::Unknown => Code::Unknown,
487 ::tonic::Code::InvalidArgument => Code::InvalidArgument,
488 ::tonic::Code::DeadlineExceeded => Code::DeadlineExceeded,
489 ::tonic::Code::NotFound => Code::NotFound,
490 ::tonic::Code::AlreadyExists => Code::AlreadyExists,
491 ::tonic::Code::PermissionDenied => Code::PermissionDenied,
492 ::tonic::Code::ResourceExhausted => Code::ResourceExhausted,
493 ::tonic::Code::FailedPrecondition => Code::FailedPrecondition,
494 ::tonic::Code::Aborted => Code::Aborted,
495 ::tonic::Code::OutOfRange => Code::OutOfRange,
496 ::tonic::Code::Unimplemented => Code::Unimplemented,
497 ::tonic::Code::Internal => Code::Internal,
498 ::tonic::Code::Unavailable => Code::Unavailable,
499 ::tonic::Code::DataLoss => Code::DataLoss,
500 ::tonic::Code::Unauthenticated => Code::Unauthenticated,
501 }
502 }
503}
504
505#[cfg(feature = "tonic")]
506impl From<::tonic::Status> for ConnectError {
507 fn from(status: ::tonic::Status) -> Self {
508 let code: Code = status.code().into();
509 let msg = status.message().to_string();
510
511 if msg.is_empty() {
515 ConnectError::from_code(code)
516 } else {
517 ConnectError::new(code, msg)
518 }
519 }
520}
521
522#[cfg(feature = "tonic")]
523impl From<Code> for ::tonic::Code {
524 fn from(code: Code) -> Self {
525 match code {
526 Code::Ok => ::tonic::Code::Ok,
527 Code::Canceled => ::tonic::Code::Cancelled,
528 Code::Unknown => ::tonic::Code::Unknown,
529 Code::InvalidArgument => ::tonic::Code::InvalidArgument,
530 Code::DeadlineExceeded => ::tonic::Code::DeadlineExceeded,
531 Code::NotFound => ::tonic::Code::NotFound,
532 Code::AlreadyExists => ::tonic::Code::AlreadyExists,
533 Code::PermissionDenied => ::tonic::Code::PermissionDenied,
534 Code::ResourceExhausted => ::tonic::Code::ResourceExhausted,
535 Code::FailedPrecondition => ::tonic::Code::FailedPrecondition,
536 Code::Aborted => ::tonic::Code::Aborted,
537 Code::OutOfRange => ::tonic::Code::OutOfRange,
538 Code::Unimplemented => ::tonic::Code::Unimplemented,
539 Code::Internal => ::tonic::Code::Internal,
540 Code::Unavailable => ::tonic::Code::Unavailable,
541 Code::DataLoss => ::tonic::Code::DataLoss,
542 Code::Unauthenticated => ::tonic::Code::Unauthenticated,
543 }
544 }
545}
546
547#[cfg(feature = "tonic")]
548impl From<ConnectError> for ::tonic::Status {
549 fn from(err: ConnectError) -> Self {
550 let code: ::tonic::Code = err.code().into();
551 ::tonic::Status::new(code, err.message().unwrap_or("").to_string())
552 }
553}
554
555pub(crate) fn internal_error_response(content_type: &'static str) -> Response {
563 const ERROR_BODY: &[u8] = br#"{"code":"internal","message":"Internal serialization error"}"#;
565
566 Response::builder()
567 .status(StatusCode::INTERNAL_SERVER_ERROR)
568 .header(header::CONTENT_TYPE, HeaderValue::from_static(content_type))
569 .body(Body::from(ERROR_BODY.to_vec()))
570 .unwrap_or_else(|_| {
572 let mut response = Response::new(Body::empty());
574 *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
575 response
576 })
577}
578
579pub(crate) fn internal_error_end_stream_frame() -> Vec<u8> {
584 const ERROR_PAYLOAD: &[u8] =
586 br#"{"error":{"code":"internal","message":"Internal serialization error"}}"#;
587
588 let mut frame = Vec::with_capacity(5 + ERROR_PAYLOAD.len());
589 frame.push(0b0000_0010); frame.extend_from_slice(&(ERROR_PAYLOAD.len() as u32).to_be_bytes());
591 frame.extend_from_slice(ERROR_PAYLOAD);
592 frame
593}
594
595pub(crate) fn internal_error_streaming_response(content_type: &'static str) -> Response {
600 let frame = internal_error_end_stream_frame();
601
602 Response::builder()
603 .status(StatusCode::OK)
604 .header(header::CONTENT_TYPE, HeaderValue::from_static(content_type))
605 .body(Body::from(frame))
606 .unwrap_or_else(|_| {
607 Response::new(Body::empty())
609 })
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615 use axum::http::HeaderMap;
616
617 #[test]
620 fn test_connect_error_new() {
621 let err = ConnectError::new(Code::NotFound, "resource not found");
622 assert!(matches!(err.code(), Code::NotFound));
623 assert_eq!(err.message(), Some("resource not found"));
624 assert!(err.details().is_empty());
625 assert!(err.meta().is_none());
626 }
627
628 #[test]
629 fn test_connect_error_from_code() {
630 let err = ConnectError::from_code(Code::Internal);
631 assert!(matches!(err.code(), Code::Internal));
632 assert!(err.message().is_none());
633 }
634
635 #[test]
636 fn test_connect_error_convenience_constructors() {
637 let err = ConnectError::new_unimplemented();
638 assert!(matches!(err.code(), Code::Unimplemented));
639 assert!(err.message().is_some());
640
641 let err = ConnectError::new_invalid_argument("bad input");
642 assert!(matches!(err.code(), Code::InvalidArgument));
643 assert_eq!(err.message(), Some("bad input"));
644
645 let err = ConnectError::new_not_found("missing");
646 assert!(matches!(err.code(), Code::NotFound));
647
648 let err = ConnectError::new_permission_denied("forbidden");
649 assert!(matches!(err.code(), Code::PermissionDenied));
650
651 let err = ConnectError::new_unauthenticated("no auth");
652 assert!(matches!(err.code(), Code::Unauthenticated));
653
654 let err = ConnectError::new_internal("server error");
655 assert!(matches!(err.code(), Code::Internal));
656
657 let err = ConnectError::new_unavailable("try later");
658 assert!(matches!(err.code(), Code::Unavailable));
659
660 let err = ConnectError::new_already_exists("duplicate");
661 assert!(matches!(err.code(), Code::AlreadyExists));
662
663 let err = ConnectError::new_resource_exhausted("quota exceeded");
664 assert!(matches!(err.code(), Code::ResourceExhausted));
665
666 let err = ConnectError::new_failed_precondition("precondition failed");
667 assert!(matches!(err.code(), Code::FailedPrecondition));
668
669 let err = ConnectError::new_aborted("aborted");
670 assert!(matches!(err.code(), Code::Aborted));
671 }
672
673 #[test]
676 fn test_with_meta_valid_headers() {
677 let err = ConnectError::new(Code::Internal, "error")
678 .with_meta("x-request-id", "req-123")
679 .with_meta("x-trace-id", "trace-456");
680
681 let meta = err.meta().expect("metadata should be present");
682 assert_eq!(meta.get("x-request-id").unwrap(), "req-123");
683 assert_eq!(meta.get("x-trace-id").unwrap(), "trace-456");
684 }
685
686 #[test]
687 fn test_with_meta_invalid_header_name_is_dropped() {
688 let err = ConnectError::new(Code::Internal, "error")
690 .with_meta("invalid header", "value")
691 .with_meta("x-valid", "kept");
692
693 let meta = err.meta().expect("metadata should be present");
694 assert!(meta.get("invalid header").is_none());
695 assert_eq!(meta.get("x-valid").unwrap(), "kept");
696 }
697
698 #[test]
699 fn test_with_meta_invalid_header_value_is_dropped() {
700 let err = ConnectError::new(Code::Internal, "error")
702 .with_meta("x-bad-value", "value\x00with\x01null")
703 .with_meta("x-valid", "kept");
704
705 let meta = err.meta().expect("metadata should be present");
706 assert!(meta.get("x-bad-value").is_none());
707 assert_eq!(meta.get("x-valid").unwrap(), "kept");
708 }
709
710 #[test]
711 fn test_with_meta_multiple_values_same_key() {
712 let err = ConnectError::new(Code::Internal, "error")
713 .with_meta("x-multi", "value1")
714 .with_meta("x-multi", "value2");
715
716 let meta = err.meta().expect("metadata should be present");
717 let values: Vec<_> = meta.get_all("x-multi").iter().collect();
718 assert_eq!(values.len(), 2);
719 assert_eq!(values[0], "value1");
720 assert_eq!(values[1], "value2");
721 }
722
723 #[test]
724 fn test_meta_mut() {
725 let mut err = ConnectError::new(Code::Internal, "error");
726
727 err.meta_mut().insert(
729 HeaderName::from_static("x-custom"),
730 HeaderValue::from_static("value"),
731 );
732
733 assert!(err.meta().is_some());
734 assert_eq!(err.meta().unwrap().get("x-custom").unwrap(), "value");
735
736 err.meta_mut().insert(
738 HeaderName::from_static("x-another"),
739 HeaderValue::from_static("value2"),
740 );
741
742 assert_eq!(err.meta().unwrap().len(), 2);
743 }
744
745 #[test]
746 fn test_set_meta_from_headers() {
747 let mut headers = HeaderMap::new();
748 headers.insert(
749 HeaderName::from_static("x-from-map"),
750 HeaderValue::from_static("map-value"),
751 );
752 headers.insert(
753 HeaderName::from_static("x-another"),
754 HeaderValue::from_static("another-value"),
755 );
756
757 let err = ConnectError::new(Code::Internal, "error").set_meta_from_headers(&headers);
758
759 let meta = err.meta().expect("metadata should be present");
760 assert_eq!(meta.get("x-from-map").unwrap(), "map-value");
761 assert_eq!(meta.get("x-another").unwrap(), "another-value");
762 }
763
764 #[test]
765 fn test_set_meta_from_headers_replaces_existing() {
766 let err = ConnectError::new(Code::Internal, "error").with_meta("x-old", "old-value");
767
768 let mut headers = HeaderMap::new();
769 headers.insert(
770 HeaderName::from_static("x-new"),
771 HeaderValue::from_static("new-value"),
772 );
773
774 let err = err.set_meta_from_headers(&headers);
775
776 let meta = err.meta().expect("metadata should be present");
777 assert!(meta.get("x-old").is_none());
779 assert_eq!(meta.get("x-new").unwrap(), "new-value");
780 }
781
782 #[test]
785 fn test_add_detail() {
786 let err = ConnectError::new(Code::Internal, "error")
787 .add_detail("test.Type1", vec![1, 2, 3])
788 .add_detail("test.Type2", vec![4, 5, 6]);
789
790 assert_eq!(err.details().len(), 2);
791 assert_eq!(err.details()[0].type_url(), "test.Type1");
792 assert_eq!(err.details()[0].value(), &[1, 2, 3]);
793 assert_eq!(err.details()[1].type_url(), "test.Type2");
794 assert_eq!(err.details()[1].value(), &[4, 5, 6]);
795 }
796
797 #[test]
798 fn test_add_error_detail() {
799 let detail = ErrorDetail::new("google.rpc.RetryInfo", vec![10, 2, 8, 5]);
800 let err =
801 ConnectError::new(Code::ResourceExhausted, "rate limited").add_error_detail(detail);
802
803 assert_eq!(err.details().len(), 1);
804 assert_eq!(err.details()[0].type_url(), "google.rpc.RetryInfo");
805 }
806
807 #[test]
810 fn test_http_status_code_mapping() {
811 let test_cases = [
812 (Code::Ok, StatusCode::OK),
813 (Code::Canceled, StatusCode::from_u16(499).unwrap()), (Code::Unknown, StatusCode::INTERNAL_SERVER_ERROR),
815 (Code::InvalidArgument, StatusCode::BAD_REQUEST),
816 (Code::DeadlineExceeded, StatusCode::GATEWAY_TIMEOUT), (Code::NotFound, StatusCode::NOT_FOUND),
818 (Code::AlreadyExists, StatusCode::CONFLICT),
819 (Code::PermissionDenied, StatusCode::FORBIDDEN),
820 (Code::ResourceExhausted, StatusCode::TOO_MANY_REQUESTS),
821 (Code::FailedPrecondition, StatusCode::BAD_REQUEST),
822 (Code::Aborted, StatusCode::CONFLICT),
823 (Code::OutOfRange, StatusCode::BAD_REQUEST),
824 (Code::Unimplemented, StatusCode::NOT_IMPLEMENTED),
825 (Code::Internal, StatusCode::INTERNAL_SERVER_ERROR),
826 (Code::Unavailable, StatusCode::SERVICE_UNAVAILABLE),
827 (Code::DataLoss, StatusCode::INTERNAL_SERVER_ERROR),
828 (Code::Unauthenticated, StatusCode::UNAUTHORIZED),
829 ];
830
831 for (code, expected_status) in test_cases {
832 let err = ConnectError::from_code(code);
833 assert_eq!(
834 err.http_status_code(),
835 expected_status,
836 "Code::{:?} should map to {:?}",
837 code,
838 expected_status
839 );
840 }
841 }
842
843 #[test]
846 fn test_status_code_to_code_conversion() {
847 assert!(matches!(Code::from(StatusCode::OK), Code::Ok));
848 assert!(matches!(
849 Code::from(StatusCode::BAD_REQUEST),
850 Code::InvalidArgument
851 ));
852 assert!(matches!(
853 Code::from(StatusCode::UNAUTHORIZED),
854 Code::Unauthenticated
855 ));
856 assert!(matches!(
857 Code::from(StatusCode::FORBIDDEN),
858 Code::PermissionDenied
859 ));
860 assert!(matches!(Code::from(StatusCode::NOT_FOUND), Code::NotFound));
861 assert!(matches!(
862 Code::from(StatusCode::CONFLICT),
863 Code::AlreadyExists
864 ));
865 assert!(matches!(
866 Code::from(StatusCode::REQUEST_TIMEOUT),
867 Code::DeadlineExceeded
868 ));
869 assert!(matches!(
870 Code::from(StatusCode::TOO_MANY_REQUESTS),
871 Code::ResourceExhausted
872 ));
873 assert!(matches!(
874 Code::from(StatusCode::NOT_IMPLEMENTED),
875 Code::Unimplemented
876 ));
877 assert!(matches!(
878 Code::from(StatusCode::SERVICE_UNAVAILABLE),
879 Code::Unavailable
880 ));
881 assert!(matches!(
882 Code::from(StatusCode::INTERNAL_SERVER_ERROR),
883 Code::Internal
884 ));
885 assert!(matches!(Code::from(StatusCode::IM_A_TEAPOT), Code::Unknown));
887 }
888
889 #[test]
890 fn test_from_status_code_and_string() {
891 let err: ConnectError = (StatusCode::NOT_FOUND, "item not found".to_string()).into();
892 assert!(matches!(err.code(), Code::NotFound));
893 assert_eq!(err.message(), Some("item not found"));
894 }
895
896 #[test]
899 fn test_into_response_unary_includes_metadata_as_headers() {
900 let err = ConnectError::new(Code::Internal, "error")
901 .with_meta("x-request-id", "req-123")
902 .with_meta("x-trace-id", "trace-456");
903
904 let response = err.into_response_with_protocol(RequestProtocol::ConnectUnaryJson);
905
906 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
908
909 assert_eq!(response.headers().get("x-request-id").unwrap(), "req-123");
911 assert_eq!(response.headers().get("x-trace-id").unwrap(), "trace-456");
912
913 assert_eq!(
915 response.headers().get(header::CONTENT_TYPE).unwrap(),
916 "application/json"
917 );
918 }
919
920 #[test]
921 fn test_into_response_unary_proto_content_type() {
922 let err = ConnectError::new(Code::NotFound, "not found");
923 let response = err.into_response_with_protocol(RequestProtocol::ConnectUnaryProto);
924
925 assert_eq!(response.status(), StatusCode::NOT_FOUND);
926 assert_eq!(
927 response.headers().get(header::CONTENT_TYPE).unwrap(),
928 "application/json" );
930 }
931
932 #[test]
933 fn test_into_response_streaming_returns_http_200() {
934 let err = ConnectError::new(Code::Internal, "stream error");
935 let response = err.into_response_with_protocol(RequestProtocol::ConnectStreamJson);
936
937 assert_eq!(response.status(), StatusCode::OK);
939 assert_eq!(
940 response.headers().get(header::CONTENT_TYPE).unwrap(),
941 "application/connect+json"
942 );
943 }
944
945 #[test]
946 fn test_into_streaming_response_json() {
947 let err = ConnectError::new(Code::Internal, "error");
948 let response = err.into_streaming_response(false);
949
950 assert_eq!(response.status(), StatusCode::OK);
951 assert_eq!(
952 response.headers().get(header::CONTENT_TYPE).unwrap(),
953 "application/connect+json"
954 );
955 }
956
957 #[test]
958 fn test_into_streaming_response_proto() {
959 let err = ConnectError::new(Code::Internal, "error");
960 let response = err.into_streaming_response(true);
961
962 assert_eq!(response.status(), StatusCode::OK);
963 assert_eq!(
964 response.headers().get(header::CONTENT_TYPE).unwrap(),
965 "application/connect+proto"
966 );
967 }
968
969 #[test]
972 fn test_serialize_error_json() {
973 let err = ConnectError::new(Code::InvalidArgument, "bad input");
974 let json = serde_json::to_string(&err).unwrap();
975
976 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
977 assert_eq!(parsed["code"], "invalid_argument");
978 assert_eq!(parsed["message"], "bad input");
979 assert!(parsed.get("details").is_none());
981 }
982
983 #[test]
984 fn test_serialize_error_with_details() {
985 let err = ConnectError::new(Code::Internal, "error")
986 .add_detail("google.rpc.RetryInfo", vec![1, 2, 3]);
987
988 let json = serde_json::to_string(&err).unwrap();
989 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
990
991 assert!(parsed.get("details").is_some());
993 let details = parsed["details"].as_array().unwrap();
994 assert_eq!(details.len(), 1);
995
996 let detail = &details[0];
998 assert_eq!(detail["type"], "google.rpc.RetryInfo");
999 assert_eq!(detail["value"], "AQID"); }
1001
1002 #[test]
1003 fn test_serialize_error_detail_strips_type_prefix() {
1004 let err = ConnectError::new(Code::Internal, "error")
1006 .add_detail("type.googleapis.com/google.rpc.ErrorInfo", vec![1, 2]);
1007
1008 let json = serde_json::to_string(&err).unwrap();
1009 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1010
1011 let details = parsed["details"].as_array().unwrap();
1012 assert_eq!(details[0]["type"], "google.rpc.ErrorInfo"); }
1014
1015 #[test]
1016 fn test_serialize_error_without_message() {
1017 let err = ConnectError::from_code(Code::Unknown);
1018 let json = serde_json::to_string(&err).unwrap();
1019
1020 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1021 assert_eq!(parsed["code"], "unknown");
1022 assert!(parsed.get("message").is_none());
1024 }
1025
1026 #[test]
1029 fn test_internal_error_response() {
1030 let response = internal_error_response("application/json");
1031
1032 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
1033 assert_eq!(
1034 response.headers().get(header::CONTENT_TYPE).unwrap(),
1035 "application/json"
1036 );
1037 }
1038
1039 #[test]
1040 fn test_internal_error_end_stream_frame() {
1041 let frame = internal_error_end_stream_frame();
1042
1043 assert!(frame.len() > 5);
1045 assert_eq!(frame[0], 0b0000_0010); let length = u32::from_be_bytes([frame[1], frame[2], frame[3], frame[4]]) as usize;
1048 assert_eq!(frame.len(), 5 + length);
1049
1050 let payload = &frame[5..];
1052 let parsed: serde_json::Value = serde_json::from_slice(payload).unwrap();
1053 assert_eq!(parsed["error"]["code"], "internal");
1054 }
1055
1056 #[test]
1057 fn test_internal_error_streaming_response() {
1058 let response = internal_error_streaming_response("application/connect+json");
1059
1060 assert_eq!(response.status(), StatusCode::OK);
1061 assert_eq!(
1062 response.headers().get(header::CONTENT_TYPE).unwrap(),
1063 "application/connect+json"
1064 );
1065 }
1066
1067 #[cfg(feature = "tonic")]
1070 mod tonic_tests {
1071 use super::*;
1072
1073 #[test]
1074 fn test_tonic_code_to_connect_code() {
1075 assert!(matches!(Code::from(::tonic::Code::Ok), Code::Ok));
1076 assert!(matches!(
1077 Code::from(::tonic::Code::Cancelled),
1078 Code::Canceled
1079 ));
1080 assert!(matches!(Code::from(::tonic::Code::Unknown), Code::Unknown));
1081 assert!(matches!(
1082 Code::from(::tonic::Code::InvalidArgument),
1083 Code::InvalidArgument
1084 ));
1085 assert!(matches!(
1086 Code::from(::tonic::Code::DeadlineExceeded),
1087 Code::DeadlineExceeded
1088 ));
1089 assert!(matches!(
1090 Code::from(::tonic::Code::NotFound),
1091 Code::NotFound
1092 ));
1093 assert!(matches!(
1094 Code::from(::tonic::Code::AlreadyExists),
1095 Code::AlreadyExists
1096 ));
1097 assert!(matches!(
1098 Code::from(::tonic::Code::PermissionDenied),
1099 Code::PermissionDenied
1100 ));
1101 assert!(matches!(
1102 Code::from(::tonic::Code::ResourceExhausted),
1103 Code::ResourceExhausted
1104 ));
1105 assert!(matches!(
1106 Code::from(::tonic::Code::FailedPrecondition),
1107 Code::FailedPrecondition
1108 ));
1109 assert!(matches!(Code::from(::tonic::Code::Aborted), Code::Aborted));
1110 assert!(matches!(
1111 Code::from(::tonic::Code::OutOfRange),
1112 Code::OutOfRange
1113 ));
1114 assert!(matches!(
1115 Code::from(::tonic::Code::Unimplemented),
1116 Code::Unimplemented
1117 ));
1118 assert!(matches!(
1119 Code::from(::tonic::Code::Internal),
1120 Code::Internal
1121 ));
1122 assert!(matches!(
1123 Code::from(::tonic::Code::Unavailable),
1124 Code::Unavailable
1125 ));
1126 assert!(matches!(
1127 Code::from(::tonic::Code::DataLoss),
1128 Code::DataLoss
1129 ));
1130 assert!(matches!(
1131 Code::from(::tonic::Code::Unauthenticated),
1132 Code::Unauthenticated
1133 ));
1134 }
1135
1136 #[test]
1137 fn test_connect_code_to_tonic_code() {
1138 assert_eq!(::tonic::Code::from(Code::Ok), ::tonic::Code::Ok);
1139 assert_eq!(
1140 ::tonic::Code::from(Code::Canceled),
1141 ::tonic::Code::Cancelled
1142 );
1143 assert_eq!(::tonic::Code::from(Code::Unknown), ::tonic::Code::Unknown);
1144 assert_eq!(
1145 ::tonic::Code::from(Code::InvalidArgument),
1146 ::tonic::Code::InvalidArgument
1147 );
1148 assert_eq!(
1149 ::tonic::Code::from(Code::DeadlineExceeded),
1150 ::tonic::Code::DeadlineExceeded
1151 );
1152 assert_eq!(::tonic::Code::from(Code::NotFound), ::tonic::Code::NotFound);
1153 assert_eq!(
1154 ::tonic::Code::from(Code::AlreadyExists),
1155 ::tonic::Code::AlreadyExists
1156 );
1157 assert_eq!(
1158 ::tonic::Code::from(Code::PermissionDenied),
1159 ::tonic::Code::PermissionDenied
1160 );
1161 assert_eq!(
1162 ::tonic::Code::from(Code::ResourceExhausted),
1163 ::tonic::Code::ResourceExhausted
1164 );
1165 assert_eq!(
1166 ::tonic::Code::from(Code::FailedPrecondition),
1167 ::tonic::Code::FailedPrecondition
1168 );
1169 assert_eq!(::tonic::Code::from(Code::Aborted), ::tonic::Code::Aborted);
1170 assert_eq!(
1171 ::tonic::Code::from(Code::OutOfRange),
1172 ::tonic::Code::OutOfRange
1173 );
1174 assert_eq!(
1175 ::tonic::Code::from(Code::Unimplemented),
1176 ::tonic::Code::Unimplemented
1177 );
1178 assert_eq!(::tonic::Code::from(Code::Internal), ::tonic::Code::Internal);
1179 assert_eq!(
1180 ::tonic::Code::from(Code::Unavailable),
1181 ::tonic::Code::Unavailable
1182 );
1183 assert_eq!(::tonic::Code::from(Code::DataLoss), ::tonic::Code::DataLoss);
1184 assert_eq!(
1185 ::tonic::Code::from(Code::Unauthenticated),
1186 ::tonic::Code::Unauthenticated
1187 );
1188 }
1189
1190 #[test]
1191 fn test_tonic_status_to_connect_error() {
1192 let status = ::tonic::Status::not_found("item not found");
1193 let err: ConnectError = status.into();
1194
1195 assert!(matches!(err.code(), Code::NotFound));
1196 assert_eq!(err.message(), Some("item not found"));
1197 }
1198
1199 #[test]
1200 fn test_tonic_status_empty_message() {
1201 let status = ::tonic::Status::new(::tonic::Code::Internal, "");
1202 let err: ConnectError = status.into();
1203
1204 assert!(matches!(err.code(), Code::Internal));
1205 assert!(err.message().is_none());
1206 }
1207
1208 #[test]
1209 fn test_connect_error_to_tonic_status() {
1210 let err = ConnectError::new(Code::PermissionDenied, "access denied");
1211 let status: ::tonic::Status = err.into();
1212
1213 assert_eq!(status.code(), ::tonic::Code::PermissionDenied);
1214 assert_eq!(status.message(), "access denied");
1215 }
1216
1217 #[test]
1218 fn test_connect_error_to_tonic_status_no_message() {
1219 let err = ConnectError::from_code(Code::Internal);
1220 let status: ::tonic::Status = err.into();
1221
1222 assert_eq!(status.code(), ::tonic::Code::Internal);
1223 assert_eq!(status.message(), "");
1224 }
1225 }
1226}