|
1 | 1 | //! Helpers for validating and checking names like package and crate names.
|
2 | 2 |
|
3 |
| -use anyhow::bail; |
4 |
| -use anyhow::Result; |
| 3 | +type Result<T> = std::result::Result<T, NameValidationError>; |
| 4 | + |
| 5 | +/// Error validating names in Cargo. |
| 6 | +#[non_exhaustive] |
| 7 | +#[derive(Debug, thiserror::Error)] |
| 8 | +pub enum NameValidationError { |
| 9 | + #[error("{0} cannot be empty")] |
| 10 | + Empty(&'static str), |
| 11 | + |
| 12 | + #[error("invalid character `{ch}` in {what}: `{name}`, {reason}")] |
| 13 | + InvalidCharacter { |
| 14 | + ch: char, |
| 15 | + what: &'static str, |
| 16 | + name: String, |
| 17 | + reason: &'static str, |
| 18 | + }, |
| 19 | + |
| 20 | + #[error( |
| 21 | + "profile name `{name}` is reserved\n{help}\n\ |
| 22 | + See https://doc.rust-lang.org/cargo/reference/profiles.html \ |
| 23 | + for more on configuring profiles." |
| 24 | + )] |
| 25 | + ProfileNameReservedKeyword { name: String, help: &'static str }, |
| 26 | + |
| 27 | + #[error("feature named `{0}` is not allowed to start with `dep:`")] |
| 28 | + FeatureNameStartsWithDepColon(String), |
| 29 | +} |
5 | 30 |
|
6 | 31 | /// Check the base requirements for a package name.
|
7 | 32 | ///
|
8 | 33 | /// This can be used for other things than package names, to enforce some
|
9 | 34 | /// level of sanity. Note that package names have other restrictions
|
10 | 35 | /// elsewhere. `cargo new` has a few restrictions, such as checking for
|
11 | 36 | /// reserved names. crates.io has even more restrictions.
|
12 |
| -pub fn validate_package_name(name: &str, what: &str, help: &str) -> Result<()> { |
| 37 | +pub fn validate_package_name(name: &str, what: &'static str, help: &str) -> Result<()> { |
13 | 38 | if name.is_empty() {
|
14 |
| - bail!("{what} cannot be empty"); |
| 39 | + return Err(NameValidationError::Empty(what)); |
15 | 40 | }
|
16 | 41 |
|
17 | 42 | let mut chars = name.chars();
|
18 | 43 | if let Some(ch) = chars.next() {
|
19 | 44 | if ch.is_digit(10) {
|
20 | 45 | // A specific error for a potentially common case.
|
21 |
| - bail!( |
22 |
| - "the name `{}` cannot be used as a {}, \ |
23 |
| - the name cannot start with a digit{}", |
24 |
| - name, |
| 46 | + return Err(NameValidationError::InvalidCharacter { |
| 47 | + ch, |
25 | 48 | what,
|
26 |
| - help |
27 |
| - ); |
| 49 | + name: name.into(), |
| 50 | + reason: "the name cannot start with a digit", |
| 51 | + }); |
28 | 52 | }
|
29 | 53 | if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
|
30 |
| - bail!( |
31 |
| - "invalid character `{}` in {}: `{}`, \ |
32 |
| - the first character must be a Unicode XID start character \ |
33 |
| - (most letters or `_`){}", |
| 54 | + return Err(NameValidationError::InvalidCharacter { |
34 | 55 | ch,
|
35 | 56 | what,
|
36 |
| - name, |
37 |
| - help |
38 |
| - ); |
| 57 | + name: name.into(), |
| 58 | + reason: "the first character must be a Unicode XID start character \ |
| 59 | + (most letters or `_`)", |
| 60 | + }); |
39 | 61 | }
|
40 | 62 | }
|
41 | 63 | for ch in chars {
|
42 | 64 | if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
|
43 |
| - bail!( |
44 |
| - "invalid character `{}` in {}: `{}`, \ |
45 |
| - characters must be Unicode XID characters \ |
46 |
| - (numbers, `-`, `_`, or most letters){}", |
| 65 | + return Err(NameValidationError::InvalidCharacter { |
47 | 66 | ch,
|
48 | 67 | what,
|
49 |
| - name, |
50 |
| - help |
51 |
| - ); |
| 68 | + name: name.into(), |
| 69 | + reason: "characters must be Unicode XID characters \ |
| 70 | + (numbers, `-`, `_`, or most letters)", |
| 71 | + }); |
52 | 72 | }
|
53 | 73 | }
|
54 | 74 | Ok(())
|
@@ -83,37 +103,28 @@ pub fn validate_profile_name(name: &str) -> Result<()> {
|
83 | 103 | .chars()
|
84 | 104 | .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
|
85 | 105 | {
|
86 |
| - bail!( |
87 |
| - "invalid character `{}` in profile name `{}`\n\ |
88 |
| - Allowed characters are letters, numbers, underscore, and hyphen.", |
| 106 | + return Err(NameValidationError::InvalidCharacter { |
89 | 107 | ch,
|
90 |
| - name |
91 |
| - ); |
| 108 | + what: "profile name", |
| 109 | + name: name.into(), |
| 110 | + reason: "allowed characters are letters, numbers, underscore, and hyphen", |
| 111 | + }); |
92 | 112 | }
|
93 | 113 |
|
94 |
| - const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \ |
95 |
| - for more on configuring profiles."; |
96 |
| - |
97 | 114 | let lower_name = name.to_lowercase();
|
98 | 115 | if lower_name == "debug" {
|
99 |
| - bail!( |
100 |
| - "profile name `{}` is reserved\n\ |
101 |
| - To configure the default development profile, use the name `dev` \ |
102 |
| - as in [profile.dev]\n\ |
103 |
| - {}", |
104 |
| - name, |
105 |
| - SEE_DOCS |
106 |
| - ); |
| 116 | + return Err(NameValidationError::ProfileNameReservedKeyword { |
| 117 | + name: name.into(), |
| 118 | + help: "To configure the default development profile, \ |
| 119 | + use the name `dev` as in [profile.dev]", |
| 120 | + }); |
107 | 121 | }
|
108 | 122 | if lower_name == "build-override" {
|
109 |
| - bail!( |
110 |
| - "profile name `{}` is reserved\n\ |
111 |
| - To configure build dependency settings, use [profile.dev.build-override] \ |
112 |
| - and [profile.release.build-override]\n\ |
113 |
| - {}", |
114 |
| - name, |
115 |
| - SEE_DOCS |
116 |
| - ); |
| 123 | + return Err(NameValidationError::ProfileNameReservedKeyword { |
| 124 | + name: name.into(), |
| 125 | + help: "To configure build dependency settings, use [profile.dev.build-override] \ |
| 126 | + and [profile.release.build-override]", |
| 127 | + }); |
117 | 128 | }
|
118 | 129 |
|
119 | 130 | // These are some arbitrary reservations. We have no plans to use
|
@@ -144,46 +155,55 @@ pub fn validate_profile_name(name: &str) -> Result<()> {
|
144 | 155 | | "uninstall"
|
145 | 156 | ) || lower_name.starts_with("cargo")
|
146 | 157 | {
|
147 |
| - bail!( |
148 |
| - "profile name `{}` is reserved\n\ |
149 |
| - Please choose a different name.\n\ |
150 |
| - {}", |
151 |
| - name, |
152 |
| - SEE_DOCS |
153 |
| - ); |
| 158 | + return Err(NameValidationError::ProfileNameReservedKeyword { |
| 159 | + name: name.into(), |
| 160 | + help: "Please choose a different name.", |
| 161 | + }); |
154 | 162 | }
|
155 | 163 |
|
156 | 164 | Ok(())
|
157 | 165 | }
|
158 | 166 |
|
159 | 167 | pub fn validate_feature_name(name: &str) -> Result<()> {
|
| 168 | + let what = "feature name"; |
160 | 169 | if name.is_empty() {
|
161 |
| - bail!("feature name cannot be empty"); |
| 170 | + return Err(NameValidationError::Empty(what)); |
162 | 171 | }
|
163 | 172 |
|
164 | 173 | if name.starts_with("dep:") {
|
165 |
| - bail!("feature named `{name}` is not allowed to start with `dep:`",); |
| 174 | + return Err(NameValidationError::FeatureNameStartsWithDepColon( |
| 175 | + name.into(), |
| 176 | + )); |
166 | 177 | }
|
167 | 178 | if name.contains('/') {
|
168 |
| - bail!("feature named `{name}` is not allowed to contain slashes",); |
| 179 | + return Err(NameValidationError::InvalidCharacter { |
| 180 | + ch: '/', |
| 181 | + what, |
| 182 | + name: name.into(), |
| 183 | + reason: "feature name is not allowed to contain slashes", |
| 184 | + }); |
169 | 185 | }
|
170 | 186 | let mut chars = name.chars();
|
171 | 187 | if let Some(ch) = chars.next() {
|
172 | 188 | if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
|
173 |
| - bail!( |
174 |
| - "invalid character `{ch}` in feature `{name}`, \ |
175 |
| - the first character must be a Unicode XID start character or digit \ |
176 |
| - (most letters or `_` or `0` to `9`)", |
177 |
| - ); |
| 189 | + return Err(NameValidationError::InvalidCharacter { |
| 190 | + ch, |
| 191 | + what, |
| 192 | + name: name.into(), |
| 193 | + reason: "the first character must be a Unicode XID start character or digit \ |
| 194 | + (most letters or `_` or `0` to `9`)", |
| 195 | + }); |
178 | 196 | }
|
179 | 197 | }
|
180 | 198 | for ch in chars {
|
181 | 199 | if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') {
|
182 |
| - bail!( |
183 |
| - "invalid character `{ch}` in feature `{name}`, \ |
184 |
| - characters must be Unicode XID characters, '-', `+`, or `.` \ |
185 |
| - (numbers, `+`, `-`, `_`, `.`, or most letters)", |
186 |
| - ); |
| 200 | + return Err(NameValidationError::InvalidCharacter { |
| 201 | + ch, |
| 202 | + what, |
| 203 | + name: name.into(), |
| 204 | + reason: "characters must be Unicode XID characters, '-', `+`, or `.` \ |
| 205 | + (numbers, `+`, `-`, `_`, `.`, or most letters)", |
| 206 | + }); |
187 | 207 | }
|
188 | 208 | }
|
189 | 209 | Ok(())
|
|
0 commit comments