use std::path::PathBuf;
use crate::change::ChangeId;
use crate::config::ConfigError;
use crate::follows::path::AttrPathParseError;
use crate::validate::ValidationError;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Flake(#[from] crate::Error),
#[error(transparent)]
Config(#[from] ConfigError),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("could not open flake.nix at {path}", path = path.display())]
FlakeNotFound {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("no flake.nix in directory {path}", path = path.display())]
FlakeDirEmpty { path: PathBuf },
#[error("`--flake` and `--lock` cannot be used with `follow [PATHS]`")]
IncompatibleFollowOptions,
#[error("no URI provided")]
NoUri,
#[error("no input id provided")]
NoId,
#[error("no inputs found in the flake")]
NoInputs,
#[error("invalid URI '{uri}'")]
InvalidUri {
uri: String,
#[source]
source: nix_uri::NixUriError,
},
#[error("invalid input id '{id}'")]
InvalidInputId {
id: String,
#[source]
source: AttrPathParseError,
},
#[error("invalid follows path '{path}'")]
InvalidFollowsPath {
path: String,
#[source]
source: AttrPathParseError,
},
#[error("could not infer id from flake reference '{uri}'")]
CouldNotInferId { uri: String },
#[error("input '{id}' has no pinnable URL (it may use follows or a non-standard format)")]
InputNotPinnable { id: String },
#[error("could not remove input '{id}'")]
CouldNotRemove { id: ChangeId },
#[error("could not read lock file '{path}'", path = path.display())]
LockFile {
path: PathBuf,
#[source]
source: crate::Error,
},
#[error("could not create follows relationship for '{id}'")]
FollowsCreateFailed { id: String },
#[error("validation failed after applying edits ({} issue(s))", .0.len())]
ValidationAfterEdit(Vec<ValidationError>),
#[error("{} file(s) failed during batch processing", failures.len())]
Batch {
failures: Vec<(PathBuf, Box<Error>)>,
},
#[error("no toggleable inputs in flake.nix")]
NoToggleableInputs,
#[error("multiple toggleable inputs")]
MultipleToggleableInputs { candidates: Vec<ToggleCandidate> },
#[error("no input named '{id}' in flake.nix")]
ToggleUnknownInput { id: String },
#[error("input '{id}' has no stored alternate")]
ToggleNoAlternate { id: String },
#[error(
"input '{id}' has {count} variants, cannot infer which to {action}",
count = alternates.len() + 1
)]
ToggleAmbiguousVariant {
id: String,
active: String,
alternates: Vec<String>,
action: ToggleAction,
},
#[error("could not match '{reference}' to any input")]
ToggleRefUnmatched { reference: String },
#[error("'{reference}' matches multiple inputs")]
ToggleRefAmbiguous {
reference: String,
candidates: Vec<RefCandidate>,
},
#[error("could not read path '{path}'")]
TogglePathMissing {
path: String,
#[source]
source: std::io::Error,
},
#[error(
"'{reference}' is already the active variant of '{id}'\n and no alternate is stored"
)]
ToggleAlreadyActive { reference: String, id: String },
#[error("'{reference}' is not a stored variant of '{id}'")]
ToggleRemoveUnstored {
reference: String,
id: String,
active: String,
alternates: Vec<String>,
},
#[error("'{reference}' is the active url of '{id}' and no alternate is stored to replace it")]
ToggleRemoveActive { reference: String, id: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToggleAction {
Activate,
Remove,
}
impl std::fmt::Display for ToggleAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Activate => write!(f, "activate"),
Self::Remove => write!(f, "remove"),
}
}
}
#[derive(Debug, Clone)]
pub struct ToggleCandidate {
pub id: String,
pub variants: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct RefCandidate {
pub id: String,
pub reason: String,
}
impl Error {
pub fn batch_bullets(&self) -> Option<Vec<String>> {
match self {
Self::Batch { failures } => Some(
failures
.iter()
.map(|(path, err)| {
format!(
"{}: {}",
path.display(),
chain_layers(err.as_ref()).join(": ")
)
})
.collect(),
),
_ => None,
}
}
pub fn validation_bullets(&self) -> Option<Vec<String>> {
match self {
Self::ValidationAfterEdit(errs) => Some(errs.iter().map(|e| e.to_string()).collect()),
_ => None,
}
}
pub fn candidate_bullets(&self) -> Option<Vec<String>> {
match self {
Self::MultipleToggleableInputs { candidates } => Some(
candidates
.iter()
.map(|c| format!("{}: {}", c.id, c.variants.join(" <-> ")))
.collect(),
),
Self::ToggleAmbiguousVariant {
active, alternates, ..
}
| Self::ToggleRemoveUnstored {
active, alternates, ..
} => Some(
std::iter::once(format!("{active} (active)"))
.chain(alternates.iter().cloned())
.collect(),
),
Self::ToggleRefAmbiguous { candidates, .. } => Some(
candidates
.iter()
.map(|c| format!("{} ({})", c.id, c.reason))
.collect(),
),
_ => None,
}
}
}
pub fn chain_layers(err: &(dyn std::error::Error + 'static)) -> Vec<String> {
let mut layers = vec![err.to_string()];
let mut current = err.source();
while let Some(source) = current {
layers.push(source.to_string());
current = source.source();
}
layers
}
pub type Result<T> = std::result::Result<T, Error>;