@@ -14,6 +14,7 @@ use cargo_util::paths;
14
14
use cargo_util_schemas:: core:: PartialVersion ;
15
15
use cargo_util_schemas:: manifest:: PathBaseName ;
16
16
use cargo_util_schemas:: manifest:: RustVersion ;
17
+ use crate_spec:: CrateSpecResolutionError ;
17
18
use indexmap:: IndexSet ;
18
19
use itertools:: Itertools ;
19
20
use toml_edit:: Item as TomlItem ;
@@ -104,6 +105,10 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
104
105
options. gctx ,
105
106
& mut registry,
106
107
)
108
+ . map_err ( |err| match err. downcast :: < CrateSpecResolutionError > ( ) {
109
+ Ok ( err) => add_spec_fix_suggestion ( err, raw) ,
110
+ Err ( other) => other,
111
+ } )
107
112
} )
108
113
. collect :: < CargoResult < Vec < _ > > > ( ) ?
109
114
} ;
@@ -1258,3 +1263,100 @@ fn precise_version(version_req: &semver::VersionReq) -> Option<String> {
1258
1263
. max ( )
1259
1264
. map ( |v| v. to_string ( ) )
1260
1265
}
1266
+
1267
+ /// Help with invalid arguments to `cargo add`
1268
+ fn add_spec_fix_suggestion ( err : CrateSpecResolutionError , arg : & DepOp ) -> crate :: Error {
1269
+ if let Some ( note) = spec_fix_suggestion_inner ( arg) {
1270
+ return anyhow:: format_err!( "{err}\n note: {note}" ) ;
1271
+ }
1272
+ err. into ( )
1273
+ }
1274
+
1275
+ fn spec_fix_suggestion_inner ( arg : & DepOp ) -> Option < & ' static str > {
1276
+ let spec = arg. crate_spec . as_deref ( ) ?;
1277
+
1278
+ let looks_like_git_url = spec. starts_with ( "git@" )
1279
+ || spec. starts_with ( "ssh:" )
1280
+ || spec. ends_with ( ".git" )
1281
+ || spec. starts_with ( "https://git" ) ; // compromise between favoring a couple of top sites vs trying to list every git host
1282
+
1283
+ // check if the arg is present to avoid suggesting it redundantly
1284
+ if arg. git . is_none ( ) && looks_like_git_url {
1285
+ Some ( "git URLs must be specified with --git <URL>" )
1286
+ } else if arg. registry . is_none ( )
1287
+ && ( spec. starts_with ( "registry+" ) || spec. starts_with ( "sparse+" ) )
1288
+ {
1289
+ Some ( "registy can be specified with --registry <NAME>" )
1290
+ } else if spec. contains ( "://" ) || looks_like_git_url {
1291
+ Some ( "`cargo add` expects crates specified as 'name' or 'name@version', not URLs" )
1292
+ } else if arg. path . is_none ( ) && spec. contains ( '/' ) {
1293
+ Some ( "local crates can be added with --path <DIR>" )
1294
+ } else {
1295
+ None
1296
+ }
1297
+ }
1298
+
1299
+ #[ test]
1300
+ fn test_spec_fix_suggestion ( ) {
1301
+ fn dep ( crate_spec : & str ) -> DepOp {
1302
+ DepOp {
1303
+ crate_spec : Some ( crate_spec. into ( ) ) ,
1304
+ rename : None ,
1305
+ features : None ,
1306
+ default_features : None ,
1307
+ optional : None ,
1308
+ public : None ,
1309
+ registry : None ,
1310
+ path : None ,
1311
+ base : None ,
1312
+ git : None ,
1313
+ branch : None ,
1314
+ rev : None ,
1315
+ tag : None ,
1316
+ }
1317
+ }
1318
+
1319
+ #[ track_caller]
1320
+ fn err_for ( dep : & DepOp ) -> String {
1321
+ add_spec_fix_suggestion (
1322
+ CrateSpec :: resolve ( dep. crate_spec . as_deref ( ) . unwrap ( ) ) . unwrap_err ( ) ,
1323
+ & dep,
1324
+ )
1325
+ . to_string ( )
1326
+ }
1327
+
1328
+ for path in [ "../some/path" , "/absolute/path" , "~/dir/Cargo.toml" ] {
1329
+ let mut dep = dep ( path) ;
1330
+ assert ! ( err_for( & dep) . contains( "note: local crates can be added with --path <DIR>" ) ) ;
1331
+
1332
+ dep. path = Some ( "." . into ( ) ) ;
1333
+ assert ! ( !err_for( & dep) . contains( "--git" ) ) ;
1334
+ assert ! ( !err_for( & dep) . contains( "--registry" ) ) ;
1335
+ assert ! ( !err_for( & dep) . contains( "--path" ) ) ;
1336
+ }
1337
+
1338
+ assert ! ( err_for( & dep(
1339
+ "registry+https://private.local:8000/index#[email protected] "
1340
+ ) )
1341
+ . contains( "--registry <NAME>" ) ) ;
1342
+
1343
+ for git_url in [
1344
+ "git@host:dir/repo.git" ,
1345
+ "https://gitlab.com/~user/crate.git" ,
1346
+ "ssh://host/path" ,
1347
+ ] {
1348
+ let mut dep = dep ( git_url) ;
1349
+ let msg = err_for ( & dep) ;
1350
+ assert ! (
1351
+ msg. contains( "note: git URLs must be specified with --git <URL>" ) ,
1352
+ "{msg} {dep:?}"
1353
+ ) ;
1354
+ assert ! ( !err_for( & dep) . contains( "--path" ) ) ;
1355
+ assert ! ( !err_for( & dep) . contains( "--registry" ) ) ;
1356
+
1357
+ dep. git = Some ( "true" . into ( ) ) ;
1358
+ let msg = err_for ( & dep) ;
1359
+ assert ! ( !msg. contains( "--git" ) ) ;
1360
+ assert ! ( msg. contains( "'name@version', not URLs" ) , "{msg} {dep:?}" ) ;
1361
+ }
1362
+ }
0 commit comments