src/http/send.rs 4.0 K raw
1
//! App-level send orchestration: pick the auth scheme, and for OAuth cache the
2
//! token + retry once on 401.
3
4
use std::collections::HashMap;
5
6
use super::client::{HttpResponse, send_request};
7
use super::oauth::{OAuthToken, fetch_token, token_valid};
8
use super::secret::resolve_secret;
9
use crate::model::{AuthKind, OAuthConfig, SavedRequest};
10
11
pub struct SendOutcome {
12
    pub result: Result<HttpResponse, String>,
13
    /// Latest OAuth token cache (unchanged on failure, refreshed on (re)fetch).
14
    /// Always `None` for the bearer/API-key schemes, which hold no cache.
15
    pub token: Option<OAuthToken>,
16
}
17
18
/// Send a request under the collection's auth scheme:
19
/// - **none** — send as-is.
20
/// - **bearer** — resolve the token (may be a `$(…)` secret) and send it as
21
///   `Authorization: Bearer …`.
22
/// - **apikey** — resolve the value and send it in the configured header.
23
/// - **oauth2** — reuse a cached client-credentials token while valid, fetch
24
///   one otherwise, and retry the request once with a fresh token on a 401.
25
pub async fn send_with_auth(
26
    client: &reqwest::Client,
27
    base_url: &str,
28
    req: &SavedRequest,
29
    vars: &HashMap<String, String>,
30
    auth: Option<&OAuthConfig>,
31
    cached: Option<OAuthToken>,
32
) -> SendOutcome {
33
    let Some(cfg) = auth.filter(|c| c.is_configured()) else {
34
        let result = send_request(client, base_url, req, vars, None, &[])
35
            .await
36
            .map_err(|e| format!("{e:#}"));
37
        return SendOutcome {
38
            result,
39
            token: cached,
40
        };
41
    };
42
43
    match cfg.kind {
44
        AuthKind::Bearer => {
45
            let token = match resolve_secret(&cfg.token) {
46
                Ok(t) => t,
47
                Err(e) => return secret_error(e, cached),
48
            };
49
            let result = send_request(client, base_url, req, vars, Some(&token), &[])
50
                .await
51
                .map_err(|e| format!("{e:#}"));
52
            SendOutcome {
53
                result,
54
                token: cached,
55
            }
56
        }
57
        AuthKind::ApiKey => {
58
            let value = match resolve_secret(&cfg.token) {
59
                Ok(v) => v,
60
                Err(e) => return secret_error(e, cached),
61
            };
62
            let extra = [(cfg.api_key_header().to_string(), value)];
63
            let result = send_request(client, base_url, req, vars, None, &extra)
64
                .await
65
                .map_err(|e| format!("{e:#}"));
66
            SendOutcome {
67
                result,
68
                token: cached,
69
            }
70
        }
71
        AuthKind::Oauth2 => oauth_send(client, base_url, req, vars, cfg, cached).await,
72
    }
73
}
74
75
fn secret_error(e: anyhow::Error, token: Option<OAuthToken>) -> SendOutcome {
76
    SendOutcome {
77
        result: Err(format!("secret resolution failed: {e:#}")),
78
        token,
79
    }
80
}
81
82
async fn oauth_send(
83
    client: &reqwest::Client,
84
    base_url: &str,
85
    req: &SavedRequest,
86
    vars: &HashMap<String, String>,
87
    cfg: &OAuthConfig,
88
    cached: Option<OAuthToken>,
89
) -> SendOutcome {
90
    let mut token = cached;
91
92
    let stale = token.as_ref().map(|t| !token_valid(t)).unwrap_or(true);
93
    if stale {
94
        match fetch_token(client, cfg).await {
95
            Ok(t) => token = Some(t),
96
            Err(e) => {
97
                return SendOutcome {
98
                    result: Err(format!("token fetch failed: {e:#}")),
99
                    token,
100
                };
101
            }
102
        }
103
    }
104
    let mut bearer = token.as_ref().map(|t| t.access_token.clone());
105
106
    let mut resp = send_request(client, base_url, req, vars, bearer.as_deref(), &[])
107
        .await
108
        .map_err(|e| format!("{e:#}"));
109
110
    if let Ok(r) = &resp
111
        && r.status == 401
112
        && let Ok(t) = fetch_token(client, cfg).await
113
    {
114
        bearer = Some(t.access_token.clone());
115
        token = Some(t);
116
        resp = send_request(client, base_url, req, vars, bearer.as_deref(), &[])
117
            .await
118
            .map_err(|e| format!("{e:#}"));
119
    }
120
121
    SendOutcome {
122
        result: resp,
123
        token,
124
    }
125
}