@@ -6,7 +6,7 @@ use rustc_hash::FxHashMap;
66use serde:: { Deserialize , Serialize } ;
77use thiserror:: Error ;
88use url:: Url ;
9- use uv_fs:: { LockedFile , with_added_extension} ;
9+ use uv_fs:: { LockedFile , LockedFileError , LockedFileMode , with_added_extension} ;
1010use uv_preview:: { Preview , PreviewFeatures } ;
1111use uv_redacted:: DisplaySafeUrl ;
1212
@@ -29,20 +29,24 @@ pub enum AuthBackend {
2929}
3030
3131impl AuthBackend {
32- pub fn from_settings ( preview : Preview ) -> Result < Self , TomlCredentialError > {
32+ pub async fn from_settings ( preview : Preview ) -> Result < Self , TomlCredentialError > {
3333 // If preview is enabled, we'll use the system-native store
3434 if preview. is_enabled ( PreviewFeatures :: NATIVE_AUTH ) {
3535 return Ok ( Self :: System ( KeyringProvider :: native ( ) ) ) ;
3636 }
3737
3838 // Otherwise, we'll use the plaintext credential store
3939 let path = TextCredentialStore :: default_file ( ) ?;
40- match TextCredentialStore :: read ( & path) {
40+ match TextCredentialStore :: read ( & path) . await {
4141 Ok ( ( store, lock) ) => Ok ( Self :: TextStore ( store, lock) ) ,
42- Err ( TomlCredentialError :: Io ( err) ) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => {
42+ Err ( err)
43+ if err
44+ . as_io_error ( )
45+ . is_some_and ( |err| err. kind ( ) == std:: io:: ErrorKind :: NotFound ) =>
46+ {
4347 Ok ( Self :: TextStore (
4448 TextCredentialStore :: default ( ) ,
45- TextCredentialStore :: lock ( & path) ?,
49+ TextCredentialStore :: lock ( & path) . await ?,
4650 ) )
4751 }
4852 Err ( err) => Err ( err) ,
@@ -70,6 +74,8 @@ pub enum AuthScheme {
7074pub enum TomlCredentialError {
7175 #[ error( transparent) ]
7276 Io ( #[ from] std:: io:: Error ) ,
77+ #[ error( transparent) ]
78+ LockedFile ( #[ from] LockedFileError ) ,
7379 #[ error( "Failed to parse TOML credential file: {0}" ) ]
7480 ParseError ( #[ from] toml:: de:: Error ) ,
7581 #[ error( "Failed to serialize credentials to TOML" ) ]
@@ -84,6 +90,21 @@ pub enum TomlCredentialError {
8490 TokenNotUnicode ( #[ from] std:: string:: FromUtf8Error ) ,
8591}
8692
93+ impl TomlCredentialError {
94+ pub fn as_io_error ( & self ) -> Option < & std:: io:: Error > {
95+ match self {
96+ Self :: Io ( err) => Some ( err) ,
97+ Self :: LockedFile ( err) => err. as_io_error ( ) ,
98+ Self :: ParseError ( _)
99+ | Self :: SerializeError ( _)
100+ | Self :: BasicAuthError ( _)
101+ | Self :: BearerAuthError ( _)
102+ | Self :: CredentialsDirError
103+ | Self :: TokenNotUnicode ( _) => None ,
104+ }
105+ }
106+ }
107+
87108#[ derive( Debug , Error ) ]
88109pub enum BasicAuthError {
89110 #[ error( "`username` is required with `scheme = basic`" ) ]
@@ -234,12 +255,12 @@ impl TextCredentialStore {
234255 }
235256
236257 /// Acquire a lock on the credentials file at the given path.
237- pub fn lock ( path : & Path ) -> Result < LockedFile , TomlCredentialError > {
258+ pub async fn lock ( path : & Path ) -> Result < LockedFile , TomlCredentialError > {
238259 if let Some ( parent) = path. parent ( ) {
239260 fs:: create_dir_all ( parent) ?;
240261 }
241262 let lock = with_added_extension ( path, ".lock" ) ;
242- Ok ( LockedFile :: acquire_blocking ( lock, "credentials store" ) ?)
263+ Ok ( LockedFile :: acquire ( lock, LockedFileMode :: Exclusive , "credentials store" ) . await ?)
243264 }
244265
245266 /// Read credentials from a file.
@@ -270,8 +291,8 @@ impl TextCredentialStore {
270291 /// Returns [`TextCredentialStore`] and a [`LockedFile`] to hold if mutating the store.
271292 ///
272293 /// If the store will not be written to following the read, the lock can be dropped.
273- pub fn read < P : AsRef < Path > > ( path : P ) -> Result < ( Self , LockedFile ) , TomlCredentialError > {
274- let lock = Self :: lock ( path. as_ref ( ) ) ?;
294+ pub async fn read < P : AsRef < Path > > ( path : P ) -> Result < ( Self , LockedFile ) , TomlCredentialError > {
295+ let lock = Self :: lock ( path. as_ref ( ) ) . await ?;
275296 let store = Self :: from_file ( path) ?;
276297 Ok ( ( store, lock) )
277298 }
@@ -447,8 +468,8 @@ mod tests {
447468 assert ! ( store. get_credentials( & url, None ) . is_none( ) ) ;
448469 }
449470
450- #[ test]
451- fn test_file_operations ( ) {
471+ #[ tokio :: test]
472+ async fn test_file_operations ( ) {
452473 let mut temp_file = NamedTempFile :: new ( ) . unwrap ( ) ;
453474 writeln ! (
454475 temp_file,
@@ -484,7 +505,7 @@ password = "pass2"
484505 store
485506 . write (
486507 temp_output. path ( ) ,
487- TextCredentialStore :: lock ( temp_file. path ( ) ) . unwrap ( ) ,
508+ TextCredentialStore :: lock ( temp_file. path ( ) ) . await . unwrap ( ) ,
488509 )
489510 . unwrap ( ) ;
490511
0 commit comments