@@ -148,13 +148,45 @@ impl TokenCredential for ImdsManagedIdentityCredential {
148
148
}
149
149
}
150
150
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
151
153
fn expires_on_string < ' de , D > ( deserializer : D ) -> std:: result:: Result < OffsetDateTime , D :: Error >
152
154
where
153
155
D : Deserializer < ' de > ,
154
156
{
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 )
158
190
}
159
191
160
192
/// 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>
179
211
Ok ( scope. strip_suffix ( "/.default" ) . unwrap_or ( * scope) )
180
212
}
181
213
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 .
183
215
// https://learn.microsoft.com/azure/app-service/overview-managed-identity?tabs=dotnet#rest-protocol-examples
184
216
#[ derive( Debug , Clone , Deserialize ) ]
185
217
#[ allow( unused) ]
@@ -210,4 +242,20 @@ mod tests {
210
242
assert_eq ! ( expected, parsed. date) ;
211
243
Ok ( ( ) )
212
244
}
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
+ }
213
261
}
0 commit comments