use std::io;
use std::sync::Arc;
use thiserror::Error;
use tokio::sync::OnceCell;
use tokio::task::JoinError;
use crate::Credentials;
#[derive(Debug, Error)]
pub enum CredentialsPromptError {
#[error("Failed to read username")]
ReadUsername(#[source] io::Error),
#[error("Failed to read password")]
ReadPassword(#[source] io::Error),
#[error("Credential prompt task failed")]
Task(#[from] JoinError),
}
#[derive(Debug, Clone)]
pub struct CredentialsPrompt {
callback: fn() -> Result<Option<Credentials>, CredentialsPromptError>,
credentials: Arc<OnceCell<Option<Credentials>>>,
}
impl CredentialsPrompt {
pub fn new(callback: fn() -> Result<Option<Credentials>, CredentialsPromptError>) -> Self {
Self {
callback,
credentials: Arc::new(OnceCell::new()),
}
}
pub fn credentials(&self) -> Option<&Credentials> {
self.credentials.get().and_then(Option::as_ref)
}
pub fn was_unavailable(&self) -> bool {
self.credentials.get().is_some_and(Option::is_none)
}
pub async fn prompt(&self) -> Result<Option<Credentials>, CredentialsPromptError> {
self.credentials
.get_or_try_init(|| async { tokio::task::spawn_blocking(self.callback).await? })
.await
.cloned()
}
}