@@ -59,6 +59,18 @@ fn clear_account(account: &str) -> Result<(), String> {
5959 }
6060}
6161
62+ #[ cfg( any( test, target_os = "macos" ) ) ]
63+ fn canonical_mutation_with_legacy_cleanup < T > (
64+ canonical_mutation : impl FnOnce ( ) -> Result < T , String > ,
65+ legacy_cleanup : impl FnOnce ( ) -> Result < ( ) , String > ,
66+ ) -> Result < T , String > {
67+ let value = canonical_mutation ( ) ?;
68+ if let Err ( error) = legacy_cleanup ( ) {
69+ log:: warn!( "Could not remove Berd's legacy OpenAI voice credential: {error}" ) ;
70+ }
71+ Ok ( value)
72+ }
73+
6274#[ cfg( target_os = "macos" ) ]
6375pub ( crate ) fn read ( credential : OpenAiVoiceCredential ) -> Result < Option < String > , String > {
6476 if let Some ( api_key) = read_account ( credential. account ( ) ) ? {
@@ -79,10 +91,14 @@ pub(crate) fn read(_credential: OpenAiVoiceCredential) -> Result<Option<String>,
7991#[ cfg( target_os = "macos" ) ]
8092pub ( crate ) fn store ( credential : OpenAiVoiceCredential , api_key : & str ) -> Result < ( ) , String > {
8193 let entry = entry ( credential. account ( ) ) ?;
82- entry
83- . set_password ( api_key)
84- . map_err ( |error| format ! ( "Could not save Berd's OpenAI voice credential: {error}" ) ) ?;
85- clear_account ( LEGACY_TTS_KEYCHAIN_ACCOUNT )
94+ canonical_mutation_with_legacy_cleanup (
95+ || {
96+ entry
97+ . set_password ( api_key)
98+ . map_err ( |error| format ! ( "Could not save Berd's OpenAI voice credential: {error}" ) )
99+ } ,
100+ || clear_account ( LEGACY_TTS_KEYCHAIN_ACCOUNT ) ,
101+ )
86102}
87103
88104#[ cfg( not( target_os = "macos" ) ) ]
@@ -92,8 +108,10 @@ pub(crate) fn store(_credential: OpenAiVoiceCredential, _api_key: &str) -> Resul
92108
93109#[ cfg( target_os = "macos" ) ]
94110pub ( crate ) fn clear ( credential : OpenAiVoiceCredential ) -> Result < ( ) , String > {
95- clear_account ( credential. account ( ) ) ?;
96- clear_account ( LEGACY_TTS_KEYCHAIN_ACCOUNT )
111+ canonical_mutation_with_legacy_cleanup (
112+ || clear_account ( credential. account ( ) ) ,
113+ || clear_account ( LEGACY_TTS_KEYCHAIN_ACCOUNT ) ,
114+ )
97115}
98116
99117#[ cfg( not( target_os = "macos" ) ) ]
@@ -109,9 +127,34 @@ pub(crate) fn require(credential: OpenAiVoiceCredential) -> Result<String, Strin
109127#[ cfg( test) ]
110128mod tests {
111129 use super :: * ;
130+ use std:: cell:: RefCell ;
112131
113132 #[ test]
114133 fn text_to_speech_uses_the_shared_voice_keychain_account ( ) {
115134 assert_eq ! ( OpenAiVoiceCredential :: TextToSpeech . account( ) , "api-key" ) ;
116135 }
136+
137+ #[ test]
138+ fn legacy_cleanup_failure_does_not_change_canonical_mutation_result ( ) {
139+ let credential = RefCell :: new ( None ) ;
140+ let save = canonical_mutation_with_legacy_cleanup (
141+ || {
142+ credential. replace ( Some ( "shared-key" ) ) ;
143+ Ok ( ( ) )
144+ } ,
145+ || Err ( "legacy cleanup failed" . to_string ( ) ) ,
146+ ) ;
147+ assert_eq ! ( save, Ok ( ( ) ) ) ;
148+ assert_eq ! ( * credential. borrow( ) , Some ( "shared-key" ) ) ;
149+
150+ let clear = canonical_mutation_with_legacy_cleanup (
151+ || {
152+ credential. replace ( None ) ;
153+ Ok ( ( ) )
154+ } ,
155+ || Err ( "legacy cleanup failed" . to_string ( ) ) ,
156+ ) ;
157+ assert_eq ! ( clear, Ok ( ( ) ) ) ;
158+ assert_eq ! ( * credential. borrow( ) , None ) ;
159+ }
117160}
0 commit comments