use std::{collections::HashMap, io::ErrorKind, path::PathBuf};
use miette::{Context, IntoDiagnostic};
use serde::{Deserialize, Serialize};
use tokio::fs;
use crate::{
ManagedFile,
errors::{DeserializationError, FileExistsError, ReadError, SerializationError, WriteError},
registry::RegistryUri,
};
pub const CREDENTIALS_FILE: &str = "credentials.toml";
#[derive(Debug, Default, Clone)]
pub struct Credentials {
pub registry_tokens: HashMap<RegistryUri, String>,
}
impl Credentials {
fn location() -> miette::Result<PathBuf> {
Ok(crate::home().into_diagnostic()?.join(CREDENTIALS_FILE))
}
pub async fn exists() -> miette::Result<bool> {
fs::try_exists(Self::location()?)
.await
.into_diagnostic()
.wrap_err(FileExistsError(CREDENTIALS_FILE))
}
pub async fn read() -> miette::Result<Option<Self>> {
match fs::read_to_string(Self::location()?).await {
Ok(contents) => {
let raw: RawCredentialCollection = toml::from_str(&contents)
.into_diagnostic()
.wrap_err(DeserializationError(ManagedFile::Credentials))?;
Ok(Some(raw.into()))
}
Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
Err(error) => Err(error)
.into_diagnostic()
.wrap_err(ReadError(CREDENTIALS_FILE)),
}
}
pub async fn write(&self) -> miette::Result<()> {
let location = Self::location()?;
if let Some(parent) = location.parent() {
fs::create_dir(parent).await.ok();
}
let data: RawCredentialCollection = self.clone().into();
fs::write(
location,
toml::to_string(&data)
.into_diagnostic()
.wrap_err(SerializationError(ManagedFile::Credentials))?
.into_bytes(),
)
.await
.into_diagnostic()
.wrap_err(WriteError(CREDENTIALS_FILE))
}
pub async fn load() -> miette::Result<Self> {
Ok(Self::read().await?.unwrap_or_else(Credentials::default))
}
}
#[derive(Serialize, Deserialize)]
struct RawCredentialCollection {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
credentials: Vec<RawRegistryCredentials>,
}
#[derive(Serialize, Deserialize)]
struct RawRegistryCredentials {
uri: RegistryUri,
token: String,
}
impl From<RawCredentialCollection> for Credentials {
fn from(value: RawCredentialCollection) -> Self {
let credentials = value
.credentials
.into_iter()
.map(|it| (it.uri, it.token))
.collect();
Self {
registry_tokens: credentials,
}
}
}
impl From<Credentials> for RawCredentialCollection {
fn from(value: Credentials) -> Self {
let credentials = value
.registry_tokens
.into_iter()
.map(|(uri, token)| RawRegistryCredentials { uri, token })
.collect();
Self { credentials }
}
}