Skip to main content

callisto_model/
error.rs

1use std::path::PathBuf;
2
3use crate::{DepKind, ManifestFormat, ManifestRole, PackageId, VersionGrammar, VersionParseError};
4
5#[derive(Debug, thiserror::Error, miette::Diagnostic, Clone, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum ModelError {
8    #[error("path `{path}` is absolute; callisto-model paths are workspace-root-relative")]
9    #[diagnostic(code(E001), help("Use a relative path relative to the workspace root."))]
10    AbsolutePath { path: PathBuf },
11
12    #[error("path `{path}` is not valid UTF-8; callisto serializes paths into its JSON contract")]
13    #[diagnostic(code(E002), help("Ensure all workspace file paths contain valid UTF-8 characters."))]
14    NonUtf8Path { path: PathBuf },
15
16    #[error("path `{path}` attempts to traverse outside the workspace root")]
17    #[diagnostic(
18        code(E003),
19        help("Keep workspace file paths strictly within the workspace directory.")
20    )]
21    PathTraversal { path: PathBuf },
22
23    #[error("`{raw}` is not a valid 40-character hexadecimal commit sha")]
24    #[diagnostic(code(E004), help("Provide a valid full 40-character commit SHA."))]
25    InvalidCommitSha { raw: String, reason: String },
26
27    #[error("manifest role {role:?} is not valid for format {format:?}")]
28    #[diagnostic(code(E005))]
29    InvalidRoleForFormat { role: String, format: String },
30
31    #[error("package `{package}` has no canonical manifest; at least one is required")]
32    #[diagnostic(code(E006), help("Add a canonical manifest file for the package."))]
33    NoCanonicalManifest { package: String },
34
35    #[error("package `{package}` has canonical manifests in disagreeing version grammars ({grammars:?}); its version of record has no single grammar")]
36    #[diagnostic(
37        code(E007),
38        help("Ensure all manifests for a package use consistent version grammars.")
39    )]
40    MixedVersionGrammars {
41        package: String,
42        grammars: Vec<VersionGrammar>,
43    },
44
45    #[error("package identity `{raw}` has ecosystem prefix `{prefix}` but no name after it")]
46    #[diagnostic(code(E008), help("Include package name after ecosystem prefix."))]
47    EmptyNameAfterPrefix { raw: String, prefix: String },
48
49    #[error("manifest path `{path}` has unsupported format")]
50    #[diagnostic(code(E009))]
51    UnknownManifestFormat { path: PathBuf },
52}
53
54impl ModelError {
55    pub fn invalid_role_for_format(role: &ManifestRole, format: &ManifestFormat) -> Self {
56        ModelError::InvalidRoleForFormat {
57            role: format!("{role:?}"),
58            format: format!("{format:?}"),
59        }
60    }
61
62    pub fn no_canonical_manifest(package: &PackageId) -> Self {
63        ModelError::NoCanonicalManifest {
64            package: package.display_name(),
65        }
66    }
67
68    pub fn mixed_version_grammars(package: &PackageId, grammars: Vec<VersionGrammar>) -> Self {
69        ModelError::MixedVersionGrammars {
70            package: package.display_name(),
71            grammars,
72        }
73    }
74}
75
76#[derive(Clone, Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq)]
77#[non_exhaustive]
78pub enum ManifestError {
79    #[error("failed to read `{path}`: {message}")]
80    #[diagnostic(code(E010), help("Check file permissions and path validity."))]
81    Read { path: PathBuf, message: String },
82
83    #[error("failed to write `{path}`: {message}")]
84    #[diagnostic(code(E011), help("Check write permissions on target directory."))]
85    Write { path: PathBuf, message: String },
86
87    #[error("`{path}` is not valid {format:?}: {message}")]
88    #[diagnostic(code(E012), help("Verify manifest syntax formatting."))]
89    Parse {
90        path: PathBuf,
91        format: ManifestFormat,
92        message: String,
93    },
94
95    #[error("`{path}` has no `{field}` field")]
96    #[diagnostic(code(E013))]
97    MissingField { path: PathBuf, field: &'static str },
98
99    #[error("`{path}` declares `{raw}` as its version, which is invalid: {source}")]
100    #[diagnostic(code(E014), help("Fix version string to follow valid semver or ecosystem grammar."))]
101    InvalidVersion {
102        path: PathBuf,
103        raw: String,
104        #[source]
105        source: VersionParseError,
106    },
107
108    #[error("`{path}` inherits `{key}` from the workspace root; write the root manifest instead")]
109    #[diagnostic(code(E015), help("Update workspace inheritance key in root manifest."))]
110    WorkspaceInherited { path: PathBuf, key: String },
111
112    #[error("`{path}` ({format:?}) is not a supported write target: {reason}")]
113    #[diagnostic(code(E016))]
114    ReadOnlyFormat {
115        path: PathBuf,
116        format: ManifestFormat,
117        reason: &'static str,
118    },
119
120    #[error("`{path}` has no `{kind:?}` dependency named `{name}`")]
121    #[diagnostic(code(E017))]
122    DependencyNotFound { path: PathBuf, name: String, kind: DepKind },
123
124    #[error("operation `{operation}` is not supported for `{path}` ({format:?})")]
125    #[diagnostic(code(E018))]
126    UnsupportedOperation {
127        path: PathBuf,
128        format: ManifestFormat,
129        operation: &'static str,
130    },
131
132    #[error("format-preserving write of `{path}` would not round-trip: {message}")]
133    #[diagnostic(code(E019), help("Ensure CST document retains formatting structure."))]
134    FormattingNotPreserved { path: PathBuf, message: String },
135
136    #[error("`{path}`: {message}")]
137    #[diagnostic(code(E027))]
138    InvariantViolation { path: PathBuf, message: String },
139
140    #[error("`{path}` dependency `{name}` has a TOML value that is neither a string nor a table; refusing to silently no-op the rewrite")]
141    #[diagnostic(
142        code(E028),
143        help("Fix the dependency's TOML shape to a plain string or a table before running callisto again.")
144    )]
145    UnrecognizedDependencyValue { path: PathBuf, name: String },
146}