use std::collections::HashMap;
use std::sync::{Arc, LazyLock, RwLock};
use url::Url;
use crate::url::RepositoryUrl;
pub static GIT_STORE: LazyLock<GitStore> = LazyLock::new(GitStore::default);
#[derive(Debug, Default)]
pub struct GitStore(RwLock<HashMap<RepositoryUrl, Arc<Credentials>>>);
impl GitStore {
pub fn insert(&self, url: RepositoryUrl, credentials: Credentials) -> Option<Arc<Credentials>> {
self.0.write().unwrap().insert(url, Arc::new(credentials))
}
pub fn get(&self, url: &RepositoryUrl) -> Option<Arc<Credentials>> {
self.0.read().unwrap().get(url).cloned()
}
}
pub fn store_credentials_from_url(url: &Url) -> bool {
if let Some(credentials) = Credentials::from_url(url) {
tracing::debug!("Caching credentials for {url}");
GIT_STORE.insert(RepositoryUrl::new(url), credentials);
true
} else {
false
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Credentials {
username: Username,
password: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Default)]
pub(crate) struct Username(Option<String>);
impl Username {
pub(crate) fn new(value: Option<String>) -> Self {
if let Some(value) = value {
if value.is_empty() {
Self(None)
} else {
Self(Some(value))
}
} else {
Self(value)
}
}
pub(crate) fn as_deref(&self) -> Option<&str> {
self.0.as_deref()
}
}
impl From<String> for Username {
fn from(value: String) -> Self {
Self::new(Some(value))
}
}
impl From<Option<String>> for Username {
fn from(value: Option<String>) -> Self {
Self::new(value)
}
}
impl Credentials {
pub fn username(&self) -> Option<&str> {
self.username.as_deref()
}
pub fn password(&self) -> Option<&str> {
self.password.as_deref()
}
pub fn apply(&self, mut url: Url) -> Url {
if let Some(username) = self.username() {
let _ = url.set_username(username);
}
if let Some(password) = self.password() {
let _ = url.set_password(Some(password));
}
url
}
pub fn from_url(url: &Url) -> Option<Self> {
if url.username().is_empty() && url.password().is_none() {
return None;
}
Some(Self {
username: if url.username().is_empty() {
None
} else {
Some(
urlencoding::decode(url.username())
.expect("An encoded username should always decode")
.into_owned(),
)
}
.into(),
password: url.password().map(|password| {
urlencoding::decode(password)
.expect("An encoded password should always decode")
.into_owned()
}),
})
}
}