Skip to content

Commit 2b496f0

Browse files
authored
Get final state from pollers (Azure#3183)
Resolves Azure#2757
1 parent d023070 commit 2b496f0

File tree

17 files changed

+1464
-272
lines changed

17 files changed

+1464
-272
lines changed

sdk/core/azure_core/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@
44

55
### Features Added
66

7+
- Added `Response::to_raw_response()` function to create a `RawResponse` from cloned data.
78
- Added `UrlExt::append_path()`.
9+
- Implemented `IntoFuture` for a `Poller`. Call `await` on a Poller to get the final model, or `into_stream()` to get a `futures::Stream` to poll the operation manually.
810

911
### Breaking Changes
1012

13+
- Added `F: Format` type parameter to `Poller` and `PollerResult`.
14+
- Added `Format` associated type to `StatusMonitor`.
15+
- Added `Format::deserialize()` function to `Format` trait.
16+
- Added `S` type parameter to `xml::from_xml` congruent with `json::from_json()`.
1117
- Moved deserializers and serializers for optional base64-encoded bytes to `base64::option` module. `base64` module now deserializes or serializes non-optional fields congruent with the `time` module.
1218
- Removed `constants` module.
1319
- Removed `CustomHeaders` policy.
1420
- Removed `ErrorKind::MockFramework`.
21+
- Removed `Poller::wait()` function. Call `await` on a `Poller` to wait for it to complete and, upon success, return the final model.
1522
- Removed `xml::read_xml_str()`.
1623
- Renamed `xml::read_xml()` to `xml::from_xml()` congruent with `json::from_json()`.
1724

sdk/core/azure_core/README.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -379,19 +379,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
379379
..Default::default()
380380
};
381381

382-
// Wait for the certificate operation to complete and get the last status monitor.
383-
let operation = client
382+
// Wait for the certificate operation to complete and get the certificate.
383+
let certificate = client
384384
.begin_create_certificate("certificate-name", body.try_into()?, None)?
385-
.wait()
386385
.await?
387-
// Deserialize the CertificateOperation:
388386
.into_body()?;
389387

390-
if matches!(operation.status, Some(status) if status == "completed") {
391-
let target = operation.target.ok_or("expected target")?;
392-
println!("Created certificate {}", target);
393-
}
394-
395388
Ok(())
396389
}
397390
```

sdk/core/azure_core/examples/core_pager.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::sync::{
1313
Arc,
1414
};
1515

16-
// This example demonstrates using a Pager to list secret properties from Key Vault.
16+
/// This example demonstrates using a [`Pager`] to list secret properties from Key Vault.
1717
async fn test_pager() -> Result<(), Box<dyn std::error::Error>> {
1818
let mut options = SecretClientOptions::default();
1919

@@ -30,7 +30,7 @@ async fn test_pager() -> Result<(), Box<dyn std::error::Error>> {
3030
)?;
3131

3232
// List secret properties using a Pager.
33-
let mut pager = client.list_secret_properties(None)?.into_stream();
33+
let mut pager = client.list_secret_properties(None)?;
3434
let mut names = Vec::new();
3535
while let Some(secret) = pager.try_next().await? {
3636
names.push(secret.resource_id()?.name);
@@ -40,15 +40,57 @@ async fn test_pager() -> Result<(), Box<dyn std::error::Error>> {
4040
Ok(())
4141
}
4242

43+
/// This example demonstrates using a [`PageIterator`] to list pages of secret properties from Key Vault.
44+
///
45+
/// Some clients may return a `PageIterator` if there are no items to iterate or multiple items to iterate.
46+
/// The following example shows how you can also get a `PageIterator` from a [`Pager`] to iterate over pages instead of items.
47+
/// The pattern for iterating pages is otherwise the same:
48+
async fn test_page_iterator() -> Result<(), Box<dyn std::error::Error>> {
49+
let mut options = SecretClientOptions::default();
50+
51+
// Ignore: this is only set up for testing.
52+
// You normally would create credentials from `azure_identity` and
53+
// use the default transport in production.
54+
let (credential, transport) = setup()?;
55+
options.client_options.transport = Some(Transport::new(transport));
56+
57+
let client = SecretClient::new(
58+
"https://my-vault.vault.azure.net",
59+
credential,
60+
Some(options),
61+
)?;
62+
63+
// List secret properties using a Pager.
64+
let mut pager = client.list_secret_properties(None)?.into_pages();
65+
let mut names = Vec::new();
66+
while let Some(page) = pager.try_next().await? {
67+
let page = page.into_body()?;
68+
for secret in page.value {
69+
names.push(secret.resource_id()?.name);
70+
}
71+
}
72+
assert_eq!(names, vec!["secret-a", "secret-b", "secret-c"]);
73+
74+
Ok(())
75+
}
76+
4377
// ----- BEGIN TEST SETUP -----
4478
#[tokio::test]
4579
async fn test_core_pager() -> Result<(), Box<dyn std::error::Error>> {
4680
test_pager().await
4781
}
4882

83+
#[tokio::test]
84+
async fn test_core_page_iterator() -> Result<(), Box<dyn std::error::Error>> {
85+
test_page_iterator().await
86+
}
87+
4988
#[tokio::main]
5089
async fn main() -> Result<(), Box<dyn std::error::Error>> {
51-
test_pager().await
90+
test_pager().await?;
91+
test_page_iterator().await?;
92+
93+
Ok(())
5294
}
5395

5496
#[allow(clippy::type_complexity)]

sdk/core/azure_core/examples/core_poller.rs

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ use azure_core_test::{credentials::MockCredential, http::MockHttpClient};
1212
use azure_security_keyvault_certificates::{
1313
models::CreateCertificateParameters, CertificateClient, CertificateClientOptions,
1414
};
15-
use futures::FutureExt;
15+
use futures::{FutureExt as _, TryStreamExt as _};
1616
use std::sync::{
1717
atomic::{AtomicUsize, Ordering},
1818
Arc,
1919
};
2020

21-
// This example demonstrates using a Poller to create a certificate with the CertificateClient.
21+
/// This example demonstrates using a [`Poller`] to await a long-running operation (LRO) to create a certificate with the CertificateClient.
2222
async fn test_poller() -> Result<(), Box<dyn std::error::Error>> {
2323
let mut options = CertificateClientOptions::default();
2424

@@ -38,13 +38,59 @@ async fn test_poller() -> Result<(), Box<dyn std::error::Error>> {
3838
let params = CreateCertificateParameters::default();
3939

4040
// Start a create_certificate long-running operation.
41-
let operation = client
41+
let certificate = client
4242
.begin_create_certificate("my-cert", params.try_into()?, None)?
43-
.wait()
4443
.await?
4544
.into_body()?;
46-
assert_eq!(operation.status.as_deref(), Some("completed"));
47-
assert!(operation.target.is_some());
45+
assert_eq!(
46+
certificate.id,
47+
Some("https://my-vault.vault.azure.net/certificates/my-cert/version".into())
48+
);
49+
assert_eq!(certificate.cer, Some(b"test".to_vec()));
50+
51+
Ok(())
52+
}
53+
54+
/// This example demonstrates using a [`Poller`] to manually poll status for a long-running operation (LRO) to create a certificate with the CertificateClient.
55+
///
56+
/// If you want to manually poll status updates, you can use the `Poller` as a stream by calling [`try_next`](futures::TryStreamExt::try_next) on a mutable reference.
57+
/// The stream will end when the operation completes, and the final status contains information about the completed operation.
58+
async fn test_poller_stream() -> Result<(), Box<dyn std::error::Error>> {
59+
let mut options = CertificateClientOptions::default();
60+
61+
// Ignore: this is only set up for testing.
62+
// You normally would create credentials from `azure_identity` and
63+
// use the default transport in production.
64+
let (credential, transport) = setup()?;
65+
options.client_options.transport = Some(Transport::new(transport));
66+
67+
let client = CertificateClient::new(
68+
"https://my-vault.vault.azure.net",
69+
credential,
70+
Some(options),
71+
)?;
72+
73+
// Minimal create parameters (empty policy for mock)
74+
let params = CreateCertificateParameters::default();
75+
76+
// Start a create_certificate long-running operation and manually poll status.
77+
let mut poller = client.begin_create_certificate("my-cert", params.try_into()?, None)?;
78+
79+
// Manually poll status updates until completion
80+
let mut final_status = None;
81+
while let Some(status) = poller.try_next().await? {
82+
let status = status.into_body()?;
83+
assert!(status.error.is_none());
84+
final_status = Some(status);
85+
}
86+
87+
// The last status should indicate completion
88+
let status = final_status.expect("expected at least one status update");
89+
assert_eq!(status.status.as_deref(), Some("completed"));
90+
assert_eq!(
91+
status.target.as_deref(),
92+
Some("https://my-vault.vault.azure.net/certificates/my-cert")
93+
);
4894

4995
Ok(())
5096
}
@@ -55,11 +101,20 @@ async fn test_core_poller() -> Result<(), Box<dyn std::error::Error>> {
55101
test_poller().await
56102
}
57103

104+
#[tokio::test]
105+
async fn test_core_poller_stream() -> Result<(), Box<dyn std::error::Error>> {
106+
test_poller_stream().await
107+
}
108+
58109
#[tokio::main]
59110
async fn main() -> Result<(), Box<dyn std::error::Error>> {
60-
test_poller().await
111+
test_poller().await?;
112+
test_poller_stream().await?;
113+
114+
Ok(())
61115
}
62116

117+
/// Setup for the await example - returns all 3 responses including the final target
63118
#[allow(clippy::type_complexity)]
64119
fn setup() -> Result<(Arc<dyn TokenCredential>, Arc<dyn HttpClient>), Box<dyn std::error::Error>> {
65120
let credential: Arc<dyn TokenCredential> = MockCredential::new()?;
@@ -74,7 +129,7 @@ fn setup() -> Result<(Arc<dyn TokenCredential>, Arc<dyn HttpClient>), Box<dyn st
74129
0 => {
75130
// Initial POST to start operation
76131
assert_eq!(request.method(), Method::Post);
77-
assert!(request.url().path().starts_with("/certificates/my-cert/create"));
132+
assert_eq!(request.url().path(), "/certificates/my-cert/create");
78133
let mut headers = Headers::new();
79134
headers.insert(RETRY_AFTER, "0");
80135
Ok(BufResponse::from_bytes(
@@ -86,13 +141,23 @@ fn setup() -> Result<(Arc<dyn TokenCredential>, Arc<dyn HttpClient>), Box<dyn st
86141
1 => {
87142
// Polling GET for status
88143
assert_eq!(request.method(), Method::Get);
89-
assert!(request.url().path().starts_with("/certificates/my-cert/pending"));
144+
assert_eq!(request.url().path(), "/certificates/my-cert/pending");
90145
Ok(BufResponse::from_bytes(
91146
StatusCode::Ok,
92147
Headers::new(),
93148
r#"{"id":"https://my-vault.vault.azure.net/certificates/my-cert/pending","status":"completed","target":"https://my-vault.vault.azure.net/certificates/my-cert"}"#,
94149
))
95150
}
151+
2 => {
152+
// Final GET for the target
153+
assert_eq!(request.method(), Method::Get);
154+
assert_eq!(request.url().path(), "/certificates/my-cert");
155+
Ok(BufResponse::from_bytes(
156+
StatusCode::Ok,
157+
Headers::new(),
158+
r#"{"id":"https://my-vault.vault.azure.net/certificates/my-cert/version","cer":"dGVzdA=="}"#,
159+
))
160+
}
96161
_ => panic!("unexpected request count {idx}"),
97162
}
98163
}

0 commit comments

Comments
 (0)