dotlock-bin 1.0.1

Encrypted project-local environment variables manager
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DotLockError {
    #[error("invalid master password")]
    InvalidMasterPassword,

    #[error("no TTY available to prompt for the master password")]
    NoTtyForPassword,

    #[error("master password is weak: missing {missing}")]
    WeakPassword { missing: String },

    #[error("DotLock is not initialized in this directory")]
    ProjectNotInitialized,

    #[error("a DotLock vault already exists in `.lock/`")]
    ProjectAlreadyInitialized,

    #[error("environment `{name}` is not initialized in this project")]
    EnvironmentNotInitialized { name: String },

    #[error("environment `{name}` already exists")]
    EnvironmentAlreadyExists { name: String },

    #[error(
        "environment name `{name}` is invalid (use up to 32 ASCII letters, digits, `-` or `_`, starting with a letter or digit)"
    )]
    InvalidEnvironmentName { name: String },

    #[error(
        "vault is in a legacy format (missing vault UUID, secret UUIDs, or the integrity hash)"
    )]
    LegacyVaultFormat,

    #[error("secret `{name}` not found")]
    SecretNotFound { name: String },

    #[error("`.lock/secrets.lock` was modified outside of DotLock")]
    TamperedSecretsFile,

    #[error("`.lock/vault.toml` metadata failed authentication (modified outside of DotLock)")]
    MetadataTampered,

    #[error(
        "vault epoch {found} is older than the newest state seen on this machine ({last_seen}); refusing a potential rollback"
    )]
    VaultRolledBack { found: u64, last_seen: u64 },

    #[error("secret `{id}` has no key wrapping in `vault.toml` (`wrapped_sdks_under_kek`)")]
    MissingSecretKeyWrapping { id: String },

    #[error("vault has an unreconciled merge; run `dl reconcile`")]
    UnreconciledMerge,

    #[error("variable name `{name}` is invalid")]
    InvalidVariableName { name: String },

    #[error("could not parse `{path}` at line {line}: {message}")]
    EnvParseError {
        path: String,
        line: usize,
        message: String,
    },

    #[error("algorithm `{alg}` is not supported")]
    UnsupportedAlgorithm { alg: String },

    #[error("no command was provided to `dl run`")]
    MissingCommand,

    #[error("the command exited with status {status}")]
    CommandFailed { status: String },

    #[error("local identity is not initialized")]
    LocalIdentityNotInitialized,

    #[error("local identity already exists")]
    LocalIdentityAlreadyInitialized,

    #[error("shared recipient `{query}` not found")]
    RecipientNotFound { query: String },

    #[error("access denied to secret `{secret}`")]
    AccessDenied { secret: String },

    #[error("invalid identity passphrase")]
    InvalidIdentityPassphrase,

    #[error("cannot resolve the home/config directory; set HOME or DOTLOCK_HOME")]
    HomeDirUnavailable,

    #[error("crypto failure: {0}")]
    Crypto(String),

    #[error("I/O failure: {0}")]
    Io(String),

    #[error("aborted by user")]
    Aborted,

    #[error("confirmation required to {action}")]
    ConfirmationRequired { action: String },
}

impl DotLockError {
    pub fn hint(&self) -> Option<&'static str> {
        match self {
            DotLockError::InvalidMasterPassword => {
                Some("check caps lock and your keyboard layout, then try again")
            }
            DotLockError::NoTtyForPassword => Some(
                "set DOTLOCK_MASTER_PASSWORD, or pass --password-stdin / --password-file <path> (preferred: env vars can leak in CI logs and process listings)",
            ),
            DotLockError::WeakPassword { .. } => {
                Some("use at least 12 characters with lowercase, uppercase, a digit and a symbol")
            }
            DotLockError::ProjectNotInitialized => {
                Some("run `dl init` in this directory before using other commands")
            }
            DotLockError::ProjectAlreadyInitialized => {
                Some("use `set/get/list/unset`, or remove `.lock/` to start over")
            }
            DotLockError::EnvironmentNotInitialized { .. } => {
                Some("create it with `dl env add <name>`, or list existing ones with `dl env list`")
            }
            DotLockError::EnvironmentAlreadyExists { .. } => {
                Some("select it with `--env <name>` or `dl env use <name>`")
            }
            DotLockError::InvalidEnvironmentName { .. } => {
                Some("environment names become directory names under `.lock/envs/`")
            }
            DotLockError::LegacyVaultFormat => {
                Some("delete the `.lock/` directory and run `dl init` to create a fresh vault")
            }
            DotLockError::SecretNotFound { .. } => Some("list available secrets with `dl list`"),
            DotLockError::MetadataTampered => Some(
                "someone modified `.lock/vault.toml` outside DotLock; restore `.lock/` from a trusted backup or git revision",
            ),
            DotLockError::VaultRolledBack { .. } => Some(
                "the repo holds an older vault state than this machine has already seen; if this checkout of an older revision is intentional, re-run with DOTLOCK_ALLOW_VAULT_ROLLBACK=1",
            ),
            DotLockError::TamperedSecretsFile => Some(
                "someone modified `.lock/secrets.lock` outside DotLock; restore from a trusted backup or start over with `dl init`",
            ),
            DotLockError::MissingSecretKeyWrapping { .. } => Some(
                "the vault metadata and secrets file are out of sync; restore `.lock/` from a trusted git revision (e.g. `git checkout <rev> -- .lock/`) or re-set the secret with `dl set`",
            ),
            DotLockError::UnreconciledMerge => Some(
                "a git merge combined the vault files without re-signing them; run `dl reconcile` interactively to review and approve the merged content",
            ),
            DotLockError::InvalidVariableName { .. } => {
                Some("use only letters, digits and underscores; the name cannot start with a digit")
            }
            DotLockError::EnvParseError { .. } => Some(
                "each line must look like `KEY=VALUE` (quotes and `export` prefix are allowed)",
            ),
            DotLockError::UnsupportedAlgorithm { .. } => {
                Some("supported algorithms: xchacha20-poly1305")
            }
            DotLockError::MissingCommand => {
                Some("pass the command after `--`, e.g. `dl run -- node app.js`")
            }
            DotLockError::LocalIdentityNotInitialized => {
                Some("run `dl cert init` to create your local key pair before using shared access")
            }
            DotLockError::LocalIdentityAlreadyInitialized => Some(
                "use `dl cert show` to inspect the existing identity, or delete it manually before reinitializing",
            ),
            DotLockError::RecipientNotFound { .. } => {
                Some("list current recipients with `dl share list`")
            }
            DotLockError::AccessDenied { .. } => {
                Some("ask the vault owner to grant access to this secret")
            }
            DotLockError::InvalidIdentityPassphrase => {
                Some("enter the passphrase used when `dl cert init` created this local identity")
            }
            DotLockError::HomeDirUnavailable => Some(
                "DotLock refuses to write identities, key caches or audit logs into the current directory; export HOME (or DOTLOCK_HOME) pointing at a per-user directory",
            ),
            DotLockError::Aborted => None,
            DotLockError::ConfirmationRequired { .. } => Some(
                "stdin is not a TTY, so the interactive confirmation cannot run; re-run with --yes/-y to confirm explicitly",
            ),
            DotLockError::CommandFailed { .. } => None,
            DotLockError::Crypto(_) | DotLockError::Io(_) => None,
        }
    }
}

impl From<std::io::Error> for DotLockError {
    fn from(err: std::io::Error) -> Self {
        DotLockError::Io(err.to_string())
    }
}