Skip to main content

cratestack_client_rust/remote_refusal/
challenges.rs

1//! [`Challenges`]: the `WWW-Authenticate` values of an error answer.
2
3use std::ops::Deref;
4
5/// Every `WWW-Authenticate` value an error answer carried, raw and in the
6/// order they arrived (cratestack#1243): an `AuthRefusal`'s challenges.
7/// Unparsed: a challenge's own grammar (RFC 9110 ยง11.6.1) is the caller's.
8///
9/// Reads as a `[String]` (it derefs to one). One pointer wide, and empty
10/// without allocating, so it keeps `ClientError` and `RpcClientError` small
11/// (`clippy::result_large_err`).
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13#[allow(
14    clippy::box_collection,
15    reason = "a thin pointer: `Vec` or `Box<[_]>` would push `RpcClientError` past `result_large_err`"
16)]
17pub struct Challenges(Option<Box<Vec<String>>>);
18
19impl Challenges {
20    /// The values as a slice; empty when the answer had none.
21    pub fn as_slice(&self) -> &[String] {
22        self.0.as_deref().map_or(&[], Vec::as_slice)
23    }
24}
25
26impl From<Vec<String>> for Challenges {
27    fn from(values: Vec<String>) -> Self {
28        Self((!values.is_empty()).then(|| Box::new(values)))
29    }
30}
31
32impl Deref for Challenges {
33    type Target = [String];
34
35    fn deref(&self) -> &[String] {
36        self.as_slice()
37    }
38}
39
40impl<'a> IntoIterator for &'a Challenges {
41    type Item = &'a String;
42    type IntoIter = std::slice::Iter<'a, String>;
43
44    fn into_iter(self) -> Self::IntoIter {
45        self.as_slice().iter()
46    }
47}