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}