Skip to main content

azure_core/http/
poller.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4//! Types and methods for long-running operations (LROs).
5
6use crate::{
7    error::{ErrorKind, ErrorResponse},
8    http::{
9        headers::{HeaderName, Headers},
10        policies::create_public_api_span,
11        Context, Format, JsonFormat, Response, StatusCode, Url,
12    },
13    sleep,
14    time::{Duration, OffsetDateTime},
15    tracing::{Span, SpanStatus},
16};
17use futures::{channel::oneshot, stream::unfold, Stream, StreamExt};
18use serde::Deserialize;
19use std::{
20    convert::Infallible,
21    fmt,
22    future::{Future, IntoFuture},
23    pin::Pin,
24    str::FromStr,
25    sync::Arc,
26    task::{Context as TaskContext, Poll},
27};
28
29/// Default retry time for long-running operations if no retry-after header is present
30///
31/// This value is the same as the default used in other Azure SDKs e.g.,
32/// <https://github.com/Azure/azure-sdk-for-python/blob/azure-core_1.35.0/sdk/core/azure-core/azure/core/polling/base_polling.py#L586>
33const DEFAULT_RETRY_TIME: Duration = Duration::seconds(30);
34
35/// Minimum retry time for long-running operations
36const MIN_RETRY_TIME: Duration = Duration::seconds(1);
37
38/// Represents the state of a [`Poller`].
39#[derive(Debug, Clone, Default, PartialEq, Eq)]
40pub enum PollerState {
41    /// The poller should fetch the initial status.
42    #[default]
43    Initial,
44    /// The poller should fetch subsequent status.
45    More(PollerContinuation),
46}
47
48/// Long-running operation (LRO) status.
49#[derive(Debug, Default, Clone, PartialEq, Eq)]
50pub enum PollerStatus {
51    /// The LRO is still in progress.
52    #[default]
53    InProgress,
54
55    /// The LRO completed successfully.
56    Succeeded,
57
58    /// The LRO failed.
59    Failed,
60
61    /// The LRO was canceled.
62    Canceled,
63
64    /// Another status not otherwise defined.
65    UnknownValue(String),
66}
67
68impl From<&str> for PollerStatus {
69    fn from(value: &str) -> Self {
70        // LRO status should be compared case-insensitively:
71        // https://github.com/Azure/azure-sdk-for-rust/issues/2482
72
73        // cspell:words inprogress
74        if "inprogress".eq_ignore_ascii_case(value) {
75            return PollerStatus::InProgress;
76        }
77
78        if "succeeded".eq_ignore_ascii_case(value) {
79            return PollerStatus::Succeeded;
80        }
81
82        if "failed".eq_ignore_ascii_case(value) {
83            return PollerStatus::Failed;
84        }
85
86        // While the specification recommends "Canceled", in practice
87        // numerous services use "Cancelled".
88        if "canceled".eq_ignore_ascii_case(value) || "cancelled".eq_ignore_ascii_case(value) {
89            return PollerStatus::Canceled;
90        }
91
92        PollerStatus::UnknownValue(value.to_owned())
93    }
94}
95
96impl FromStr for PollerStatus {
97    type Err = Infallible;
98    fn from_str(value: &str) -> Result<Self, Self::Err> {
99        Ok(value.into())
100    }
101}
102
103impl<'de> Deserialize<'de> for PollerStatus {
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106        D: serde::Deserializer<'de>,
107    {
108        struct PollerStatusVisitor;
109        impl serde::de::Visitor<'_> for PollerStatusVisitor {
110            type Value = PollerStatus;
111
112            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
113                formatter.write_str("a string representing a PollerStatus")
114            }
115
116            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
117            where
118                E: serde::de::Error,
119            {
120                FromStr::from_str(s).map_err(serde::de::Error::custom)
121            }
122        }
123
124        deserializer.deserialize_str(PollerStatusVisitor)
125    }
126}
127
128/// Options to create the [`Poller`].
129#[derive(Debug, Clone)]
130pub struct PollerOptions<'a> {
131    /// Allows customization of the method call.
132    pub context: Context<'a>,
133    /// The time to wait between polling intervals in absence of a `retry-after` header.
134    ///
135    /// The default is 30 seconds. The minimum time enforced by [`Poller::new`] is 1 second.
136    pub frequency: Duration,
137}
138
139impl Default for PollerOptions<'_> {
140    fn default() -> Self {
141        Self {
142            frequency: DEFAULT_RETRY_TIME,
143            context: Context::new(),
144        }
145    }
146}
147
148impl<'a> PollerOptions<'a> {
149    /// Converts these poller options into an owned form so they can outlive the current scope.
150    #[must_use]
151    pub fn into_owned(self) -> PollerOptions<'static> {
152        PollerOptions {
153            context: self.context.into_owned(),
154            frequency: self.frequency,
155        }
156    }
157}
158
159/// The result of fetching the status monitor from a [`Poller`], whether the long-running operation (LRO) is in progress or done.
160pub enum PollerResult<M, F = JsonFormat>
161where
162    M: StatusMonitor,
163    F: Format,
164{
165    /// The long-running operation (LRO) is in progress and the next status monitor update may be fetched from `continuation`.
166    ///
167    /// # Fields
168    ///
169    /// * `response` contains the HTTP response with the status monitor.
170    /// * `retry_after` is the optional client-specified [`Duration`] to wait. The default is 30 seconds.
171    /// * `continuation` is the next link / continuation token.
172    InProgress {
173        /// The HTTP response with the status monitor.
174        response: Response<M, F>,
175        /// The optional client-specified [`Duration`] to wait before polling again.
176        retry_after: Duration,
177        /// The next link / continuation token.
178        continuation: PollerContinuation,
179    },
180
181    /// The long-running operation (LRO) succeeded and contains the final output.
182    ///
183    /// # Fields
184    ///
185    /// * `response` contains the HTTP response with the status monitor in a terminal state.
186    Done {
187        /// The HTTP response with the status monitor in a terminal state.
188        response: Response<M, F>,
189    },
190
191    /// The long-running operation (LRO) succeeded and contains the final status.
192    ///
193    /// # Fields
194    ///
195    /// * `response` contains the HTTP response with the final status monitor.
196    /// * `get_target` is an async function that fetches the final output.
197    Succeeded {
198        /// The HTTP response with the final status monitor.
199        response: Response<M, F>,
200        /// An async function that fetches the final output.
201        target: BoxedCallback<M>,
202    },
203}
204
205impl<M, F> fmt::Debug for PollerResult<M, F>
206where
207    M: StatusMonitor,
208    F: Format,
209{
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        match self {
212            Self::InProgress {
213                retry_after,
214                continuation,
215                ..
216            } => f
217                .debug_struct("InProgress")
218                .field("retry_after", &retry_after)
219                .field("continuation", &continuation)
220                .finish_non_exhaustive(),
221            Self::Done { .. } => f.debug_struct("Done").finish_non_exhaustive(),
222            Self::Succeeded { .. } => f.debug_struct("Succeeded").finish_non_exhaustive(),
223        }
224    }
225}
226
227/// Information returned by the server to poll subsequent status.
228#[derive(Clone, Debug, PartialEq, Eq)]
229#[non_exhaustive]
230pub enum PollerContinuation {
231    /// Polling status or result is found at the following links.
232    Links {
233        /// A link to poll subsequent status.
234        next_link: Url,
235
236        /// A link to get the final result.
237        ///
238        /// May be `None` if the final result is contained in the status monitor.
239        final_link: Option<Url>,
240    },
241}
242
243impl fmt::Display for PollerContinuation {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        match self {
246            PollerContinuation::Links { next_link, .. } => f.write_str(next_link.as_str()),
247        }
248    }
249}
250
251/// Represents a status monitor for a long-running operation (LRO).
252pub trait StatusMonitor {
253    /// The model type returned after the long-running operation (LRO) has completed successfully.
254    ///
255    /// Set this to the unit type `()` if no final resource is expected.
256    type Output;
257
258    /// The format used to deserialize the `Output`.
259    ///
260    /// Set this to [`NoFormat`](crate::http::NoFormat) if no final resource is expected.
261    type Format: Format + Send;
262
263    /// Gets the [`PollerStatus`] from the status monitor.
264    fn status(&self) -> PollerStatus;
265}
266
267mod types {
268    use super::{PollerResult, Response, StatusMonitor, Stream};
269    use std::{future::Future, pin::Pin};
270
271    pub type BoxedStream<M, F> = Box<dyn Stream<Item = crate::Result<Response<M, F>>> + Send>;
272    pub type BoxedFuture<M> = Box<
273        dyn Future<
274                Output = crate::Result<
275                    Response<<M as StatusMonitor>::Output, <M as StatusMonitor>::Format>,
276                >,
277            > + Send,
278    >;
279    pub type BoxedCallback<M> = Box<dyn FnOnce() -> Pin<BoxedFuture<M>> + Send>;
280
281    /// A pinned boxed [`Future`] that can be stored and called dynamically.
282    pub type PollerResultFuture<M, F> =
283        Pin<Box<dyn Future<Output = crate::Result<PollerResult<M, F>>> + Send + 'static>>;
284}
285
286pub use types::PollerResultFuture;
287use types::{BoxedCallback, BoxedFuture, BoxedStream};
288
289/// Represents a long-running operation (LRO)
290///
291/// A `Poller` implements both [`IntoFuture`] and [`Stream`].
292/// You can `await` a `Poller` to get the final model upon successful completion; or,
293/// you can call [`next`](StreamExt::next) or [`try_next`](futures::stream::TryStreamExt::try_next) on a mutable `Poller` to poll status manually.
294///
295/// # Examples
296///
297/// For clients that return a `Poller`, you can await it to get the final result:
298///
299/// ```no_run
300/// # use azure_core::credentials::TokenCredential;
301/// # use azure_core_examples::certificates::{CertificateClient, models::CreateCertificateParameters};
302/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
303/// # let credential: std::sync::Arc<dyn TokenCredential> = unimplemented!();
304/// let client = CertificateClient::new(
305///     "https://my-vault.vault.azure.net",
306///     credential.clone(),
307///     None,
308/// )?;
309///
310/// let params = CreateCertificateParameters::default();
311///
312/// // Await the poller to get the final certificate.
313/// let certificate = client
314///     .begin_create_certificate("my-cert", params.try_into()?, None)?
315///     .await?
316///     .into_model()?;
317/// # Ok(()) }
318/// ```
319///
320/// If you want to manually poll status updates, you can use the `Poller` as a stream:
321///
322/// ```no_run
323/// # use azure_core::credentials::TokenCredential;
324/// # use azure_core_examples::certificates::{CertificateClient, models::CreateCertificateParameters};
325/// # use futures::TryStreamExt;
326/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
327/// # let credential: std::sync::Arc<dyn TokenCredential> = unimplemented!();
328/// let client = CertificateClient::new(
329///     "https://my-vault.vault.azure.net",
330///     credential.clone(),
331///     None,
332/// )?;
333///
334/// let params = CreateCertificateParameters::default();
335///
336/// // Manually poll status updates.
337/// let mut poller = client
338///     .begin_create_certificate("my-cert", params.try_into()?, None)?;
339///
340/// while let Some(status) = poller.try_next().await? {
341///     let status = status.into_model()?;
342///     println!("Status: {:?}", status.status);
343/// }
344///
345/// // After the stream ends, await to get the final certificate.
346/// let certificate = poller.await?.into_model()?;
347/// # Ok(()) }
348/// ```
349#[must_use = "streams do nothing unless you `.await` or poll them"]
350#[pin_project::pin_project]
351pub struct Poller<M, F = JsonFormat>
352where
353    M: StatusMonitor,
354    F: Format,
355{
356    #[pin]
357    stream: Pin<BoxedStream<M, F>>,
358    target: Option<BoxedFuture<M>>,
359}
360
361impl<M, F> Poller<M, F>
362where
363    M: StatusMonitor,
364    F: Format + Send,
365{
366    /// Creates a [`Poller`] from a callback that will be called repeatedly to monitor a long-running operation (LRO).
367    ///
368    /// This method expects a callback that accepts a single [`PollerState`] parameter, and returns a [`PollerResult`] value asynchronously.
369    /// The `N` type parameter is the type of the next link/continuation token. It may be any [`Send`]able type.
370    /// The `M` type parameter must implement [`StatusMonitor`].
371    ///
372    /// The stream will yield [`Response`] values for each intermediate response while the operation is in progress
373    /// i.e., while `M::status()` returns [`PollerStatus::InProgress`]. The stream ends when the operation completes
374    /// successfully, fails, or is canceled.
375    ///
376    /// # Panics
377    ///
378    /// Panics if [`PollerOptions::frequency`] is less than 1 second.
379    ///
380    /// # Examples
381    ///
382    /// To poll a long-running operation:
383    ///
384    /// ```rust,no_run
385    /// # use azure_core::{Result, json, http::{Context, JsonFormat, Pipeline, RawResponse, Request, Response, Method, Url, poller::{Poller, PollerContinuation, PollerResult, PollerState, PollerStatus, StatusMonitor}}};
386    /// # use serde::Deserialize;
387    /// # let api_version = "2025-06-04".to_string();
388    /// # let pipeline: Pipeline = panic!("Not a runnable example");
389    /// #[derive(Deserialize)]
390    /// struct OperationResult {
391    ///     id: String,
392    ///     status: Option<PollerStatus>,
393    ///     result: Option<String>,
394    /// }
395    ///
396    /// impl StatusMonitor for OperationResult {
397    ///     type Output = OperationResult;
398    ///     type Format = JsonFormat;
399    ///
400    ///     fn status(&self) -> PollerStatus {
401    ///         self.status.clone().unwrap_or_default()
402    ///     }
403    /// }
404    ///
405    /// let url = "https://example.com/my_operation".parse().unwrap();
406    /// let mut req = Request::new(url, Method::Post);
407    ///
408    /// let poller = Poller::new(move |operation_state: PollerState, poller_options| {
409    ///     // The callback must be 'static, so you have to clone and move any values you want to use.
410    ///     let pipeline = pipeline.clone();
411    ///     let api_version = api_version.clone();
412    ///     let mut req = req.clone();
413    ///     Box::pin(async move {
414    ///         if let PollerState::More(continuation) = operation_state {
415    ///             // Use the continuation to get the next URL for polling
416    ///             let next_link = match continuation {
417    ///                 PollerContinuation::Links { next_link, .. } => next_link,
418    ///                 _ => unreachable!(),
419    ///             };
420    ///             *req.url_mut() = next_link.clone();
421    ///             req.set_method(Method::Get);
422    ///         }
423    ///
424    ///         req.url_mut()
425    ///             .query_pairs_mut()
426    ///             .append_pair("api-version", &api_version);
427    ///
428    ///         let resp = pipeline
429    ///             .send(&poller_options.context, &mut req, None)
430    ///             .await?;
431    ///         let (status, headers, body) = resp.deconstruct();
432    ///         let result: OperationResult = json::from_json(&body)?;
433    ///         let final_body = body.clone(); // Clone before moving into Response
434    ///         let resp: Response<OperationResult> = RawResponse::from_bytes(status, headers, body).into();
435    ///
436    ///         match result.status() {
437    ///             PollerStatus::InProgress => {
438    ///                 // Continue polling with the operation URL from the response
439    ///                 let next_link = format!("https://example.com/operations/{}", result.id).parse()?;
440    ///                 Ok(PollerResult::InProgress {
441    ///                     response: resp,
442    ///                     retry_after: poller_options.frequency,
443    ///                     continuation: PollerContinuation::Links { next_link, final_link: None },
444    ///                 })
445    ///             }
446    ///             PollerStatus::Succeeded => {
447    ///                 // The result is in the operation response; otherwise, get the target URL
448    ///                 // from the response headers or body and asynchronously fetch the operation target.
449    ///                 Ok(PollerResult::Succeeded {
450    ///                     response: resp,
451    ///                     target: Box::new(move || {
452    ///                         Box::pin(async move {
453    ///                             // In this example, the final result is already in the status response
454    ///                             // In other cases, you might fetch from a target URL
455    ///                             use azure_core::http::headers::Headers;
456    ///                             Ok(RawResponse::from_bytes(status, Headers::new(), final_body).into())
457    ///                         })
458    ///                     }),
459    ///                 })
460    ///             }
461    ///             _ => Ok(PollerResult::Done { response: resp })
462    ///         }
463    ///     })
464    /// }, None);
465    /// ```
466    pub fn new<Fun>(make_request: Fun, options: Option<PollerOptions<'static>>) -> Self
467    where
468        M: Send + 'static,
469        M::Output: Send + 'static,
470        M::Format: Send + 'static,
471        Fun: Fn(PollerState, PollerOptions<'static>) -> PollerResultFuture<M, F> + Send + 'static,
472    {
473        let options = options.unwrap_or_default();
474        let (stream, target) = create_poller_stream(make_request, options);
475        Self {
476            stream: Box::pin(stream),
477            target: Some(target),
478        }
479    }
480}
481
482impl<M, F> Stream for Poller<M, F>
483where
484    M: StatusMonitor,
485    F: Format,
486{
487    type Item = crate::Result<Response<M, F>>;
488
489    fn poll_next(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Option<Self::Item>> {
490        let state = self.project().stream.poll_next(cx);
491        if let Poll::Ready(Some(Ok(ref response))) = state {
492            check_status_code(response)?;
493        }
494
495        state
496    }
497}
498
499impl<M, F> IntoFuture for Poller<M, F>
500where
501    M: StatusMonitor + 'static,
502    M::Output: Send + 'static,
503    M::Format: Send + 'static,
504    F: Format + 'static,
505{
506    type Output = crate::Result<Response<M::Output, M::Format>>;
507
508    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
509
510    fn into_future(mut self) -> Self::IntoFuture {
511        Box::pin(async move {
512            // Poll the stream until completion
513            while let Some(result) = self.stream.next().await {
514                // Check if we got an error from the stream
515                result?;
516            }
517
518            // Extract the target future
519            let target = self.target.ok_or_else(|| {
520                crate::Error::new(
521                    ErrorKind::Other,
522                    "poller completed without a target response",
523                )
524            })?;
525
526            // Pin and await the target future to get the final response
527            Box::into_pin(target).await
528        })
529    }
530}
531
532impl<M, F> fmt::Debug for Poller<M, F>
533where
534    M: StatusMonitor,
535    F: Format,
536{
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        f.write_str("Poller")
539    }
540}
541#[derive(Debug, Clone, PartialEq, Eq)]
542enum State {
543    Init,
544    InProgress(PollerContinuation),
545    Done,
546}
547
548/// The type of the oneshot channel sender for the target future.
549type TargetTransmitterType<'a, M> = (Pin<BoxedFuture<M>>, Option<Context<'a>>);
550
551/// Represents the state used for each iteration through the poller stream.
552struct StreamState<'a, M, Fun>
553where
554    M: StatusMonitor,
555{
556    /// The current polling state (Init, InProgress, or Done)
557    state: State,
558    /// The callback function to make requests
559    make_request: Fun,
560    /// Optional channel sender for the target future
561    target_tx: Option<oneshot::Sender<TargetTransmitterType<'a, M>>>,
562    /// The poller options
563    options: PollerOptions<'a>,
564    /// Whether a span was added to the context
565    added_span: bool,
566}
567
568fn create_poller_stream<
569    M,
570    F: Format,
571    Fun: Fn(PollerState, PollerOptions<'static>) -> PollerResultFuture<M, F> + Send + 'static,
572>(
573    make_request: Fun,
574    options: PollerOptions<'static>,
575) -> (
576    impl Stream<Item = crate::Result<Response<M, F>>> + 'static,
577    BoxedFuture<M>,
578)
579where
580    M: StatusMonitor + 'static,
581    M::Output: Send + 'static,
582    M::Format: Send + 'static,
583{
584    let (target_tx, target_rx) = oneshot::channel();
585
586    assert!(
587        options.frequency >= MIN_RETRY_TIME,
588        "minimum polling frequency is 1 second"
589    );
590    let stream = unfold(
591        // We flow the `make_request` callback through the state value to avoid cloning.
592        StreamState::<M, Fun> {
593            state: State::Init,
594            make_request,
595            target_tx: Some(target_tx),
596            options,
597            added_span: false,
598        },
599        move |mut poller_stream_state| async move {
600            let result = match poller_stream_state.state {
601                State::Init => {
602                    // At the very start of polling, create a span for the entire request, and attach it to the context
603                    let span =
604                        create_public_api_span(&poller_stream_state.options.context, None, None);
605                    if let Some(ref s) = span {
606                        poller_stream_state.added_span = true;
607                        poller_stream_state.options.context =
608                            poller_stream_state.options.context.with_value(s.clone());
609                    }
610                    (poller_stream_state.make_request)(
611                        PollerState::Initial,
612                        poller_stream_state.options.clone(),
613                    )
614                    .await
615                }
616                State::InProgress(continuation) => {
617                    tracing::debug!(
618                        "subsequent operation request to {:?}",
619                        continuation.to_string()
620                    );
621                    (poller_stream_state.make_request)(
622                        PollerState::More(continuation),
623                        poller_stream_state.options.clone(),
624                    )
625                    .await
626                }
627                State::Done => {
628                    tracing::debug!("done");
629                    return None;
630                }
631            };
632            let (item, next_state) = match result {
633                Err(e) => {
634                    if poller_stream_state.added_span {
635                        if let Some(span) =
636                            poller_stream_state.options.context.value::<Arc<dyn Span>>()
637                        {
638                            // Mark the span as an error with an appropriate description.
639                            span.set_status(SpanStatus::Error {
640                                description: e.to_string(),
641                            });
642                            span.set_attribute("error.type", e.kind().to_string().into());
643                            span.end();
644                        }
645                    }
646
647                    poller_stream_state.state = State::Done;
648                    return Some((Err(e), poller_stream_state));
649                }
650                Ok(PollerResult::InProgress {
651                    response,
652                    retry_after,
653                    continuation: n,
654                }) => {
655                    // Note that test-proxy automatically adds a transform that zeroes an existing `after-retry` header during playback, so don't check at runtime:
656                    // <https://github.com/Azure/azure-sdk-tools/blob/a80b559d7682891f36a491b73f52fcb679d40923/tools/test-proxy/Azure.Sdk.Tools.TestProxy/RecordingHandler.cs#L1175>
657                    tracing::trace!("retry poller in {}s", retry_after.whole_seconds());
658                    sleep(retry_after).await;
659
660                    (Ok(response), State::InProgress(n))
661                }
662                // Note that we will normally never reach this state. The normal progression of the `make_request` callback is to return `Succeeded` with a target future,
663                // and then the stream yields the final response and transitions to `Done` state.
664                // The only time that the `make_request` callback will normally enter the `Done` state directly is if the LRO fails or is canceled.
665                Ok(PollerResult::Done { response }) => (Ok(response), State::Done),
666                Ok(PollerResult::Succeeded {
667                    response,
668                    target: get_target,
669                }) => {
670                    // Send the target callback through the channel
671                    if let Some(tx) = poller_stream_state.target_tx.take() {
672                        let _ = tx.send((
673                            get_target(),
674                            if poller_stream_state.added_span {
675                                Some(poller_stream_state.options.context.clone())
676                            } else {
677                                None
678                            },
679                        ));
680                    }
681                    // Also yield the final status response
682                    poller_stream_state.state = State::Done;
683                    return Some((Ok(response), poller_stream_state));
684                }
685            };
686
687            // Update state and return
688            poller_stream_state.state = next_state;
689            Some((item, poller_stream_state))
690        },
691    );
692
693    let target = Box::new(async move {
694        match target_rx.await {
695            Ok(target_state) => {
696                // Await the target future to get the final response from the poller.
697                let res = target_state.0.await;
698                // If we added a span to the target, take the result of the final target future to finalize the span.
699
700                if let Some(ctx) = target_state.1 {
701                    match &res {
702                        Ok(response) => {
703                            // When the result is done, finalize the span. Note that we only do that if we created the span in the first place,
704                            // otherwise it is the responsibility of the caller to end their span.
705                            if let Some(span) = ctx.value::<Arc<dyn Span>>() {
706                                // 5xx status codes SHOULD set status to Error.
707                                // The description should not be set because it can be inferred from "http.response.status_code".
708                                if response.status().is_server_error() {
709                                    span.set_status(SpanStatus::Error {
710                                        description: "".to_string(),
711                                    });
712                                }
713                                if response.status().is_client_error()
714                                    || response.status().is_server_error()
715                                {
716                                    span.set_attribute(
717                                        "error.type",
718                                        response.status().to_string().into(),
719                                    );
720                                }
721
722                                span.end();
723                            }
724                        }
725                        Err(err) => {
726                            if let Some(span) = ctx.value::<Arc<dyn Span>>() {
727                                span.set_status(SpanStatus::Error {
728                                    description: err.to_string(),
729                                });
730                                span.set_attribute("error.type", err.kind().to_string().into());
731                                span.end();
732                            }
733                        }
734                    }
735                }
736                res
737            }
738            Err(err) => Err(crate::Error::with_error(
739                ErrorKind::Other,
740                err,
741                "poller completed without defining a target",
742            )),
743        }
744    });
745
746    (stream, target)
747}
748
749/// Get the retry duration from the operation response or [`PollerOptions`].
750pub fn get_retry_after(
751    headers: &Headers,
752    retry_headers: &[HeaderName],
753    options: &PollerOptions,
754) -> Duration {
755    #[cfg_attr(feature = "test", allow(unused_mut))]
756    let duration =
757        crate::http::policies::get_retry_after(headers, OffsetDateTime::now_utc, retry_headers)
758            .unwrap_or(options.frequency);
759
760    #[cfg(feature = "test")]
761    {
762        use crate::test::RecordingMode;
763
764        // Even though test-proxy will zero an existing `after-retry` (or similar proprietary) header during playback,
765        // we need to override the frequency for services which do not send back supported headers in their response.
766        if matches!(headers.get_optional::<RecordingMode>(), Ok(Some(mode)) if mode == RecordingMode::Playback)
767        {
768            if duration > Duration::ZERO {
769                tracing::debug!(
770                    "overriding {}s poller retry in playback",
771                    duration.whole_seconds()
772                );
773            }
774
775            return Duration::ZERO;
776        }
777    }
778
779    duration
780}
781
782fn check_status_code<T, F: Format>(response: &Response<T, F>) -> crate::Result<()> {
783    let status = response.status();
784    match status {
785        StatusCode::Ok | StatusCode::Accepted | StatusCode::Created | StatusCode::NoContent => {
786            Ok(())
787        }
788        _ => {
789            // Ideally we could take an owned `Response` and move data to avoid cloning the `RawResponse`.
790            let raw_response = Box::new(response.to_raw_response());
791            let error_code = F::deserialize(raw_response.body())
792                .ok()
793                .and_then(|err: ErrorResponse| err.error)
794                .and_then(|details| details.code);
795            Err(ErrorKind::HttpResponse {
796                status,
797                error_code,
798                raw_response: Some(raw_response),
799            }
800            .into_error())
801        }
802    }
803}
804
805#[cfg(test)]
806mod tests {
807    use super::*;
808    #[cfg(feature = "xml")]
809    use crate::http::XmlFormat;
810    use crate::http::{
811        headers::Headers, AsyncRawResponse, HttpClient, Method, NoFormat, RawResponse, Request,
812    };
813    use azure_core_test::http::MockHttpClient;
814    use futures::{FutureExt as _, TryStreamExt as _};
815    use std::sync::{Arc, Mutex};
816
817    #[derive(Debug, serde::Deserialize)]
818    struct TestStatus {
819        status: String,
820        #[serde(default)]
821        target: Option<String>,
822    }
823
824    #[derive(Debug, serde::Deserialize)]
825    struct TestOutput {
826        #[serde(default)]
827        id: Option<String>,
828        #[serde(default)]
829        name: Option<String>,
830    }
831
832    impl StatusMonitor for TestStatus {
833        type Output = TestOutput;
834        type Format = JsonFormat;
835
836        fn status(&self) -> PollerStatus {
837            self.status.parse().unwrap_or_default()
838        }
839    }
840
841    #[cfg(feature = "xml")]
842    #[derive(Debug, serde::Deserialize)]
843    struct XmlTestStatus {
844        status: String,
845    }
846
847    #[cfg(feature = "xml")]
848    impl StatusMonitor for XmlTestStatus {
849        type Output = TestOutput;
850        type Format = XmlFormat;
851
852        fn status(&self) -> PollerStatus {
853            self.status.parse().unwrap_or_default()
854        }
855    }
856
857    #[tokio::test]
858    async fn poller_succeeded() {
859        let call_count = Arc::new(Mutex::new(0));
860
861        let mock_client = {
862            let call_count = call_count.clone();
863            Arc::new(MockHttpClient::new(move |_| {
864                let call_count = call_count.clone();
865                async move {
866                    let mut count = call_count.lock().unwrap();
867                    *count += 1;
868
869                    if *count == 1 {
870                        // First call returns 201 Created with InProgress status
871                        Ok(AsyncRawResponse::from_bytes(
872                            StatusCode::Created,
873                            Headers::new(),
874                            br#"{"status":"InProgress"}"#.to_vec(),
875                        ))
876                    } else {
877                        // Second call returns 200 OK with Succeeded status
878                        Ok(AsyncRawResponse::from_bytes(
879                            StatusCode::Ok,
880                            Headers::new(),
881                            br#"{"status":"Succeeded"}"#.to_vec(),
882                        ))
883                    }
884                }
885                .boxed()
886            }))
887        };
888
889        let mut poller = Poller::new(
890            move |_, _| {
891                let client = mock_client.clone();
892                Box::pin(async move {
893                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
894                    let raw_response = client.execute_request(&req).await?;
895                    let (status, headers, body) = raw_response.deconstruct();
896                    let bytes = body.collect().await?;
897
898                    let test_status: TestStatus = crate::json::from_json(&bytes)?;
899                    let response: Response<TestStatus> =
900                        RawResponse::from_bytes(status, headers, bytes).into();
901
902                    match test_status.status() {
903                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
904                            response,
905                            retry_after: Duration::ZERO,
906                            continuation: PollerContinuation::Links {
907                                next_link: req.url().clone(),
908                                final_link: None,
909                            },
910                        }),
911                        _ => Ok(PollerResult::Done { response }),
912                    }
913                })
914            },
915            None,
916        );
917
918        // First poll should succeed (201 Created with InProgress)
919        let first_result = poller.next().await;
920        assert!(first_result.is_some());
921        let first_response = first_result.unwrap().unwrap();
922        assert_eq!(first_response.status(), StatusCode::Created);
923        let first_body = first_response.into_model().unwrap();
924        assert_eq!(first_body.status(), PollerStatus::InProgress);
925
926        // Second poll should succeed (200 OK with Succeeded)
927        let second_result = poller.next().await;
928        assert!(second_result.is_some());
929        let second_response = second_result.unwrap().unwrap();
930        assert_eq!(second_response.status(), StatusCode::Ok);
931        let second_body = second_response.into_model().unwrap();
932        assert_eq!(second_body.status(), PollerStatus::Succeeded);
933
934        // Third poll should return None (end of stream)
935        let third_result = poller.next().await;
936        assert!(third_result.is_none());
937
938        // Verify both calls were made
939        assert_eq!(*call_count.lock().unwrap(), 2);
940    }
941
942    #[tokio::test]
943    async fn poller_failed() {
944        let call_count = Arc::new(Mutex::new(0));
945
946        let mock_client = {
947            let call_count = call_count.clone();
948            Arc::new(MockHttpClient::new(move |_| {
949                let call_count = call_count.clone();
950                async move {
951                    let mut count = call_count.lock().unwrap();
952                    *count += 1;
953
954                    if *count == 1 {
955                        // First call returns 201 Created with InProgress status
956                        Ok(AsyncRawResponse::from_bytes(
957                            StatusCode::Created,
958                            Headers::new(),
959                            br#"{"status":"InProgress"}"#.to_vec(),
960                        ))
961                    } else {
962                        // Second call returns 200 OK with Failed status
963                        Ok(AsyncRawResponse::from_bytes(
964                            StatusCode::Ok,
965                            Headers::new(),
966                            br#"{"status":"Failed"}"#.to_vec(),
967                        ))
968                    }
969                }
970                .boxed()
971            }))
972        };
973        let mut poller = Poller::new(
974            move |_, _| {
975                let client = mock_client.clone();
976                Box::pin(async move {
977                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
978                    let raw_response = client
979                        .execute_request(&req)
980                        .await?
981                        .try_into_raw_response()
982                        .await?;
983                    let (status, headers, body) = raw_response.deconstruct();
984
985                    let test_status: TestStatus = crate::json::from_json(&body)?;
986                    let response: Response<TestStatus> =
987                        RawResponse::from_bytes(status, headers, body).into();
988
989                    match test_status.status() {
990                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
991                            response,
992                            retry_after: Duration::ZERO,
993                            continuation: PollerContinuation::Links {
994                                next_link: req.url().clone(),
995                                final_link: None,
996                            },
997                        }),
998                        _ => Ok(PollerResult::Done { response }),
999                    }
1000                })
1001            },
1002            None,
1003        );
1004
1005        // First poll should succeed (201 Created with InProgress)
1006        let first_result = poller.next().await;
1007        assert!(first_result.is_some());
1008        let first_response = first_result.unwrap().unwrap();
1009        assert_eq!(first_response.status(), StatusCode::Created);
1010        let first_body = first_response.into_model().unwrap();
1011        assert_eq!(first_body.status(), PollerStatus::InProgress);
1012
1013        // Second poll should succeed (200 OK with Succeeded)
1014        let second_result = poller.next().await;
1015        assert!(second_result.is_some());
1016        let second_response = second_result.unwrap().unwrap();
1017        assert_eq!(second_response.status(), StatusCode::Ok);
1018        let second_body = second_response.into_model().unwrap();
1019        assert_eq!(second_body.status(), PollerStatus::Failed);
1020
1021        // Third poll should return None (end of stream)
1022        let third_result = poller.next().await;
1023        assert!(third_result.is_none());
1024
1025        // Verify both calls were made
1026        assert_eq!(*call_count.lock().unwrap(), 2);
1027    }
1028
1029    #[tokio::test]
1030    async fn poller_failed_with_http_429() {
1031        let call_count = Arc::new(Mutex::new(0));
1032
1033        let mock_client = {
1034            let call_count = call_count.clone();
1035            Arc::new(MockHttpClient::new(move |_| {
1036                let call_count = call_count.clone();
1037                async move {
1038                    let mut count = call_count.lock().unwrap();
1039                    *count += 1;
1040
1041                    if *count == 1 {
1042                        // First call returns 200 OK with InProgress status
1043                        Ok(AsyncRawResponse::from_bytes(
1044                            StatusCode::Ok,
1045                            Headers::new(),
1046                            br#"{"status":"InProgress"}"#.to_vec(),
1047                        ))
1048                    } else {
1049                        // Second call returns 429 Too Many Requests
1050                        Ok(AsyncRawResponse::from_bytes(
1051                            StatusCode::TooManyRequests,
1052                            Headers::new(),
1053                            vec![],
1054                        ))
1055                    }
1056                }
1057                .boxed()
1058            }))
1059        };
1060
1061        let mut poller = Poller::new(
1062            move |_, _| {
1063                let client = mock_client.clone();
1064                Box::pin(async move {
1065                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1066                    let raw_response = client
1067                        .execute_request(&req)
1068                        .await?
1069                        .try_into_raw_response()
1070                        .await?;
1071                    let (status, headers, body) = raw_response.deconstruct();
1072
1073                    if status == StatusCode::Ok {
1074                        let test_status: TestStatus = crate::json::from_json(&body)?;
1075                        let response: Response<TestStatus> =
1076                            RawResponse::from_bytes(status, headers, body).into();
1077
1078                        match test_status.status() {
1079                            PollerStatus::InProgress => Ok(PollerResult::InProgress {
1080                                response,
1081                                retry_after: Duration::ZERO,
1082                                continuation: PollerContinuation::Links {
1083                                    next_link: req.url().clone(),
1084                                    final_link: None,
1085                                },
1086                            }),
1087                            _ => Ok(PollerResult::Done { response }),
1088                        }
1089                    } else {
1090                        // Return the error response which should trigger check_status_code
1091                        let response: Response<TestStatus> =
1092                            RawResponse::from_bytes(status, headers, body).into();
1093                        Ok(PollerResult::Done { response })
1094                    }
1095                })
1096            },
1097            None,
1098        );
1099
1100        // First poll should succeed (200 OK with InProgress)
1101        let first_result = poller.next().await;
1102        assert!(first_result.is_some());
1103        assert!(first_result.unwrap().is_ok());
1104
1105        // Second poll should fail due to 429 status code being rejected by check_status_code
1106        let second_result = poller.next().await;
1107        assert!(second_result.is_some());
1108        let error = second_result.unwrap().unwrap_err();
1109
1110        // Verify the error is an HttpResponse error with 429 status
1111        match error.kind() {
1112            ErrorKind::HttpResponse { status, .. } => {
1113                assert_eq!(*status, StatusCode::TooManyRequests);
1114            }
1115            _ => panic!("Expected HttpResponse error, got {:?}", error.kind()),
1116        }
1117
1118        // Verify both calls were made
1119        assert_eq!(*call_count.lock().unwrap(), 2);
1120    }
1121
1122    #[tokio::test]
1123    async fn poller_into_future_succeeds() {
1124        let call_count = Arc::new(Mutex::new(0));
1125
1126        let mock_client = {
1127            let call_count = call_count.clone();
1128            Arc::new(MockHttpClient::new(move |_| {
1129                let call_count = call_count.clone();
1130                async move {
1131                    let mut count = call_count.lock().unwrap();
1132                    *count += 1;
1133
1134                    if *count == 1 {
1135                        // First call returns 201 Created with InProgress status
1136                        Ok(AsyncRawResponse::from_bytes(
1137                            StatusCode::Created,
1138                            Headers::new(),
1139                            br#"{"status":"InProgress"}"#.to_vec(),
1140                        ))
1141                    } else {
1142                        // Second call returns 200 OK with Succeeded status and final result
1143                        Ok(AsyncRawResponse::from_bytes(
1144                            StatusCode::Ok,
1145                            Headers::new(),
1146                            br#"{"status":"Succeeded","id":"op1","name":"Operation completed successfully"}"#.to_vec(),
1147                        ))
1148                    }
1149                }
1150                .boxed()
1151            }))
1152        };
1153
1154        let poller = Poller::new(
1155            move |_, _| {
1156                let client = mock_client.clone();
1157                Box::pin(async move {
1158                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1159                    let raw_response = client.execute_request(&req).await?;
1160                    let (status, headers, body) = raw_response.deconstruct();
1161                    let bytes = body.collect().await?;
1162
1163                    let test_status: TestStatus = crate::json::from_json(&bytes)?;
1164                    let response: Response<TestStatus> =
1165                        RawResponse::from_bytes(status, headers.clone(), bytes.clone()).into();
1166
1167                    match test_status.status() {
1168                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1169                            response,
1170                            retry_after: Duration::ZERO,
1171                            continuation: PollerContinuation::Links {
1172                                next_link: req.url().clone(),
1173                                final_link: None,
1174                            },
1175                        }),
1176                        PollerStatus::Succeeded => {
1177                            // Return the status response with a callback to fetch the final resource
1178                            Ok(PollerResult::Succeeded {
1179                                response,
1180                                target: Box::new(|| {
1181                                    Box::pin(async {
1182                                        // In a real scenario, this would fetch the final resource
1183                                        // For this test, the final status already contains the result
1184                                        use crate::http::headers::Headers;
1185                                        let headers = Headers::new();
1186                                        let bytes = bytes::Bytes::from(
1187                                            r#"{"id": "op1", "name": "Operation completed successfully"}"#,
1188                                        );
1189                                        Ok(RawResponse::from_bytes(StatusCode::Ok, headers, bytes)
1190                                            .into())
1191                                    })
1192                                }),
1193                            })
1194                        }
1195                        _ => Ok(PollerResult::Done { response }),
1196                    }
1197                })
1198            },
1199            None,
1200        );
1201
1202        // Use IntoFuture to await completion
1203        let result = poller.await;
1204        assert!(result.is_ok());
1205        let response = result.unwrap();
1206        assert_eq!(response.status(), StatusCode::Ok);
1207        let output = response.into_model().unwrap();
1208        assert_eq!(output.id.as_deref(), Some("op1"));
1209        assert_eq!(
1210            output.name.as_deref(),
1211            Some("Operation completed successfully")
1212        );
1213
1214        // Verify both calls were made
1215        assert_eq!(*call_count.lock().unwrap(), 2);
1216    }
1217
1218    #[tokio::test]
1219    async fn poller_into_future_with_target_url() {
1220        let call_count = Arc::new(Mutex::new(0));
1221
1222        let mock_client = {
1223            let call_count = call_count.clone();
1224            Arc::new(MockHttpClient::new(move |req: &Request| {
1225                let call_count = call_count.clone();
1226                let url = req.url().to_string();
1227                async move {
1228                    let mut count = call_count.lock().unwrap();
1229                    *count += 1;
1230
1231                    if *count == 1 {
1232                        // First call to operation URL returns InProgress status
1233                        Ok(AsyncRawResponse::from_bytes(
1234                            StatusCode::Accepted,
1235                            Headers::new(),
1236                            br#"{"status":"InProgress"}"#.to_vec(),
1237                        ))
1238                    } else if *count == 2 {
1239                        // Second call to operation URL returns Succeeded with target URL
1240                        Ok(AsyncRawResponse::from_bytes(
1241                            StatusCode::Ok,
1242                            Headers::new(),
1243                            br#"{"status":"Succeeded","target":"https://example.com/resources/123"}"#.to_vec(),
1244                        ))
1245                    } else {
1246                        // Third call fetches the final resource from target URL
1247                        assert_eq!(url, "https://example.com/resources/123");
1248                        Ok(AsyncRawResponse::from_bytes(
1249                            StatusCode::Ok,
1250                            Headers::new(),
1251                            br#"{"id":"123","name":"Test Resource"}"#.to_vec(),
1252                        ))
1253                    }
1254                }
1255                .boxed()
1256            }))
1257        };
1258
1259        let poller = Poller::new(
1260            move |_, _| {
1261                let client = mock_client.clone();
1262                Box::pin(async move {
1263                    let req = Request::new(
1264                        "https://example.com/operations/op1".parse().unwrap(),
1265                        Method::Get,
1266                    );
1267                    let raw_response = client.execute_request(&req).await?;
1268                    let (status, headers, body) = raw_response.deconstruct();
1269                    let bytes = body.collect().await?;
1270
1271                    let operation_status: TestStatus = crate::json::from_json(&bytes)?;
1272                    let response: Response<TestStatus> =
1273                        RawResponse::from_bytes(status, headers.clone(), bytes.clone()).into();
1274
1275                    match operation_status.status() {
1276                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1277                            response,
1278                            retry_after: Duration::ZERO,
1279                            continuation: PollerContinuation::Links {
1280                                next_link: req.url().clone(),
1281                                final_link: None,
1282                            },
1283                        }),
1284                        PollerStatus::Succeeded => {
1285                            // Return the status response with a callback to fetch the final resource
1286                            if let Some(target_url) = operation_status.target {
1287                                let client_clone = client.clone();
1288                                Ok(PollerResult::Succeeded {
1289                                    response,
1290                                    target: Box::new(move || {
1291                                        Box::pin(async move {
1292                                            let target_req = Request::new(
1293                                                target_url.parse().unwrap(),
1294                                                Method::Get,
1295                                            );
1296                                            let target_response =
1297                                                client_clone.execute_request(&target_req).await?;
1298                                            let (target_status, target_headers, target_body) =
1299                                                target_response.deconstruct();
1300                                            let target_bytes = target_body.collect().await?;
1301
1302                                            Ok(RawResponse::from_bytes(
1303                                                target_status,
1304                                                target_headers,
1305                                                target_bytes,
1306                                            )
1307                                            .into())
1308                                        })
1309                                    }),
1310                                })
1311                            } else {
1312                                Err(crate::Error::new(
1313                                    ErrorKind::Other,
1314                                    "no target URL in succeeded response",
1315                                ))
1316                            }
1317                        }
1318                        _ => Ok(PollerResult::Done { response }),
1319                    }
1320                })
1321            },
1322            None,
1323        );
1324
1325        // Use IntoFuture to await completion
1326        let result = poller.await;
1327        assert!(result.is_ok());
1328        let response = result.unwrap();
1329        assert_eq!(response.status(), StatusCode::Ok);
1330        let resource = response.into_model().unwrap();
1331        assert_eq!(resource.id.as_deref(), Some("123"));
1332        assert_eq!(resource.name.as_deref(), Some("Test Resource"));
1333
1334        // Verify all three calls were made
1335        assert_eq!(*call_count.lock().unwrap(), 3);
1336    }
1337
1338    #[tokio::test]
1339    async fn poller_into_future_no_response_body() {
1340        #[derive(Debug, serde::Deserialize)]
1341        struct NoBodyStatus {
1342            status: String,
1343        }
1344
1345        impl StatusMonitor for NoBodyStatus {
1346            type Output = ();
1347            type Format = NoFormat;
1348
1349            fn status(&self) -> PollerStatus {
1350                self.status.parse().unwrap_or_default()
1351            }
1352        }
1353
1354        let call_count = Arc::new(Mutex::new(0));
1355
1356        let mock_client = {
1357            let call_count = call_count.clone();
1358            Arc::new(MockHttpClient::new(move |_| {
1359                let call_count = call_count.clone();
1360                async move {
1361                    let mut count = call_count.lock().unwrap();
1362                    *count += 1;
1363
1364                    if *count == 1 {
1365                        // First call returns 202 Accepted with InProgress status
1366                        Ok(AsyncRawResponse::from_bytes(
1367                            StatusCode::Accepted,
1368                            Headers::new(),
1369                            br#"{"status":"InProgress"}"#.to_vec(),
1370                        ))
1371                    } else {
1372                        // Second call returns 200 OK with Succeeded status
1373                        Ok(AsyncRawResponse::from_bytes(
1374                            StatusCode::Ok,
1375                            Headers::new(),
1376                            br#"{"status":"Succeeded"}"#.to_vec(),
1377                        ))
1378                    }
1379                }
1380                .boxed()
1381            }))
1382        };
1383
1384        let poller = Poller::new(
1385            move |_, _| {
1386                let client = mock_client.clone();
1387                Box::pin(async move {
1388                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1389                    let raw_response = client.execute_request(&req).await?;
1390                    let (status, headers, body) = raw_response.deconstruct();
1391                    let bytes = body.collect().await?;
1392
1393                    let no_body_status: NoBodyStatus = crate::json::from_json(&bytes)?;
1394                    let response: Response<NoBodyStatus> =
1395                        RawResponse::from_bytes(status, headers.clone(), bytes.clone()).into();
1396
1397                    match no_body_status.status() {
1398                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1399                            response,
1400                            retry_after: Duration::ZERO,
1401                            continuation: PollerContinuation::Links {
1402                                next_link: req.url().clone(),
1403                                final_link: None,
1404                            },
1405                        }),
1406                        PollerStatus::Succeeded => {
1407                            // Return the status response with a callback
1408                            Ok(PollerResult::Succeeded {
1409                                response,
1410                                target: Box::new(move || {
1411                                    Box::pin(async move {
1412                                        // Return a Response<()> with no body for operations that don't return data
1413                                        use crate::http::headers::Headers;
1414                                        let headers = Headers::new();
1415                                        Ok(RawResponse::from_bytes(status, headers, Vec::new())
1416                                            .into())
1417                                    })
1418                                }),
1419                            })
1420                        }
1421                        _ => Ok(PollerResult::Done { response }),
1422                    }
1423                })
1424            },
1425            None,
1426        );
1427
1428        // Use IntoFuture to await completion
1429        let result = poller.await;
1430        assert!(result.is_ok());
1431        let response = result.unwrap();
1432        assert_eq!(response.status(), StatusCode::Ok);
1433        // For operations with no response body, we don't need to call into_model()
1434        // The important thing is that the poller completed successfully and returned Response<()>
1435
1436        // Verify both calls were made
1437        assert_eq!(*call_count.lock().unwrap(), 2);
1438    }
1439
1440    #[cfg(feature = "xml")]
1441    #[tokio::test]
1442    async fn poller_succeeded_xml() {
1443        let call_count = Arc::new(Mutex::new(0));
1444
1445        let mock_client = {
1446            let call_count = call_count.clone();
1447            Arc::new(MockHttpClient::new(move |_| {
1448                let call_count = call_count.clone();
1449                async move {
1450                    let mut count = call_count.lock().unwrap();
1451                    *count += 1;
1452
1453                    if *count == 1 {
1454                        // First call returns 201 Created with InProgress status
1455                        Ok(AsyncRawResponse::from_bytes(
1456                            StatusCode::Created,
1457                            Headers::new(),
1458                            b"<XmlTestStatus><status>InProgress</status></XmlTestStatus>".to_vec(),
1459                        ))
1460                    } else {
1461                        // Second call returns 200 OK with Succeeded status
1462                        Ok(AsyncRawResponse::from_bytes(
1463                            StatusCode::Ok,
1464                            Headers::new(),
1465                            b"<XmlTestStatus><status>Succeeded</status></XmlTestStatus>".to_vec(),
1466                        ))
1467                    }
1468                }
1469                .boxed()
1470            }))
1471        };
1472
1473        let mut poller = Poller::new(
1474            move |_, _| {
1475                let client = mock_client.clone();
1476                Box::pin(async move {
1477                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1478                    let raw_response = client.execute_request(&req).await?;
1479                    let (status, headers, body) = raw_response.deconstruct();
1480                    let bytes = body.collect().await?;
1481
1482                    let test_status: XmlTestStatus = crate::xml::from_xml(&bytes)?;
1483                    let response: Response<XmlTestStatus, XmlFormat> =
1484                        RawResponse::from_bytes(status, headers, bytes).into();
1485
1486                    match test_status.status() {
1487                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1488                            response,
1489                            retry_after: Duration::ZERO,
1490                            continuation: PollerContinuation::Links {
1491                                next_link: req.url().clone(),
1492                                final_link: None,
1493                            },
1494                        }),
1495                        _ => Ok(PollerResult::Done { response }),
1496                    }
1497                })
1498            },
1499            None,
1500        );
1501
1502        // First poll should succeed (201 Created with InProgress)
1503        let first_result = poller.next().await;
1504        assert!(first_result.is_some());
1505        let first_response = first_result.unwrap().unwrap();
1506        assert_eq!(first_response.status(), StatusCode::Created);
1507        let first_body = first_response.into_model().unwrap();
1508        assert_eq!(first_body.status(), PollerStatus::InProgress);
1509
1510        // Second poll should succeed (200 OK with Succeeded)
1511        let second_result = poller.next().await;
1512        assert!(second_result.is_some());
1513        let second_response = second_result.unwrap().unwrap();
1514        assert_eq!(second_response.status(), StatusCode::Ok);
1515        let second_body = second_response.into_model().unwrap();
1516        assert_eq!(second_body.status(), PollerStatus::Succeeded);
1517
1518        // Third poll should return None (end of stream)
1519        let third_result = poller.next().await;
1520        assert!(third_result.is_none());
1521
1522        // Verify both calls were made
1523        assert_eq!(*call_count.lock().unwrap(), 2);
1524    }
1525
1526    #[cfg(feature = "xml")]
1527    #[tokio::test]
1528    async fn poller_into_future_succeeds_xml() {
1529        let call_count = Arc::new(Mutex::new(0));
1530
1531        let mock_client = {
1532            let call_count = call_count.clone();
1533            Arc::new(MockHttpClient::new(move |_| {
1534                let call_count = call_count.clone();
1535                async move {
1536                    let mut count = call_count.lock().unwrap();
1537                    *count += 1;
1538
1539                    if *count == 1 {
1540                        // First call returns 201 Created with InProgress status
1541                        Ok(AsyncRawResponse::from_bytes(
1542                            StatusCode::Created,
1543                            Headers::new(),
1544                            b"<XmlTestStatus><status>InProgress</status></XmlTestStatus>"
1545                                .to_vec(),
1546                        ))
1547                    } else {
1548                        // Second call returns 200 OK with Succeeded status and final result
1549                        // Note: The response contains both status and the final output fields
1550                        Ok(AsyncRawResponse::from_bytes(
1551                            StatusCode::Ok,
1552                            Headers::new(),
1553                            b"<XmlTestStatus><status>Succeeded</status><id>op1</id><name>Operation completed successfully</name></XmlTestStatus>"
1554                                .to_vec(),
1555                        ))
1556                    }
1557                }
1558                .boxed()
1559            }))
1560        };
1561
1562        let poller = Poller::new(
1563            move |_, _| {
1564                let client = mock_client.clone();
1565                Box::pin(async move {
1566                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1567                    let raw_response = client.execute_request(&req).await?;
1568                    let (status, headers, body) = raw_response.deconstruct();
1569                    let bytes = body.collect().await?;
1570
1571                    let test_status: XmlTestStatus = crate::xml::from_xml(&bytes)?;
1572                    let response: Response<XmlTestStatus, XmlFormat> =
1573                        RawResponse::from_bytes(status, headers.clone(), bytes.clone()).into();
1574
1575                    match test_status.status() {
1576                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1577                            response,
1578                            retry_after: Duration::ZERO,
1579                            continuation: PollerContinuation::Links {
1580                                next_link: req.url().clone(),
1581                                final_link: None,
1582                            },
1583                        }),
1584                        PollerStatus::Succeeded => {
1585                            // Return the status response with a callback
1586                            Ok(PollerResult::Succeeded {
1587                                response,
1588                                target: Box::new(move || {
1589                                    Box::pin(async move {
1590                                        // For XML format, return the final response
1591                                        let headers = Headers::new();
1592                                        let bytes = bytes::Bytes::from(
1593                                            r#"<TestOutput><id>op1</id><name>Operation completed successfully</name></TestOutput>"#,
1594                                        );
1595                                        Ok(RawResponse::from_bytes(StatusCode::Ok, headers, bytes)
1596                                            .into())
1597                                    })
1598                                }),
1599                            })
1600                        }
1601                        _ => Ok(PollerResult::Done { response }),
1602                    }
1603                })
1604            },
1605            None,
1606        );
1607
1608        // Use IntoFuture to await completion
1609        let result = poller.await;
1610        assert!(result.is_ok());
1611        let response = result.unwrap();
1612        assert_eq!(response.status(), StatusCode::Ok);
1613        let output = response.into_model().unwrap();
1614        assert_eq!(output.id.as_deref(), Some("op1"));
1615        assert_eq!(
1616            output.name.as_deref(),
1617            Some("Operation completed successfully")
1618        );
1619
1620        // Verify both calls were made
1621        assert_eq!(*call_count.lock().unwrap(), 2);
1622    }
1623
1624    #[tokio::test]
1625    async fn poller_into_future_output_is_self() {
1626        // Test case where StatusMonitor::Output is the same type as the status monitor itself
1627        #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
1628        struct SelfContainedStatus {
1629            status: String,
1630            id: Option<String>,
1631            result: Option<String>,
1632        }
1633
1634        impl StatusMonitor for SelfContainedStatus {
1635            type Output = Self; // Output is the same type as the monitor
1636            type Format = JsonFormat;
1637
1638            fn status(&self) -> PollerStatus {
1639                self.status.parse().unwrap_or_default()
1640            }
1641        }
1642
1643        let call_count = Arc::new(Mutex::new(0));
1644
1645        let mock_client = {
1646            let call_count = call_count.clone();
1647            Arc::new(MockHttpClient::new(move |_| {
1648                let call_count = call_count.clone();
1649                async move {
1650                    let mut count = call_count.lock().unwrap();
1651                    *count += 1;
1652
1653                    if *count == 1 {
1654                        // First call returns 201 Created with InProgress status
1655                        Ok(AsyncRawResponse::from_bytes(
1656                            StatusCode::Created,
1657                            Headers::new(),
1658                            br#"{"status":"InProgress","id":"op1"}"#.to_vec(),
1659                        ))
1660                    } else {
1661                        // Second call returns 200 OK with Succeeded status and final result in the same object
1662                        Ok(AsyncRawResponse::from_bytes(
1663                            StatusCode::Ok,
1664                            Headers::new(),
1665                            br#"{"status":"Succeeded","id":"op1","result":"Operation completed successfully"}"#.to_vec(),
1666                        ))
1667                    }
1668                }
1669                .boxed()
1670            }))
1671        };
1672
1673        let poller = Poller::new(
1674            move |_, _| {
1675                let client = mock_client.clone();
1676                Box::pin(async move {
1677                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1678                    let raw_response = client.execute_request(&req).await?;
1679                    let (status, headers, body) = raw_response.deconstruct();
1680                    let bytes = body.collect().await?;
1681
1682                    let self_status: SelfContainedStatus = crate::json::from_json(&bytes)?;
1683                    let response: Response<SelfContainedStatus> =
1684                        RawResponse::from_bytes(status, headers.clone(), bytes.clone()).into();
1685
1686                    match self_status.status() {
1687                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1688                            response,
1689                            retry_after: Duration::ZERO,
1690                            continuation: PollerContinuation::Links {
1691                                next_link: req.url().clone(),
1692                                final_link: None,
1693                            },
1694                        }),
1695                        PollerStatus::Succeeded => {
1696                            // The final result is already in the status response itself
1697                            // No separate fetch needed - just return the same response in the callback
1698                            let final_bytes = bytes.clone();
1699                            Ok(PollerResult::Succeeded {
1700                                response,
1701                                target: Box::new(move || {
1702                                    Box::pin(async move {
1703                                        // Return the same data - no additional fetch needed
1704                                        let headers = Headers::new();
1705                                        Ok(RawResponse::from_bytes(
1706                                            StatusCode::Ok,
1707                                            headers,
1708                                            final_bytes,
1709                                        )
1710                                        .into())
1711                                    })
1712                                }),
1713                            })
1714                        }
1715                        _ => Ok(PollerResult::Done { response }),
1716                    }
1717                })
1718            },
1719            None,
1720        );
1721
1722        // Use IntoFuture to await completion
1723        let result = poller.await;
1724        assert!(result.is_ok());
1725        let response = result.unwrap();
1726        assert_eq!(response.status(), StatusCode::Ok);
1727        let output = response.into_model().unwrap();
1728        assert_eq!(output.id.as_deref(), Some("op1"));
1729        assert_eq!(
1730            output.result.as_deref(),
1731            Some("Operation completed successfully")
1732        );
1733
1734        // Verify both calls were made
1735        assert_eq!(*call_count.lock().unwrap(), 2);
1736    }
1737
1738    #[tokio::test]
1739    async fn poller_stream_output_is_self() {
1740        // Test case where StatusMonitor::Output is the same type as the status monitor itself
1741        // Used as a stream to monitor progress
1742        #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
1743        struct SelfContainedStatus {
1744            status: String,
1745            id: Option<String>,
1746            result: Option<String>,
1747        }
1748
1749        impl StatusMonitor for SelfContainedStatus {
1750            type Output = Self; // Output is the same type as the monitor
1751            type Format = JsonFormat;
1752
1753            fn status(&self) -> PollerStatus {
1754                self.status.parse().unwrap_or_default()
1755            }
1756        }
1757
1758        let call_count = Arc::new(Mutex::new(0));
1759
1760        let mock_client = {
1761            let call_count = call_count.clone();
1762            Arc::new(MockHttpClient::new(move |_| {
1763                let call_count = call_count.clone();
1764                async move {
1765                    let mut count = call_count.lock().unwrap();
1766                    *count += 1;
1767
1768                    if *count == 1 {
1769                        // First call returns 201 Created with InProgress status
1770                        Ok(AsyncRawResponse::from_bytes(
1771                            StatusCode::Created,
1772                            Headers::new(),
1773                            br#"{"status":"InProgress","id":"op1"}"#.to_vec(),
1774                        ))
1775                    } else {
1776                        // Second call returns 200 OK with Succeeded status and final result in the same object
1777                        Ok(AsyncRawResponse::from_bytes(
1778                            StatusCode::Ok,
1779                            Headers::new(),
1780                            br#"{"status":"Succeeded","id":"op1","result":"Operation completed successfully"}"#.to_vec(),
1781                        ))
1782                    }
1783                }
1784                .boxed()
1785            }))
1786        };
1787
1788        let mut poller = Poller::new(
1789            move |_, _| {
1790                let client = mock_client.clone();
1791                Box::pin(async move {
1792                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1793                    let raw_response = client.execute_request(&req).await?;
1794                    let (status, headers, body) = raw_response.deconstruct();
1795                    let bytes = body.collect().await?;
1796
1797                    let self_status: SelfContainedStatus = crate::json::from_json(&bytes)?;
1798                    let response: Response<SelfContainedStatus> =
1799                        RawResponse::from_bytes(status, headers.clone(), bytes.clone()).into();
1800
1801                    match self_status.status() {
1802                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1803                            response,
1804                            retry_after: Duration::ZERO,
1805                            continuation: PollerContinuation::Links {
1806                                next_link: req.url().clone(),
1807                                final_link: None,
1808                            },
1809                        }),
1810                        PollerStatus::Succeeded => {
1811                            // The final result is already in the status response itself
1812                            let final_bytes = bytes.clone();
1813                            Ok(PollerResult::Succeeded {
1814                                response,
1815                                target: Box::new(move || {
1816                                    Box::pin(async move {
1817                                        use crate::http::headers::Headers;
1818                                        let headers = Headers::new();
1819                                        Ok(RawResponse::from_bytes(
1820                                            StatusCode::Ok,
1821                                            headers,
1822                                            final_bytes,
1823                                        )
1824                                        .into())
1825                                    })
1826                                }),
1827                            })
1828                        }
1829                        _ => Ok(PollerResult::Done { response }),
1830                    }
1831                })
1832            },
1833            None,
1834        );
1835
1836        // Use as a stream to monitor progress
1837        let mut statuses = Vec::new();
1838        while let Some(status_response) = poller.try_next().await.unwrap() {
1839            let status = status_response.into_model().unwrap();
1840            statuses.push(status);
1841        }
1842
1843        // Should have received both InProgress and Succeeded statuses
1844        assert_eq!(statuses.len(), 2);
1845        assert_eq!(statuses[0].status, "InProgress");
1846        assert_eq!(statuses[0].id.as_deref(), Some("op1"));
1847        assert_eq!(statuses[0].result, None);
1848
1849        assert_eq!(statuses[1].status, "Succeeded");
1850        assert_eq!(statuses[1].id.as_deref(), Some("op1"));
1851        assert_eq!(
1852            statuses[1].result.as_deref(),
1853            Some("Operation completed successfully")
1854        );
1855
1856        // Verify both calls were made
1857        assert_eq!(*call_count.lock().unwrap(), 2);
1858    }
1859
1860    #[tokio::test]
1861    async fn into_future_output_model_and_raw_response() {
1862        let call_count = Arc::new(Mutex::new(0));
1863        let final_json = br#"{"id":"res1","name":"My Resource"}"#;
1864
1865        let mock_client = {
1866            let call_count = call_count.clone();
1867            Arc::new(MockHttpClient::new(move |_| {
1868                let call_count = call_count.clone();
1869                async move {
1870                    let mut count = call_count.lock().unwrap();
1871                    *count += 1;
1872
1873                    if *count == 1 {
1874                        Ok(AsyncRawResponse::from_bytes(
1875                            StatusCode::Accepted,
1876                            Headers::new(),
1877                            br#"{"status":"InProgress"}"#.to_vec(),
1878                        ))
1879                    } else {
1880                        Ok(AsyncRawResponse::from_bytes(
1881                            StatusCode::Ok,
1882                            Headers::new(),
1883                            br#"{"status":"Succeeded"}"#.to_vec(),
1884                        ))
1885                    }
1886                }
1887                .boxed()
1888            }))
1889        };
1890
1891        let poller = Poller::new(
1892            move |_, _| {
1893                let client = mock_client.clone();
1894                Box::pin(async move {
1895                    let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1896                    let raw_response = client.execute_request(&req).await?;
1897                    let (status, headers, body) = raw_response.deconstruct();
1898                    let bytes = body.collect().await?;
1899
1900                    let test_status: TestStatus = crate::json::from_json(&bytes)?;
1901                    let response: Response<TestStatus> =
1902                        RawResponse::from_bytes(status, headers.clone(), bytes).into();
1903
1904                    match test_status.status() {
1905                        PollerStatus::InProgress => Ok(PollerResult::InProgress {
1906                            response,
1907                            retry_after: Duration::ZERO,
1908                            continuation: PollerContinuation::Links {
1909                                next_link: req.url().clone(),
1910                                final_link: None,
1911                            },
1912                        }),
1913                        PollerStatus::Succeeded => {
1914                            let target_body =
1915                                bytes::Bytes::from_static(br#"{"id":"res1","name":"My Resource"}"#);
1916                            Ok(PollerResult::Succeeded {
1917                                response,
1918                                target: Box::new(move || {
1919                                    Box::pin(async move {
1920                                        Ok(RawResponse::from_bytes(
1921                                            StatusCode::Ok,
1922                                            Headers::new(),
1923                                            target_body,
1924                                        )
1925                                        .into())
1926                                    })
1927                                }),
1928                            })
1929                        }
1930                        _ => Ok(PollerResult::Done { response }),
1931                    }
1932                })
1933            },
1934            None,
1935        );
1936
1937        // Await the poller to get Response<TestOutput> (the StatusMonitor::Output type).
1938        let response = poller.await.unwrap();
1939        assert_eq!(response.status(), StatusCode::Ok);
1940
1941        // Clone the RawResponse before consuming via into_model().
1942        let raw = response.to_raw_response();
1943
1944        // Deserialize into the Output model and verify fields.
1945        let output = response.into_model().unwrap();
1946        assert_eq!(output.id.as_deref(), Some("res1"));
1947        assert_eq!(output.name.as_deref(), Some("My Resource"));
1948
1949        // The RawResponse still holds the full JSON body.
1950        let (raw_status, _, raw_body) = raw.deconstruct();
1951        assert_eq!(raw_status, StatusCode::Ok);
1952        assert_eq!(raw_body.as_ref(), final_json);
1953
1954        assert_eq!(*call_count.lock().unwrap(), 2);
1955    }
1956
1957    #[tokio::test]
1958    #[deny(unfulfilled_lint_expectations)]
1959    async fn must_use_poller() {
1960        fn create() -> crate::Result<Poller<TestStatus>> {
1961            let call_count = Arc::new(Mutex::new(0));
1962
1963            let mock_client = {
1964                let call_count = call_count.clone();
1965                Arc::new(MockHttpClient::new(move |_| {
1966                    let call_count = call_count.clone();
1967                    async move {
1968                        let mut count = call_count.lock().unwrap();
1969                        *count += 1;
1970
1971                        if *count == 1 {
1972                            // First call returns 201 Created with InProgress status
1973                            Ok(AsyncRawResponse::from_bytes(
1974                                StatusCode::Created,
1975                                Headers::new(),
1976                                br#"{"status":"InProgress"}"#.to_vec(),
1977                            ))
1978                        } else {
1979                            // Second call returns 200 OK with Succeeded status
1980                            Ok(AsyncRawResponse::from_bytes(
1981                                StatusCode::Ok,
1982                                Headers::new(),
1983                                br#"{"status":"Succeeded"}"#.to_vec(),
1984                            ))
1985                        }
1986                    }
1987                    .boxed()
1988                }))
1989            };
1990
1991            let poller = Poller::new(
1992                move |_, _| {
1993                    let client = mock_client.clone();
1994                    Box::pin(async move {
1995                        let req = Request::new("https://example.com".parse().unwrap(), Method::Get);
1996                        let raw_response = client.execute_request(&req).await?;
1997                        let (status, headers, body) = raw_response.deconstruct();
1998                        let bytes = body.collect().await?;
1999
2000                        let test_status: TestStatus = crate::json::from_json(&bytes)?;
2001                        let response: Response<TestStatus> =
2002                            RawResponse::from_bytes(status, headers, bytes).into();
2003
2004                        match test_status.status() {
2005                            PollerStatus::InProgress => Ok(PollerResult::InProgress {
2006                                response,
2007                                retry_after: Duration::ZERO,
2008                                continuation: PollerContinuation::Links {
2009                                    next_link: req.url().clone(),
2010                                    final_link: None,
2011                                },
2012                            }),
2013                            _ => Ok(PollerResult::Done { response }),
2014                        }
2015                    })
2016                },
2017                None,
2018            );
2019
2020            Ok(poller)
2021        }
2022
2023        #[expect(unused_must_use)]
2024        create();
2025
2026        #[expect(unused_must_use)]
2027        create().unwrap();
2028
2029        // We cannot use #[must_use] to force a stream to be polled.
2030        #[deny(unused_must_use)]
2031        let _poller = create().unwrap();
2032
2033        #[deny(unused_must_use)]
2034        let mut poller = create().unwrap();
2035        assert!(poller.next().await.is_some());
2036        assert!(poller.next().await.is_some());
2037        assert!(poller.next().await.is_none());
2038    }
2039}