Skip to content

Commit cabf455

Browse files
committed
Add example of deserialize ErrorResponse
Resolves #3271
1 parent e37a714 commit cabf455

File tree

2 files changed

+119
-0
lines changed

2 files changed

+119
-0
lines changed

sdk/core/azure_core/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
217217
}
218218
```
219219

220+
Most Azure services return a standard error response model, which will populate the `status` and `error_code` with more specific information.
221+
The `raw_response` field (elided above) will always contain the `RawResponse` including the error response body in its entirety,
222+
and you can [deserialize](https://github.com/Azure/azure-sdk-for-rust/blob/main/sdk/core/azure_core/examples/core_error_response.rs) the standard error response model to get more information.
223+
220224
### Consuming service methods returning `Pager<T>`
221225

222226
If a service call returns multiple values in pages, it should return `Result<Pager<T>>` as a result. You can iterate all items from all pages.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
use azure_core::{
5+
credentials::TokenCredential,
6+
error::ErrorResponse,
7+
http::{headers::Headers, BufResponse, HttpClient, StatusCode, Transport},
8+
json,
9+
};
10+
use azure_core_test::{credentials::MockCredential, http::MockHttpClient, ErrorKind};
11+
use azure_security_keyvault_secrets::{
12+
models::SetSecretParameters, SecretClient, SecretClientOptions,
13+
};
14+
use futures::FutureExt;
15+
use std::sync::Arc;
16+
17+
/// This example demonstrates deserializing a standard Azure error response to get more details.
18+
async fn test_error_response() -> Result<(), Box<dyn std::error::Error>> {
19+
let mut options = SecretClientOptions::default();
20+
21+
// Ignore: this is only set up for testing.
22+
// You normally would create credentials from `azure_identity` and
23+
// use the default transport in production.
24+
let (credential, transport) = setup()?;
25+
options.client_options.transport = Some(Transport::new(transport));
26+
27+
let client = SecretClient::new(
28+
"https://my-vault.vault.azure.net",
29+
credential,
30+
Some(options),
31+
)?;
32+
33+
let secret = SetSecretParameters {
34+
value: Some("secret_value".into()),
35+
content_type: Some("text/plain".into()),
36+
..Default::default()
37+
};
38+
39+
// This would fail with a name like "secret_name".
40+
let error = client
41+
.set_secret("secret_name", secret.try_into()?, None)
42+
.await
43+
.unwrap_err();
44+
let ErrorKind::HttpResponse {
45+
status,
46+
error_code,
47+
raw_response: Some(raw_response),
48+
} = error.kind()
49+
else {
50+
panic!("expected HTTP error response");
51+
};
52+
53+
assert_eq!(*status, StatusCode::BadRequest);
54+
assert_eq!(error_code.as_deref(), Some("BadParameter"));
55+
assert!(error
56+
.to_string()
57+
.contains("The request URI contains an invalid name: secret_name"));
58+
59+
// Now deserialize the `raw_response` to get additional details.
60+
let error_response: ErrorResponse = json::from_json(raw_response.body())?;
61+
let details = error_response
62+
.error
63+
.expect("expected HTTP error response details");
64+
65+
assert_eq!(details.code.as_deref(), Some("BadParameter"));
66+
assert_eq!(
67+
details.message.as_deref(),
68+
Some("The request URI contains an invalid name: secret_name")
69+
);
70+
assert_eq!(details.target.as_deref(), Some("secret-name"));
71+
assert_eq!(details.details.len(), 1);
72+
assert_eq!(
73+
details.details[0].message.as_deref(),
74+
Some("secret-name can contain only letters, numbers, and dashes")
75+
);
76+
77+
Ok(())
78+
}
79+
80+
// ----- BEGIN TEST SETUP -----
81+
#[tokio::test]
82+
async fn test_core_error_response() -> Result<(), Box<dyn std::error::Error>> {
83+
test_error_response().await
84+
}
85+
86+
#[tokio::main]
87+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
88+
test_error_response().await
89+
}
90+
91+
#[allow(clippy::type_complexity)]
92+
fn setup() -> Result<(Arc<dyn TokenCredential>, Arc<dyn HttpClient>), Box<dyn std::error::Error>> {
93+
let client = MockHttpClient::new(|_| {
94+
async move {
95+
Ok(BufResponse::from_bytes(
96+
StatusCode::BadRequest,
97+
Headers::new(),
98+
r#"{
99+
"error": {
100+
"code": "BadParameter",
101+
"message": "The request URI contains an invalid name: secret_name",
102+
"target": "secret-name",
103+
"details": [
104+
{"message": "secret-name can contain only letters, numbers, and dashes"}
105+
]
106+
}
107+
}"#,
108+
))
109+
}
110+
.boxed()
111+
});
112+
113+
Ok((MockCredential::new()?, Arc::new(client)))
114+
}
115+
// ----- END TEST SETUP -----

0 commit comments

Comments
 (0)