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}