Skip to main content

openlark_core/auth/
token_provider.rs

1//! Token Provider Trait
2//!
3//! 为 `openlark-core::http::Transport` 提供“只负责注入、不负责获取逻辑”的抽象:
4//! - core 只知道“需要什么 token(App/Tenant/User)”,并把必要上下文(如 app_ticket/tenant_key)交给 provider
5//! - token 获取/刷新/缓存由业务 crate(例如 openlark-auth)实现
6
7use crate::{SDKResult, constants::AccessTokenType};
8use std::{future::Future, pin::Pin};
9
10/// Token 获取请求上下文
11///
12/// 注意:这里不绑定 `Config`,避免 core 持有"获取逻辑",由具体实现自行决定读取哪些配置或状态。
13#[derive(Debug, Clone, Default)]
14pub struct TokenRequest {
15    /// 令牌类型
16    pub token_type: AccessTokenType,
17    /// 租户键
18    pub tenant_key: Option<String>,
19    /// 应用票据
20    pub app_ticket: Option<String>,
21}
22
23impl TokenRequest {
24    /// 创建应用令牌请求
25    pub fn app() -> Self {
26        Self {
27            token_type: AccessTokenType::App,
28            ..Default::default()
29        }
30    }
31
32    /// 创建租户令牌请求
33    pub fn tenant() -> Self {
34        Self {
35            token_type: AccessTokenType::Tenant,
36            ..Default::default()
37        }
38    }
39
40    /// 创建用户令牌请求
41    pub fn user() -> Self {
42        Self {
43            token_type: AccessTokenType::User,
44            ..Default::default()
45        }
46    }
47
48    /// 设置租户键
49    pub fn tenant_key(mut self, tenant_key: impl Into<String>) -> Self {
50        self.tenant_key = Some(tenant_key.into());
51        self
52    }
53
54    /// 设置应用票据
55    pub fn app_ticket(mut self, app_ticket: impl Into<String>) -> Self {
56        self.app_ticket = Some(app_ticket.into());
57        self
58    }
59}
60
61/// Token provider trait for acquiring and refreshing access tokens
62///
63/// This trait allows `Transport` to obtain tokens without knowing
64/// concrete implementation (in-memory cache, distributed cache, custom refresh logic).
65pub trait TokenProvider: Send + Sync + std::fmt::Debug {
66    /// 获取指定类型的 access token
67    fn get_token(
68        &self,
69        request: TokenRequest,
70    ) -> Pin<Box<dyn Future<Output = SDKResult<String>> + Send + '_>>;
71
72    /// 便捷方法:获取 tenant token(可带 tenant_key)
73    fn get_tenant_token(
74        &self,
75        tenant_key: Option<&str>,
76    ) -> Pin<Box<dyn Future<Output = SDKResult<String>> + Send + '_>> {
77        let tenant_key = tenant_key.map(str::to_owned);
78        Box::pin(async move {
79            let mut req = TokenRequest::tenant();
80            if let Some(key) = tenant_key.as_deref()
81                && !key.is_empty()
82            {
83                req = req.tenant_key(key);
84            }
85            self.get_token(req).await
86        })
87    }
88
89    /// Optional: Get app access token explicitly
90    fn get_app_token(&self) -> Pin<Box<dyn Future<Output = SDKResult<String>> + Send + '_>> {
91        Box::pin(async move { self.get_token(TokenRequest::app()).await })
92    }
93
94    /// Optional: Get user access token explicitly
95    fn get_user_token(&self) -> Pin<Box<dyn Future<Output = SDKResult<String>> + Send + '_>> {
96        Box::pin(async move { self.get_token(TokenRequest::user()).await })
97    }
98}
99
100/// Default implementation that does not cache tokens
101///
102/// This is used when caching is disabled or as a fallback.
103#[derive(Debug)]
104pub struct NoOpTokenProvider;
105
106impl TokenProvider for NoOpTokenProvider {
107    fn get_token(
108        &self,
109        request: TokenRequest,
110    ) -> Pin<Box<dyn Future<Output = SDKResult<String>> + Send + '_>> {
111        Box::pin(async move {
112            Err(crate::error::configuration_error(format!(
113                "token_provider: NoOpTokenProvider 未实现 token 获取逻辑(请求:{request:?}),请在 Config 中设置 TokenProvider(建议使用 openlark-auth 提供的实现)。"
114            )))
115        })
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_token_request_app() {
125        let req = TokenRequest::app();
126        assert_eq!(req.token_type, AccessTokenType::App);
127        assert!(req.tenant_key.is_none());
128        assert!(req.app_ticket.is_none());
129    }
130
131    #[test]
132    fn test_token_request_tenant() {
133        let req = TokenRequest::tenant();
134        assert_eq!(req.token_type, AccessTokenType::Tenant);
135    }
136
137    #[test]
138    fn test_token_request_user() {
139        let req = TokenRequest::user();
140        assert_eq!(req.token_type, AccessTokenType::User);
141    }
142
143    #[test]
144    fn test_token_request_with_tenant_key() {
145        let req = TokenRequest::tenant().tenant_key("test_tenant");
146        assert_eq!(req.tenant_key, Some("test_tenant".to_string()));
147    }
148
149    #[test]
150    fn test_token_request_with_app_ticket() {
151        let req = TokenRequest::app().app_ticket("test_ticket");
152        assert_eq!(req.app_ticket, Some("test_ticket".to_string()));
153    }
154
155    #[test]
156    fn test_token_request_default() {
157        let req = TokenRequest::default();
158        assert_eq!(req.token_type, AccessTokenType::None);
159    }
160
161    #[test]
162    fn test_token_request_debug() {
163        let req = TokenRequest::app();
164        let debug_str = format!("{req:?}");
165        assert!(debug_str.contains("TokenRequest"));
166    }
167
168    #[tokio::test]
169    async fn test_no_op_token_provider_returns_error() {
170        let provider = NoOpTokenProvider;
171        let result = provider.get_token(TokenRequest::app()).await;
172        assert!(result.is_err());
173    }
174
175    #[test]
176    fn test_no_op_token_provider_debug() {
177        let provider = NoOpTokenProvider;
178        let debug_str = format!("{provider:?}");
179        assert!(debug_str.contains("NoOpTokenProvider"));
180    }
181}