lychee_lib/types/basic_auth/
credentials.rs1use async_trait::async_trait;
2use std::str::FromStr;
3
4use headers::authorization::Credentials;
5use headers::{Authorization, authorization::Basic};
6use http::header::AUTHORIZATION;
7use reqwest::Request;
8use serde::Deserialize;
9use thiserror::Error;
10
11use crate::chain::{ChainResult, Handler};
12
13#[derive(Copy, Clone, Debug, Error, PartialEq)]
14pub enum BasicAuthCredentialsParseError {
15 #[error("Invalid basic auth credentials syntax")]
16 InvalidSyntax,
17
18 #[error("Missing basic auth password")]
19 MissingPassword,
20
21 #[error("Missing basic auth username")]
22 MissingUsername,
23
24 #[error(
25 "Too many values separated by colon. Expected 2, got {0}. Valid form is '<username>:<password>'"
26 )]
27 TooManyParts(usize),
28}
29
30#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
33pub struct BasicAuthCredentials {
34 pub username: String,
36
37 pub password: String,
39}
40
41impl FromStr for BasicAuthCredentials {
42 type Err = BasicAuthCredentialsParseError;
43
44 fn from_str(credentials: &str) -> Result<Self, Self::Err> {
45 let parts: Vec<_> = credentials.trim().split(':').collect();
46
47 if parts.len() <= 1 {
48 return Err(BasicAuthCredentialsParseError::InvalidSyntax);
49 }
50
51 if parts.len() > 2 {
52 return Err(BasicAuthCredentialsParseError::TooManyParts(parts.len()));
53 }
54
55 if parts[0].is_empty() {
56 return Err(BasicAuthCredentialsParseError::MissingUsername);
57 }
58
59 if parts[1].is_empty() {
60 return Err(BasicAuthCredentialsParseError::MissingPassword);
61 }
62
63 Ok(Self {
64 username: parts[0].to_string(),
65 password: parts[1].to_string(),
66 })
67 }
68}
69
70impl BasicAuthCredentials {
71 #[must_use]
73 pub fn to_authorization(&self) -> Authorization<Basic> {
74 Authorization::basic(&self.username, &self.password)
75 }
76
77 pub fn append_to_request(&self, request: &mut Request) {
79 request
80 .headers_mut()
81 .append(AUTHORIZATION, self.to_authorization().0.encode());
82 }
83}
84
85#[async_trait]
86impl<Response> Handler<Request, Response> for Option<BasicAuthCredentials> {
87 async fn handle(&mut self, mut request: Request) -> ChainResult<Request, Response> {
88 if let Some(credentials) = self {
89 credentials.append_to_request(&mut request);
90 }
91
92 ChainResult::Next(request)
93 }
94}