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