cratestack_client_rust/rpc/
error.rs1use cratestack_core::{CratestackError, rpc::RpcErrorBody};
2use reqwest::StatusCode;
3use serde::de::DeserializeOwned;
4
5use crate::codec::HttpClientCodec;
6use crate::envelope_error::EnvelopeError;
7use crate::error::{ClientError, TransportError};
8use crate::remote_refusal::{Challenges, www_authenticate};
9use crate::runtime::wire::RuntimeResponseWire;
10
11#[derive(Debug, Clone)]
16pub struct RpcRemoteError {
17 pub status: StatusCode,
18 pub body: RpcErrorBody,
21 pub challenges: Challenges,
25}
26
27impl std::fmt::Display for RpcRemoteError {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 write!(
30 f,
31 "RPC call failed with code {} (status {}): {}",
32 self.body.code,
33 self.status.as_u16(),
34 self.body.message
35 )
36 }
37}
38
39impl std::error::Error for RpcRemoteError {}
40
41#[derive(Debug, thiserror::Error)]
46#[non_exhaustive]
47pub enum RpcClientError {
48 #[error("transport error: {0}")]
49 Transport(#[source] TransportError),
50 #[cfg(feature = "middleware")]
54 #[error("middleware error: {0}")]
55 Middleware(#[source] crate::middleware::MiddlewareError),
56 #[error("codec error: {0}")]
57 Codec(#[from] CratestackError),
58 #[error("invalid response: {0}")]
59 InvalidResponse(String),
60 #[error("bad input: {0}")]
61 BadInput(String),
62 #[error("signed transport: {0}")]
64 Envelope(#[from] EnvelopeError),
65 #[error("{0}")]
66 Remote(RpcRemoteError),
67}
68
69pub type RpcStream<O> = tokio::sync::mpsc::Receiver<Result<O, RpcClientError>>;
76
77pub(crate) fn http_status_for_rpc_code(code: &str) -> StatusCode {
83 match code {
84 "invalid_argument" => StatusCode::BAD_REQUEST,
85 "unauthenticated" => StatusCode::UNAUTHORIZED,
86 "permission_denied" => StatusCode::FORBIDDEN,
87 "not_found" => StatusCode::NOT_FOUND,
88 "conflict" => StatusCode::CONFLICT,
89 "aborted" => StatusCode::CONFLICT,
92 "failed_precondition" => StatusCode::PRECONDITION_FAILED,
93 "resource_exhausted" => StatusCode::TOO_MANY_REQUESTS,
98 _ => StatusCode::INTERNAL_SERVER_ERROR,
99 }
100}
101
102pub(crate) fn client_error_to_rpc(error: ClientError) -> RpcClientError {
103 match error {
104 ClientError::Transport(error) => RpcClientError::Transport(error),
105 #[cfg(feature = "middleware")]
106 ClientError::Middleware(error) => RpcClientError::Middleware(error),
107 ClientError::Codec(error) => RpcClientError::Codec(error),
108 ClientError::InvalidResponse(message) => RpcClientError::InvalidResponse(message),
109 ClientError::BadInput(message) => RpcClientError::BadInput(message),
110 ClientError::Envelope(error) => RpcClientError::Envelope(error),
111 ClientError::State(message) => RpcClientError::InvalidResponse(message),
112 ClientError::Remote {
113 status,
114 error,
115 message,
116 challenges,
117 } => {
118 let body = error
123 .map(|error| cratestack_core::rpc::RpcErrorBody::from_cratestack_response(*error))
124 .unwrap_or_else(|| RpcErrorBody {
125 code: "internal".to_owned(),
126 message,
127 details: None,
128 extra: Default::default(),
129 });
130 RpcClientError::Remote(RpcRemoteError {
131 status,
132 body,
133 challenges,
134 })
135 }
136 }
137}
138
139pub(crate) fn decode_rpc_unary_response<C, Output>(
140 codec: &C,
141 response: &RuntimeResponseWire,
142) -> Result<Output, RpcClientError>
143where
144 C: HttpClientCodec,
145 Output: DeserializeOwned,
146{
147 let content_type = response
148 .headers
149 .iter()
150 .find(|header| header.name.eq_ignore_ascii_case("content-type"))
151 .map(|header| header.value.as_str())
152 .ok_or_else(|| {
153 RpcClientError::InvalidResponse("response is missing Content-Type header".to_owned())
154 })?;
155
156 if (200..=299).contains(&response.status_code) {
157 codec
158 .decode_response::<Output>(content_type, &response.body)
159 .map_err(RpcClientError::Codec)
160 } else {
161 let body = codec
162 .decode_response::<RpcErrorBody>(content_type, &response.body)
163 .unwrap_or_else(|_| RpcErrorBody {
164 code: "internal".to_owned(),
165 message: format!(
166 "unexpected RPC error body for status {}",
167 response.status_code
168 ),
169 details: None,
170 extra: Default::default(),
171 });
172 Err(RpcClientError::Remote(RpcRemoteError {
173 status: StatusCode::from_u16(response.status_code)
174 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
175 body,
176 challenges: www_authenticate(&response.headers),
177 }))
178 }
179}
180
181impl From<ClientError> for RpcClientError {
182 fn from(error: ClientError) -> Self {
183 client_error_to_rpc(error)
184 }
185}