cratestack_client_rust/remote_refusal/mod.rs
1//! What a remote refusal says beyond its code (cratestack#1243): the
2//! `WWW-Authenticate` challenges an `AuthRefusal` sends, and one place to
3//! read the decoded error envelope from either error type.
4//!
5//! The envelope's extra members (cratestack#1208) are already decoded:
6//! `CratestackErrorResponse::extra` on REST, `RpcErrorBody::extra` on RPC.
7
8use cratestack_core::CratestackErrorResponse;
9
10use crate::error::ClientError;
11use crate::rpc::error::{RpcClientError, RpcRemoteError};
12use crate::runtime::wire::RuntimeHeader;
13
14mod challenges;
15
16pub use challenges::Challenges;
17
18/// Every `WWW-Authenticate` value in `headers`, raw and in the order they
19/// arrived.
20pub(crate) fn www_authenticate(headers: &[RuntimeHeader]) -> Challenges {
21 headers
22 .iter()
23 .filter(|header| header.name.eq_ignore_ascii_case("www-authenticate"))
24 .map(|header| header.value.clone())
25 .collect::<Vec<_>>()
26 .into()
27}
28
29impl ClientError {
30 /// The decoded error envelope of a [`ClientError::Remote`]: `code`,
31 /// `message`, `details` and the extra members in `extra`. `None` for
32 /// any other variant, or when the body did not decode.
33 pub fn remote_error(&self) -> Option<&CratestackErrorResponse> {
34 match self {
35 ClientError::Remote { error, .. } => error.as_deref(),
36 _ => None,
37 }
38 }
39
40 /// The `WWW-Authenticate` values of a [`ClientError::Remote`], raw and
41 /// in order; empty for any other variant.
42 pub fn challenges(&self) -> &[String] {
43 match self {
44 ClientError::Remote { challenges, .. } => challenges.as_slice(),
45 _ => &[],
46 }
47 }
48}
49
50impl RpcClientError {
51 /// The server's answer, for an [`RpcClientError::Remote`].
52 pub fn remote(&self) -> Option<&RpcRemoteError> {
53 match self {
54 RpcClientError::Remote(remote) => Some(remote),
55 _ => None,
56 }
57 }
58
59 /// The `WWW-Authenticate` values of an [`RpcClientError::Remote`], raw
60 /// and in order; empty for any other variant.
61 pub fn challenges(&self) -> &[String] {
62 self.remote()
63 .map_or(&[], |remote| remote.challenges.as_slice())
64 }
65}
66
67#[cfg(test)]
68mod tests;