use home::home_dir;
use spacetimedb_lib::{bsatn, de::Deserialize, ser::Serialize};
use std::path::PathBuf;
use thiserror::Error;
const CREDENTIALS_DIR: &str = ".spacetimedb_client_credentials";
#[derive(Error, Debug)]
pub enum CredentialFileError {
#[error("Failed to determine user home directory as root for credentials storage")]
DetermineHomeDir,
#[error("Error creating credential storage directory {path}")]
CreateDir {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Error serializing credentials for storage in file")]
Serialize {
#[source]
source: bsatn::EncodeError,
},
#[error("Error writing BSATN-serialized credentials to file {path}")]
Write {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Error reading BSATN-serialized credentials from file {path}")]
Read {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Error deserializing credentials from bytes stored in file {path}")]
Deserialize {
path: PathBuf,
#[source]
source: bsatn::DecodeError,
},
}
pub struct File {
filename: String,
}
#[derive(Serialize, Deserialize)]
struct Credentials {
token: String,
}
impl File {
pub fn new(key: impl Into<String>) -> Self {
Self { filename: key.into() }
}
fn determine_home_dir() -> Result<PathBuf, CredentialFileError> {
home_dir().ok_or(CredentialFileError::DetermineHomeDir)
}
fn ensure_credentials_dir() -> Result<(), CredentialFileError> {
let mut path = Self::determine_home_dir()?;
path.push(CREDENTIALS_DIR);
std::fs::create_dir_all(&path).map_err(|source| CredentialFileError::CreateDir { path, source })
}
fn path(&self) -> Result<PathBuf, CredentialFileError> {
let mut path = Self::determine_home_dir()?;
path.push(CREDENTIALS_DIR);
path.push(&self.filename);
Ok(path)
}
pub fn save(self, token: impl Into<String>) -> Result<(), CredentialFileError> {
Self::ensure_credentials_dir()?;
let creds = bsatn::to_vec(&Credentials { token: token.into() })
.map_err(|source| CredentialFileError::Serialize { source })?;
let path = self.path()?;
std::fs::write(&path, creds).map_err(|source| CredentialFileError::Write { path, source })?;
Ok(())
}
pub fn load(self) -> Result<Option<String>, CredentialFileError> {
let path = self.path()?;
let bytes = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(e) if matches!(e.kind(), std::io::ErrorKind::NotFound) => return Ok(None),
Err(source) => return Err(CredentialFileError::Read { path, source }),
};
let creds = bsatn::from_slice::<Credentials>(&bytes)
.map_err(|source| CredentialFileError::Deserialize { path, source })?;
Ok(Some(creds.token))
}
}