cargo/ops/cargo_add/
crate_spec.rs1use anyhow::Context as _;
4
5use super::Dependency;
6use crate::util::toml_mut::dependency::RegistrySource;
7use crate::CargoResult;
8use cargo_util_schemas::manifest::PackageName;
9
10#[derive(Debug)]
16pub struct CrateSpec {
17 name: String,
19 version_req: Option<String>,
21}
22
23impl CrateSpec {
24 pub fn resolve(pkg_id: &str) -> CargoResult<Self> {
26 let (name, version) = pkg_id
27 .split_once('@')
28 .map(|(n, v)| (n, Some(v)))
29 .unwrap_or((pkg_id, None));
30
31 let package_name = PackageName::new(name);
32 if !pkg_id.contains("@") && package_name.is_err() {
33 for (idx, ch) in pkg_id.char_indices() {
34 if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
35 let mut suggested_pkg_id = pkg_id.to_string();
36 suggested_pkg_id.insert_str(idx, "@");
37 if let Ok(_) = CrateSpec::resolve(&suggested_pkg_id.as_str()) {
38 let err = package_name.unwrap_err();
39 return Err(
40 anyhow::format_err!("{err}\n\n\
41 help: if this is meant to be a package name followed by a version, insert an `@` like `{suggested_pkg_id}`").into());
42 }
43 }
44 }
45 }
46
47 package_name?;
48
49 if let Some(version) = version {
50 semver::VersionReq::parse(version).with_context(|| {
51 if let Some(stripped) = version.strip_prefix("v") {
52 return format!(
53 "the version provided, `{version}` is not a \
54 valid SemVer requirement\n\n\
55 help: changing the package to `{name}@{stripped}`",
56 );
57 }
58 format!("invalid version requirement `{version}`")
59 })?;
60 }
61
62 let id = Self {
63 name: name.to_owned(),
64 version_req: version.map(|s| s.to_owned()),
65 };
66
67 Ok(id)
68 }
69
70 pub fn to_dependency(&self) -> CargoResult<Dependency> {
72 let mut dep = Dependency::new(self.name());
73 if let Some(version_req) = self.version_req() {
74 dep = dep.set_source(RegistrySource::new(version_req));
75 }
76 Ok(dep)
77 }
78
79 pub fn name(&self) -> &str {
80 &self.name
81 }
82
83 pub fn version_req(&self) -> Option<&str> {
84 self.version_req.as_deref()
85 }
86}