@@ -148,13 +148,45 @@ impl TokenCredential for ImdsManagedIdentityCredential {
148148 }
149149}
150150
151+ // `expires_on` varies between a number and a date string depending on token server implementation
152+ // https://github.com/Azure/azure-sdk-for-go/blob/66eca06a3fb1a931ddd3c7e61462967f6e5b9c2e/sdk/azidentity/managed_identity_client.go#L310
151153fn expires_on_string < ' de , D > ( deserializer : D ) -> std:: result:: Result < OffsetDateTime , D :: Error >
152154where
153155 D : Deserializer < ' de > ,
154156{
155- let v = String :: deserialize ( deserializer) ?;
156- let as_i64 = v. parse :: < i64 > ( ) . map_err ( de:: Error :: custom) ?;
157- OffsetDateTime :: from_unix_timestamp ( as_i64) . map_err ( de:: Error :: custom)
157+ struct ExpiresOnVisitor ;
158+
159+ impl de:: Visitor < ' _ > for ExpiresOnVisitor {
160+ type Value = OffsetDateTime ;
161+
162+ fn expecting ( & self , formatter : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
163+ formatter. write_str ( "a string or integer representing a Unix timestamp" )
164+ }
165+
166+ fn visit_str < E > ( self , value : & str ) -> std:: result:: Result < Self :: Value , E >
167+ where
168+ E : de:: Error ,
169+ {
170+ let as_i64 = value. parse :: < i64 > ( ) . map_err ( de:: Error :: custom) ?;
171+ OffsetDateTime :: from_unix_timestamp ( as_i64) . map_err ( de:: Error :: custom)
172+ }
173+
174+ fn visit_i64 < E > ( self , value : i64 ) -> std:: result:: Result < Self :: Value , E >
175+ where
176+ E : de:: Error ,
177+ {
178+ OffsetDateTime :: from_unix_timestamp ( value) . map_err ( de:: Error :: custom)
179+ }
180+
181+ fn visit_u64 < E > ( self , value : u64 ) -> std:: result:: Result < Self :: Value , E >
182+ where
183+ E : de:: Error ,
184+ {
185+ OffsetDateTime :: from_unix_timestamp ( value as i64 ) . map_err ( de:: Error :: custom)
186+ }
187+ }
188+
189+ deserializer. deserialize_any ( ExpiresOnVisitor )
158190}
159191
160192/// Convert a `AADv2` scope to an `AADv1` resource
@@ -179,7 +211,7 @@ fn scopes_to_resource<'a>(scopes: &'a [&'a str]) -> azure_core::Result<&'a str>
179211 Ok ( scope. strip_suffix ( "/.default" ) . unwrap_or ( * scope) )
180212}
181213
182- // NOTE: expires_on is a String version of unix epoch time, not an integer.
214+ // NOTE: expires_on is _meant_ to be a String version of unix epoch time, not an integer, though it varies between implementations .
183215// https://learn.microsoft.com/azure/app-service/overview-managed-identity?tabs=dotnet#rest-protocol-examples
184216#[ derive( Debug , Clone , Deserialize ) ]
185217#[ allow( unused) ]
@@ -210,4 +242,20 @@ mod tests {
210242 assert_eq ! ( expected, parsed. date) ;
211243 Ok ( ( ) )
212244 }
245+
246+ #[ test]
247+ fn check_expires_on_int ( ) -> azure_core:: Result < ( ) > {
248+ let as_string = r#"{"date": 1586984735}"# ;
249+ let expected = datetime ! ( 2020 -4 -15 21 : 5 : 35 UTC ) ;
250+ let parsed: TestExpires = from_json ( as_string) ?;
251+ assert_eq ! ( expected, parsed. date) ;
252+ Ok ( ( ) )
253+ }
254+
255+ #[ test]
256+ fn check_expires_on_invalid ( ) {
257+ let as_string = r#"{"date": "invalid"}"# ;
258+ let parsed: Result < TestExpires , Error > = from_json ( as_string) ;
259+ assert ! ( parsed. is_err( ) ) ;
260+ }
213261}
0 commit comments