@@ -14,6 +14,7 @@ use cargo_util::paths;
1414use  cargo_util_schemas:: core:: PartialVersion ; 
1515use  cargo_util_schemas:: manifest:: PathBaseName ; 
1616use  cargo_util_schemas:: manifest:: RustVersion ; 
17+ use  crate_spec:: CrateSpecResolutionError ; 
1718use  indexmap:: IndexSet ; 
1819use  itertools:: Itertools ; 
1920use  toml_edit:: Item  as  TomlItem ; 
@@ -104,6 +105,10 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
104105                    options. gctx , 
105106                    & mut  registry, 
106107                ) 
108+                 . map_err ( |err| match  err. downcast :: < CrateSpecResolutionError > ( )  { 
109+                     Ok ( err)  => add_spec_fix_suggestion ( err,  raw) , 
110+                     Err ( other)  => other, 
111+                 } ) 
107112            } ) 
108113            . collect :: < CargoResult < Vec < _ > > > ( ) ?
109114    } ; 
@@ -1258,3 +1263,100 @@ fn precise_version(version_req: &semver::VersionReq) -> Option<String> {
12581263        . max ( ) 
12591264        . map ( |v| v. to_string ( ) ) 
12601265} 
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