@@ -172,16 +172,17 @@ pub struct ProviderAlias {
172172 /// The provider URI (e.g. `keyring://`, `bws://project-uuid`).
173173 pub uri : String ,
174174 /// Bootstrap credentials: environment variable name to the [`BootstrapSource`]
175- /// that supplies it. `None` for a bare string alias.
176- pub env : Option < HashMap < String , BootstrapSource > > ,
175+ /// that supplies it. Empty for a bare string alias, so "declares no
176+ /// bootstrap credentials" has exactly one representation.
177+ pub env : HashMap < String , BootstrapSource > ,
177178}
178179
179180impl ProviderAlias {
180181 /// A bare alias carrying only a URI and no bootstrap credentials.
181182 pub fn from_uri ( uri : impl Into < String > ) -> Self {
182183 Self {
183184 uri : uri. into ( ) ,
184- env : None ,
185+ env : HashMap :: new ( ) ,
185186 }
186187 }
187188}
@@ -201,10 +202,8 @@ impl From<&str> for ProviderAlias {
201202impl std:: fmt:: Display for ProviderAlias {
202203 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
203204 write ! ( f, "{}" , self . uri) ?;
204- if let Some ( env) = & self . env
205- && !env. is_empty ( )
206- {
207- let mut vars: Vec < & str > = env. keys ( ) . map ( String :: as_str) . collect ( ) ;
205+ if !self . env . is_empty ( ) {
206+ let mut vars: Vec < & str > = self . env . keys ( ) . map ( String :: as_str) . collect ( ) ;
208207 vars. sort ( ) ;
209208 write ! ( f, " (bootstrap: {})" , vars. join( ", " ) ) ?;
210209 }
@@ -214,17 +213,16 @@ impl std::fmt::Display for ProviderAlias {
214213
215214impl Serialize for ProviderAlias {
216215 fn serialize < S : serde:: Serializer > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error > {
217- match & self . env {
216+ if self . env . is_empty ( ) {
218217 // A bare alias serializes back to the plain-string form, so an alias
219218 // that was written as a string round-trips unchanged.
220- None => serializer. serialize_str ( & self . uri ) ,
221- Some ( env) => {
222- use serde:: ser:: SerializeStruct ;
223- let mut table = serializer. serialize_struct ( "ProviderAlias" , 2 ) ?;
224- table. serialize_field ( "uri" , & self . uri ) ?;
225- table. serialize_field ( "env" , env) ?;
226- table. end ( )
227- }
219+ serializer. serialize_str ( & self . uri )
220+ } else {
221+ use serde:: ser:: SerializeStruct ;
222+ let mut table = serializer. serialize_struct ( "ProviderAlias" , 2 ) ?;
223+ table. serialize_field ( "uri" , & self . uri ) ?;
224+ table. serialize_field ( "env" , & self . env ) ?;
225+ table. end ( )
228226 }
229227 }
230228}
@@ -261,12 +259,7 @@ impl<'de> Deserialize<'de> for ProviderAlias {
261259 let table = Table :: deserialize ( serde:: de:: value:: MapAccessDeserializer :: new ( map) ) ?;
262260 Ok ( ProviderAlias {
263261 uri : table. uri ,
264- // Normalize an explicit empty table (`env = {}`) to `None`:
265- // "declares no bootstrap credentials" then has a single
266- // representation, so consumers checking `env.is_some()`
267- // (e.g. the one-hop rule) cannot disagree with consumers
268- // iterating the entries.
269- env : table. env . filter ( |env| !env. is_empty ( ) ) ,
262+ env : table. env . unwrap_or_default ( ) ,
270263 } )
271264 }
272265 }
@@ -2092,7 +2085,7 @@ mod provider_alias_tests {
20922085 fn bare_string_parses_as_uri_without_env ( ) {
20932086 let map = parse ( r#"keyring = "keyring://""# ) ;
20942087 assert_eq ! ( map[ "keyring" ] , ProviderAlias :: from( "keyring://" ) ) ;
2095- assert_eq ! ( map[ "keyring" ] . env, None ) ;
2088+ assert ! ( map[ "keyring" ] . env. is_empty ( ) ) ;
20962089 }
20972090
20982091 #[ test]
@@ -2102,8 +2095,7 @@ mod provider_alias_tests {
21022095 assert_eq ! ( alias. uri, "bws://proj" ) ;
21032096 let source = alias
21042097 . env
2105- . as_ref ( )
2106- . and_then ( |env| env. get ( "BWS_ACCESS_TOKEN" ) )
2098+ . get ( "BWS_ACCESS_TOKEN" )
21072099 . expect ( "env carries the variable" ) ;
21082100 assert_eq ! ( source, & BootstrapSource :: from( "keyring" ) ) ;
21092101 }
@@ -2113,7 +2105,7 @@ mod provider_alias_tests {
21132105 let map = parse (
21142106 r#"vault = { uri = "vault://kv", env = { VAULT_ROLE_ID = { provider = "onepassword", ref = { vault = "Infra", item = "approle", field = "role_id" } } } }"# ,
21152107 ) ;
2116- let source = map[ "vault" ] . env . as_ref ( ) . unwrap ( ) [ "VAULT_ROLE_ID" ] . clone ( ) ;
2108+ let source = map[ "vault" ] . env [ "VAULT_ROLE_ID" ] . clone ( ) ;
21172109 assert_eq ! ( source. provider, "onepassword" ) ;
21182110 let reference = source. reference . expect ( "ref present" ) ;
21192111 assert_eq ! ( reference. vault. as_deref( ) , Some ( "Infra" ) ) ;
@@ -2135,10 +2127,7 @@ mod provider_alias_tests {
21352127 for source in [ bare, with_ref] {
21362128 let alias = ProviderAlias {
21372129 uri : "vault://kv" . to_string ( ) ,
2138- env : Some ( HashMap :: from ( [ (
2139- "VAULT_ROLE_ID" . to_string ( ) ,
2140- source. clone ( ) ,
2141- ) ] ) ) ,
2130+ env : HashMap :: from ( [ ( "VAULT_ROLE_ID" . to_string ( ) , source. clone ( ) ) ] ) ,
21422131 } ;
21432132 let map = HashMap :: from ( [ ( "vault" . to_string ( ) , alias. clone ( ) ) ] ) ;
21442133 let serialized = toml:: to_string ( & map) . unwrap ( ) ;
@@ -2154,12 +2143,10 @@ mod provider_alias_tests {
21542143
21552144 #[ test]
21562145 fn empty_env_table_is_equivalent_to_no_env ( ) {
2157- // `env = {}` declares nothing, so it must normalize to `None`: the
2158- // one-hop rule checks `env.is_some()` and would otherwise reject the
2159- // alias as a bootstrap source while login reports nothing to store.
2146+ // `env = {}` declares nothing: the alias equals its bare-string form
2147+ // and serializes back to it.
21602148 let map = parse ( r#"keyring = { uri = "keyring://", env = {} }"# ) ;
21612149 assert_eq ! ( map[ "keyring" ] , ProviderAlias :: from( "keyring://" ) ) ;
2162- assert_eq ! ( map[ "keyring" ] . env, None ) ;
21632150 }
21642151
21652152 #[ test]
@@ -2188,10 +2175,10 @@ mod provider_alias_tests {
21882175 fn alias_with_env_round_trips_through_toml ( ) {
21892176 let alias = ProviderAlias {
21902177 uri : "bws://proj" . to_string ( ) ,
2191- env : Some ( HashMap :: from ( [ (
2178+ env : HashMap :: from ( [ (
21922179 "BWS_ACCESS_TOKEN" . to_string ( ) ,
21932180 BootstrapSource :: from ( "keyring" ) ,
2194- ) ] ) ) ,
2181+ ) ] ) ,
21952182 } ;
21962183 let map = HashMap :: from ( [ ( "bws" . to_string ( ) , alias. clone ( ) ) ] ) ;
21972184 let serialized = toml:: to_string ( & map) . unwrap ( ) ;
@@ -2218,12 +2205,6 @@ API_KEY = { description = "key", required = true }
22182205 let providers = config. providers . expect ( "[providers] present" ) ;
22192206 assert_eq ! ( providers[ "keyring" ] , ProviderAlias :: from( "keyring://" ) ) ;
22202207 assert_eq ! ( providers[ "bws" ] . uri, "bws://proj" ) ;
2221- assert ! (
2222- providers[ "bws" ]
2223- . env
2224- . as_ref( )
2225- . unwrap( )
2226- . contains_key( "BWS_ACCESS_TOKEN" )
2227- ) ;
2208+ assert ! ( providers[ "bws" ] . env. contains_key( "BWS_ACCESS_TOKEN" ) ) ;
22282209 }
22292210}
0 commit comments