| 1 | //! App-level send orchestration: OAuth token caching + one 401 retry. |
| 2 | |
| 3 | use std::collections::HashMap; |
| 4 | |
| 5 | use super::client::{HttpResponse, send_request}; |
| 6 | use super::oauth::{OAuthToken, fetch_token, token_valid}; |
| 7 | use crate::model::{OAuthConfig, SavedRequest}; |
| 8 | |
| 9 | pub struct SendOutcome { |
| 10 | pub result: Result<HttpResponse, String>, |
| 11 | /// Latest token cache (unchanged on failure, refreshed on (re)fetch). |
| 12 | pub token: Option<OAuthToken>, |
| 13 | } |
| 14 | |
| 15 | /// Send a request, transparently handling OAuth client-credentials auth: |
| 16 | /// reuse a cached token while valid, fetch one otherwise, and retry the |
| 17 | /// request once with a fresh token on a 401 response. |
| 18 | pub async fn send_with_auth( |
| 19 | client: &reqwest::Client, |
| 20 | base_url: &str, |
| 21 | req: &SavedRequest, |
| 22 | vars: &HashMap<String, String>, |
| 23 | auth: Option<&OAuthConfig>, |
| 24 | cached: Option<OAuthToken>, |
| 25 | ) -> SendOutcome { |
| 26 | let mut token = cached; |
| 27 | let auth = auth.filter(|c| c.is_configured()); |
| 28 | |
| 29 | let mut bearer: Option<String> = None; |
| 30 | if let Some(cfg) = auth { |
| 31 | let stale = token.as_ref().map(|t| !token_valid(t)).unwrap_or(true); |
| 32 | if stale { |
| 33 | match fetch_token(client, cfg).await { |
| 34 | Ok(t) => token = Some(t), |
| 35 | Err(e) => { |
| 36 | return SendOutcome { |
| 37 | result: Err(format!("token fetch failed: {e:#}")), |
| 38 | token, |
| 39 | }; |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | bearer = token.as_ref().map(|t| t.access_token.clone()); |
| 44 | } |
| 45 | |
| 46 | let mut resp = send_request(client, base_url, req, vars, bearer.as_deref()) |
| 47 | .await |
| 48 | .map_err(|e| format!("{e:#}")); |
| 49 | |
| 50 | if let (Ok(r), Some(cfg)) = (&resp, auth) |
| 51 | && r.status == 401 |
| 52 | && let Ok(t) = fetch_token(client, cfg).await |
| 53 | { |
| 54 | bearer = Some(t.access_token.clone()); |
| 55 | token = Some(t); |
| 56 | resp = send_request(client, base_url, req, vars, bearer.as_deref()) |
| 57 | .await |
| 58 | .map_err(|e| format!("{e:#}")); |
| 59 | } |
| 60 | |
| 61 | SendOutcome { |
| 62 | result: resp, |
| 63 | token, |
| 64 | } |
| 65 | } |