azure_core/
lro.rs

1use crate::headers::Headers;
2use std::time::Duration;
3use time::OffsetDateTime;
4
5/// Default retry time for long running operations if no retry-after header is present
6///
7/// This value is the same as the default used in the Azure SDK for Python.
8/// Ref: <https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-mgmt-core/azure/mgmt/core/polling/arm_polling.py#L191>
9const DEFAULT_RETRY_TIME: Duration = Duration::from_secs(30);
10
11/// Long Running Operation (LRO) status
12///
13/// Ref: <https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/async-operations#provisioningstate-values>
14#[derive(Debug)]
15pub enum LroStatus {
16    InProgress,
17    Succeeded,
18    Failed,
19    Canceled,
20    Other(String),
21}
22
23impl From<&str> for LroStatus {
24    fn from(s: &str) -> Self {
25        match s {
26            "InProgress" => LroStatus::InProgress,
27            "Succeeded" => LroStatus::Succeeded,
28            "Failed" => LroStatus::Failed,
29            // While the specification indicates we should use `Canceled`, in
30            // practice numerous services use `Cancelled`.  As such, we support
31            // both.
32            //
33            // Ref: <https://github.com/Azure/azure-resource-manager-rpc/issues/144>
34            "Canceled" | "Cancelled" => LroStatus::Canceled,
35            _ => LroStatus::Other(s.to_owned()),
36        }
37    }
38}
39
40pub fn get_retry_after(headers: &Headers) -> Duration {
41    crate::get_retry_after(headers, OffsetDateTime::now_utc).unwrap_or(DEFAULT_RETRY_TIME)
42}
43
44pub mod location {
45    use crate::{
46        from_json,
47        headers::{Headers, AZURE_ASYNCOPERATION, LOCATION, OPERATION_LOCATION},
48        lro::LroStatus,
49        Url,
50    };
51
52    #[derive(Debug, Clone, Copy)]
53    pub enum FinalState {
54        AzureAsyncOperation,
55        Location,
56        OperationLocation,
57    }
58
59    pub fn get_location(headers: &Headers, final_state: FinalState) -> crate::Result<Option<Url>> {
60        match final_state {
61            FinalState::AzureAsyncOperation => headers.get_optional_as(&AZURE_ASYNCOPERATION),
62            FinalState::Location => headers.get_optional_as(&LOCATION),
63            FinalState::OperationLocation => headers.get_optional_as(&OPERATION_LOCATION),
64        }
65    }
66
67    pub fn get_provisioning_state(body: &[u8]) -> Option<LroStatus> {
68        #[derive(serde::Deserialize)]
69        struct Body {
70            status: String,
71        }
72        let body: Body = from_json(body).ok()?;
73        Some(LroStatus::from(body.status.as_str()))
74    }
75}
76
77pub mod body_content {
78    use crate::{from_json, lro::LroStatus, to_json, StatusCode};
79    use serde::{Deserialize, Serialize};
80
81    /// Extract the provisioning state based on the status code and response body
82    ///
83    /// Ref: <https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-core/azure/core/polling/base_polling.py>
84    pub fn get_provisioning_state<S>(status_code: StatusCode, body: &S) -> crate::Result<LroStatus>
85    where
86        S: Serialize,
87    {
88        match status_code {
89            StatusCode::Accepted => Ok(LroStatus::InProgress),
90            StatusCode::Created => {
91                Ok(get_provisioning_state_from_body(body).unwrap_or(LroStatus::InProgress))
92            }
93            StatusCode::Ok => {
94                Ok(get_provisioning_state_from_body(body).unwrap_or(LroStatus::Succeeded))
95            }
96            StatusCode::NoContent => Ok(LroStatus::Succeeded),
97            _ => Err(crate::error::Error::from(
98                crate::error::ErrorKind::HttpResponse {
99                    status: status_code,
100                    error_code: Some("invalid status found in LRO response".to_owned()),
101                },
102            )),
103        }
104    }
105
106    #[derive(Deserialize)]
107    #[serde(rename_all = "snake_case")]
108    struct Properties {
109        provisioning_state: String,
110    }
111
112    #[derive(Deserialize)]
113    struct Body {
114        properties: Properties,
115    }
116
117    fn get_provisioning_state_from_body<S>(body: &S) -> Option<LroStatus>
118    where
119        S: Serialize,
120    {
121        let body: Body = from_json(to_json(&body).ok()?).ok()?;
122        Some(LroStatus::from(body.properties.provisioning_state.as_str()))
123    }
124}