use std::path::PathBuf;
use thiserror::Error;
pub type VersionResult<T> = Result<T, VersionError>;
#[derive(Debug, Error, Clone)]
pub enum VersionError {
#[error("Invalid version '{version}': {reason}")]
InvalidVersion {
version: String,
reason: String,
},
#[error("Failed to parse version '{version}': {reason}")]
ParseError {
version: String,
reason: String,
},
#[error("Invalid bump type '{bump}': {reason}")]
InvalidBump {
bump: String,
reason: String,
},
#[error("Invalid bump type '{bump_type}'. Must be one of: major, minor, patch, none")]
InvalidBumpType {
bump_type: String,
},
#[error("Circular dependency detected")]
CircularDependency {
cycle: Vec<String>,
},
#[error("Package '{name}' not found in workspace at '{workspace_root}'")]
PackageNotFound {
name: String,
workspace_root: PathBuf,
},
#[error("Failed to read package.json at '{path}': {reason}")]
PackageJsonError {
path: PathBuf,
reason: String,
},
#[error("Version resolution failed for package '{package}': {reason}")]
ResolutionFailed {
package: String,
reason: String,
},
#[error("Failed to propagate version update from '{from}' to '{to}': {reason}")]
PropagationFailed {
from: String,
to: String,
reason: String,
},
#[error("Invalid versioning strategy '{strategy}': expected 'independent' or 'unified'")]
InvalidStrategy {
strategy: String,
},
#[error("Failed to apply version updates to '{path}': {reason}")]
ApplyFailed {
path: PathBuf,
reason: String,
},
#[error("Dependency '{dependency}' not found in package '{package}'")]
DependencyNotFound {
package: String,
dependency: String,
},
#[error(
"Invalid version spec '{spec}' for dependency '{dependency}' in package '{package}': {reason}"
)]
InvalidVersionSpec {
package: String,
dependency: String,
spec: String,
reason: String,
},
#[error("Version conflict for dependency '{dependency}': {conflict}")]
VersionConflict {
dependency: String,
conflict: String,
},
#[error("Maximum propagation depth ({max_depth}) exceeded starting from package '{package}'")]
MaxDepthExceeded {
package: String,
max_depth: usize,
},
#[error("Failed to generate snapshot version for '{package}': {reason}")]
SnapshotFailed {
package: String,
reason: String,
},
#[error("No packages require version updates")]
NoPackagesToUpdate,
#[error("Invalid workspace root '{path}': {reason}")]
InvalidWorkspaceRoot {
path: PathBuf,
reason: String,
},
#[error("Filesystem error at '{path}': {reason}")]
FileSystemError {
path: PathBuf,
reason: String,
},
}
impl AsRef<str> for VersionError {
fn as_ref(&self) -> &str {
match self {
Self::InvalidVersion { .. } => "invalid version",
Self::ParseError { .. } => "version parse error",
Self::InvalidBump { .. } => "invalid bump type",
Self::InvalidBumpType { .. } => "invalid bump type",
Self::CircularDependency { .. } => "circular dependency",
Self::PackageNotFound { .. } => "package not found",
Self::PackageJsonError { .. } => "package.json error",
Self::ResolutionFailed { .. } => "version resolution failed",
Self::PropagationFailed { .. } => "version propagation failed",
Self::InvalidStrategy { .. } => "invalid versioning strategy",
Self::ApplyFailed { .. } => "failed to apply version updates",
Self::DependencyNotFound { .. } => "dependency not found",
Self::InvalidVersionSpec { .. } => "invalid version specification",
Self::VersionConflict { .. } => "version conflict",
Self::MaxDepthExceeded { .. } => "max propagation depth exceeded",
Self::SnapshotFailed { .. } => "snapshot version generation failed",
Self::NoPackagesToUpdate => "no packages to update",
Self::InvalidWorkspaceRoot { .. } => "invalid workspace root",
Self::FileSystemError { .. } => "filesystem error",
}
}
}
impl VersionError {
#[must_use]
pub fn cycle(&self) -> Option<&Vec<String>> {
match self {
Self::CircularDependency { cycle } => Some(cycle),
_ => None,
}
}
#[must_use]
pub fn cycle_display(&self) -> Option<String> {
self.cycle().map(|c| c.join(" -> "))
}
#[must_use]
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::FileSystemError { .. } | Self::PackageJsonError { .. } | Self::ApplyFailed { .. }
)
}
}