Skip to main content

pas_external/oidc/
refresh_outcome.rs

1//! `RefreshOutcome` — typed boundary return for
2//! [`super::RelyingParty::refresh`].
3//!
4//! Symmetric with [`super::Completion`] (the deep return of
5//! [`super::RelyingParty::complete`]): both wrap the OAuth wire DTO at
6//! the SDK boundary so consumers never touch
7//! [`crate::oauth::TokenResponse`] directly.
8//!
9//! Phase 11.Y added this type. The 0.7.x shape exposed
10//! `oauth::TokenResponse` (an OAuth-wire-shaped DTO with `expires_in:
11//! Option<u64>`) at the refresh boundary; the typed boundary is
12//! deeper — `expires_in: Option<Duration>` lets the consumer drop a
13//! manual `Duration::from_secs` mapping at every call site.
14
15use std::time::Duration;
16
17use crate::oauth::TokenResponse;
18
19/// Outcome of a successful PAS refresh-token round-trip.
20///
21/// Returned by [`super::RelyingParty::refresh`] on the success path.
22/// Failures (4xx / 5xx / transport) surface as
23/// [`super::RefreshError`] variants.
24///
25/// All fields except `access_token` are `Option` because:
26///
27/// - **`refresh_token`** — PAS may or may not rotate the refresh
28///   credential per RFC 6749 §6 (rotation is implementation-defined).
29///   When `None`, the consumer reuses the existing refresh_token.
30/// - **`id_token`** — refresh-grant id_token return is OIDC Core §12
31///   "MAY", not "MUST". When present, the consumer can rebuild the
32///   `IdAssertion<S>` for sv-axis comparison; when absent, the prior
33///   id_token's claims persist with the access_token rotation.
34/// - **`expires_in`** — RFC 6749 §5.1 makes this OPTIONAL; the consumer
35///   falls back to a sensible default (typically 1h) when PAS omits it.
36#[derive(Debug, Clone)]
37#[non_exhaustive]
38pub struct RefreshOutcome {
39    /// Fresh access_token. Always present on the success path.
40    pub access_token: String,
41    /// Fresh refresh_token, when PAS rotated the credential.
42    pub refresh_token: Option<String>,
43    /// Fresh id_token, when PAS included one in the refresh response.
44    pub id_token: Option<String>,
45    /// Access-token TTL hint, when PAS included `expires_in`.
46    pub expires_in: Option<Duration>,
47}
48
49impl From<TokenResponse> for RefreshOutcome {
50    fn from(t: TokenResponse) -> Self {
51        Self {
52            access_token: t.access_token,
53            refresh_token: t.refresh_token,
54            id_token: t.id_token,
55            expires_in: t.expires_in.map(Duration::from_secs),
56        }
57    }
58}