connectrpc_axum/context/
error.rs1use crate::context::RequestProtocol;
4use crate::context::protocol::SUPPORTED_CONTENT_TYPES;
5use crate::error::{Code, ConnectError};
6use axum::body::Body;
7use axum::http::StatusCode;
8use axum::response::Response;
9
10#[derive(Debug)]
15pub struct ContextError(pub RequestProtocol, pub ConnectError);
16
17impl ContextError {
18 pub fn new(protocol: RequestProtocol, err: ConnectError) -> Self {
20 Self(protocol, err)
21 }
22
23 pub fn internal(protocol: RequestProtocol, _msg: impl Into<String>) -> Self {
28 Self(
30 protocol,
31 ConnectError::new(Code::Internal, "internal error"),
32 )
33 }
34
35 pub fn protocol(&self) -> RequestProtocol {
37 self.0
38 }
39
40 pub fn error(&self) -> &ConnectError {
42 &self.1
43 }
44
45 pub fn into_response(self) -> Response {
47 self.1.into_response_with_protocol(self.0)
48 }
49
50 pub fn into_connect_error(self) -> ConnectError {
52 self.1
53 }
54}
55
56impl std::fmt::Display for ContextError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 write!(f, "{}", self.1.message().unwrap_or("error"))
59 }
60}
61
62impl std::error::Error for ContextError {}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum ProtocolNegotiationError {
79 UnsupportedMediaType,
81}
82
83impl ProtocolNegotiationError {
84 pub fn into_response(self) -> Response {
86 Response::builder()
87 .status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
88 .header("Accept-Post", SUPPORTED_CONTENT_TYPES)
89 .body(Body::empty())
90 .unwrap()
91 }
92}
93
94impl std::fmt::Display for ProtocolNegotiationError {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 Self::UnsupportedMediaType => write!(f, "unsupported media type"),
98 }
99 }
100}
101
102impl std::error::Error for ProtocolNegotiationError {}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_response_error_new() {
110 let err = ContextError::new(
111 RequestProtocol::ConnectUnaryJson,
112 ConnectError::new(Code::InvalidArgument, "test error"),
113 );
114 assert!(matches!(err.error().code(), Code::InvalidArgument));
115 assert!(matches!(err.protocol(), RequestProtocol::ConnectUnaryJson));
116 }
117
118 #[test]
119 fn test_response_error_internal() {
120 let err = ContextError::internal(RequestProtocol::ConnectUnaryJson, "secret details");
121 assert!(matches!(err.error().code(), Code::Internal));
123 assert_eq!(err.error().message(), Some("internal error"));
124 }
125
126 #[test]
127 fn test_into_response() {
128 let err = ContextError::new(
129 RequestProtocol::ConnectUnaryJson,
130 ConnectError::new(Code::NotFound, "not found"),
131 );
132 let _response = err.into_response();
133 }
134
135 #[test]
136 fn test_display() {
137 let err = ContextError::new(
138 RequestProtocol::ConnectUnaryProto,
139 ConnectError::new(Code::NotFound, "not found"),
140 );
141 assert_eq!(format!("{err}"), "not found");
142 }
143
144 #[test]
147 fn test_protocol_negotiation_error_into_response() {
148 use axum::http::StatusCode;
149
150 let err = ProtocolNegotiationError::UnsupportedMediaType;
151 let response = err.into_response();
152
153 assert_eq!(response.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
155
156 let accept_post = response.headers().get("Accept-Post");
158 assert!(accept_post.is_some());
159 let accept_post_value = accept_post.unwrap().to_str().unwrap();
160 assert!(accept_post_value.contains("application/json"));
161 assert!(accept_post_value.contains("application/proto"));
162 assert!(accept_post_value.contains("application/connect+json"));
163 assert!(accept_post_value.contains("application/connect+proto"));
164 }
165
166 #[test]
167 fn test_protocol_negotiation_error_display() {
168 let err = ProtocolNegotiationError::UnsupportedMediaType;
169 assert_eq!(format!("{err}"), "unsupported media type");
170 }
171
172 #[test]
173 fn test_protocol_negotiation_error_debug() {
174 let err = ProtocolNegotiationError::UnsupportedMediaType;
175 assert_eq!(format!("{err:?}"), "UnsupportedMediaType");
176 }
177}