http_authentication/schemes/bearer/
credentials.rs

1use alloc::{boxed::Box, format, string::String};
2use core::str::{self, FromStr};
3
4use crate::{schemes::NAME_BEARER as NAME, SP};
5
6//
7#[derive(Debug, Clone)]
8pub struct Credentials {
9    pub token: Box<str>,
10}
11
12impl Credentials {
13    pub fn new(token: impl AsRef<str>) -> Self {
14        Self {
15            token: token.as_ref().into(),
16        }
17    }
18
19    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, CredentialsParseError> {
20        let bytes = bytes.as_ref();
21
22        if bytes.len() < NAME.len() + 1 {
23            return Err(CredentialsParseError::Other("too short"));
24        }
25
26        if !&bytes[..NAME.len()].eq_ignore_ascii_case(NAME.as_bytes()) {
27            return Err(CredentialsParseError::SchemeMismatch);
28        }
29
30        if bytes[NAME.len()..NAME.len() + 1] != [SP as u8] {
31            return Err(CredentialsParseError::OneSPMismatch);
32        }
33
34        let token68_bytes = &bytes[NAME.len() + 1..];
35
36        let token = token68_bytes;
37        let token = str::from_utf8(token).map_err(CredentialsParseError::TokenToStrFailed)?;
38
39        Ok(Self::new(token))
40    }
41
42    fn internal_to_string(&self) -> String {
43        format!("{NAME}{SP}{}", self.token)
44    }
45}
46
47//
48#[derive(Debug)]
49pub enum CredentialsParseError {
50    SchemeMismatch,
51    OneSPMismatch,
52    TokenToStrFailed(str::Utf8Error),
53    Other(&'static str),
54}
55
56impl core::fmt::Display for CredentialsParseError {
57    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58        write!(f, "{self:?}")
59    }
60}
61
62#[cfg(feature = "std")]
63impl std::error::Error for CredentialsParseError {}
64
65//
66impl core::fmt::Display for Credentials {
67    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68        write!(f, "{}", self.internal_to_string())
69    }
70}
71
72//
73impl FromStr for Credentials {
74    type Err = CredentialsParseError;
75
76    fn from_str(s: &str) -> Result<Self, Self::Err> {
77        Self::from_bytes(s.as_bytes())
78    }
79}
80
81//
82//
83//
84#[cfg(test)]
85pub(crate) const DEMO_CREDENTIALS_STR: &str = "Bearer mF_9.B5f-4.1JqM";
86#[cfg(test)]
87pub(crate) const DEMO_CREDENTIALS_TOKEN_STR: &str = "mF_9.B5f-4.1JqM";
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    use alloc::string::ToString as _;
94
95    #[test]
96    fn test_parse_and_render() {
97        let c = DEMO_CREDENTIALS_STR.parse::<Credentials>().unwrap();
98        assert_eq!(c.token, DEMO_CREDENTIALS_TOKEN_STR.into());
99        assert_eq!(c.to_string(), DEMO_CREDENTIALS_STR);
100
101        //
102        match Credentials::from_str("Bearer") {
103            Err(CredentialsParseError::Other(err)) => {
104                assert_eq!(err, "too short")
105            }
106            x => panic!("{x:?}"),
107        }
108
109        match Credentials::from_str("MyScheme ") {
110            Err(CredentialsParseError::SchemeMismatch) => {}
111            x => panic!("{x:?}"),
112        }
113
114        match Credentials::from_str("Bearer-") {
115            Err(CredentialsParseError::OneSPMismatch) => {}
116            x => panic!("{x:?}"),
117        }
118    }
119}