oauth2_broker/ext/
rate_limit.rs

1//! Rate limit policy contracts for flows that need to consult provider budgets
2//! before issuing token requests.
3
4// self
5use crate::{
6	_prelude::*,
7	auth::{ProviderId, ScopeSet, TenantId},
8};
9
10/// Boxed future returned by [`RateLimitPolicy::evaluate`].
11pub type RateLimitFuture<'a, Error> =
12	Pin<Box<dyn Future<Output = Result<RateLimitDecision, Error>> + 'a + Send>>;
13
14/// Strategy that inspects tenant/provider budgets before flows hit upstream token endpoints.
15pub trait RateLimitPolicy<Error>
16where
17	Self: Send + Sync,
18{
19	/// Evaluates whether the next call should be delayed.
20	fn evaluate(&self, context: &RateLimitContext) -> RateLimitFuture<'_, Error>;
21}
22
23/// Context shared with a [`RateLimitPolicy`] before an outbound call is made.
24#[derive(Clone, Debug)]
25pub struct RateLimitContext {
26	/// Tenant identifier for the call.
27	pub tenant_id: TenantId,
28	/// Provider identifier for the call.
29	pub provider_id: ProviderId,
30	/// Normalized scope set the broker is about to request.
31	pub scope: ScopeSet,
32	/// Logical operation (grant/flow) being attempted.
33	pub operation: String,
34	/// Timestamp the broker observed before invoking the policy.
35	pub observed_at: OffsetDateTime,
36}
37impl RateLimitContext {
38	/// Creates a new context for the given tenant/provider/scope/operation tuple.
39	pub fn new(
40		tenant_id: TenantId,
41		provider_id: ProviderId,
42		scope: ScopeSet,
43		operation: impl Into<String>,
44	) -> Self {
45		Self {
46			tenant_id,
47			provider_id,
48			scope,
49			operation: operation.into(),
50			observed_at: OffsetDateTime::now_utc(),
51		}
52	}
53
54	/// Overrides the timestamp associated with the observation.
55	pub fn with_observed_at(mut self, instant: OffsetDateTime) -> Self {
56		self.observed_at = instant;
57
58		self
59	}
60}
61
62/// Result emitted by a [`RateLimitPolicy`].
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub enum RateLimitDecision {
65	/// The request may proceed immediately.
66	Allow,
67	/// The request should be delayed.
68	Delay(RetryDirective),
69}
70
71/// Advises callers when to retry after a [`RateLimitDecision::Delay`].
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct RetryDirective {
74	/// Instant when it is safe to retry.
75	pub earliest_retry_at: OffsetDateTime,
76	/// Suggested backoff duration.
77	pub recommended_backoff: Duration,
78	/// Optional descriptive string.
79	pub reason: Option<String>,
80}
81impl RetryDirective {
82	/// Creates a new directive with the provided timing metadata.
83	pub fn new(earliest_retry_at: OffsetDateTime, recommended_backoff: Duration) -> Self {
84		Self { earliest_retry_at, recommended_backoff, reason: None }
85	}
86
87	/// Adds a human-readable reason.
88	pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
89		self.reason = Some(reason.into());
90
91		self
92	}
93}