1use super::errors::*;
3
4#[derive(Debug)]
11pub struct CrateSpec {
12 pub name: String,
14 pub version_req: Option<String>,
16}
17
18impl CrateSpec {
19 pub fn resolve(pkg_id: &str) -> CargoResult<Self> {
21 let (name, version) = pkg_id
22 .split_once('@')
23 .map(|(n, v)| (n, Some(v)))
24 .unwrap_or((pkg_id, None));
25
26 let invalid: Vec<_> = name
27 .chars()
28 .filter(|c| !is_name_char(*c))
29 .map(|c| c.to_string())
30 .collect();
31 if !invalid.is_empty() {
32 return Err(anyhow::format_err!(
33 "Invalid name `{}`: {}",
34 name,
35 invalid.join(", ")
36 ));
37 }
38
39 if let Some(version) = version {
40 semver::VersionReq::parse(version)
41 .with_context(|| format!("Invalid version requirement `{version}`"))?;
42 }
43
44 Ok(Self {
45 name: name.to_owned(),
46 version_req: version.map(|s| s.to_owned()),
47 })
48 }
49}
50
51impl std::str::FromStr for CrateSpec {
52 type Err = Error;
53
54 fn from_str(s: &str) -> CargoResult<Self> {
55 Self::resolve(s)
56 }
57}
58
59fn is_name_char(c: char) -> bool {
60 c.is_alphanumeric() || ['-', '_'].contains(&c)
61}