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::{constants::AccessTokenType, SDKResult};
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                if !key.is_empty() {
82                    req = req.tenant_key(key);
83                }
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(
113                format!(
114                    "token_provider: NoOpTokenProvider 未实现 token 获取逻辑(请求:{:?}),请在 Config 中设置 TokenProvider(建议使用 openlark-auth 提供的实现)。",
115                    request
116                ),
117            ))
118        })
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_token_request_app() {
128        let req = TokenRequest::app();
129        assert_eq!(req.token_type, AccessTokenType::App);
130        assert!(req.tenant_key.is_none());
131        assert!(req.app_ticket.is_none());
132    }
133
134    #[test]
135    fn test_token_request_tenant() {
136        let req = TokenRequest::tenant();
137        assert_eq!(req.token_type, AccessTokenType::Tenant);
138    }
139
140    #[test]
141    fn test_token_request_user() {
142        let req = TokenRequest::user();
143        assert_eq!(req.token_type, AccessTokenType::User);
144    }
145
146    #[test]
147    fn test_token_request_with_tenant_key() {
148        let req = TokenRequest::tenant().tenant_key("test_tenant");
149        assert_eq!(req.tenant_key, Some("test_tenant".to_string()));
150    }
151
152    #[test]
153    fn test_token_request_with_app_ticket() {
154        let req = TokenRequest::app().app_ticket("test_ticket");
155        assert_eq!(req.app_ticket, Some("test_ticket".to_string()));
156    }
157
158    #[test]
159    fn test_token_request_default() {
160        let req = TokenRequest::default();
161        assert_eq!(req.token_type, AccessTokenType::None);
162    }
163
164    #[test]
165    fn test_token_request_debug() {
166        let req = TokenRequest::app();
167        let debug_str = format!("{:?}", req);
168        assert!(debug_str.contains("TokenRequest"));
169    }
170
171    #[tokio::test]
172    async fn test_no_op_token_provider_returns_error() {
173        let provider = NoOpTokenProvider;
174        let result = provider.get_token(TokenRequest::app()).await;
175        assert!(result.is_err());
176    }
177
178    #[test]
179    fn test_no_op_token_provider_debug() {
180        let provider = NoOpTokenProvider;
181        let debug_str = format!("{:?}", provider);
182        assert!(debug_str.contains("NoOpTokenProvider"));
183    }
184}