1use std::fmt::Display;
2
3#[derive(Default, Hash, Eq, PartialEq, Debug, Copy, Clone)]
5pub enum AppType {
6 #[default]
8 SelfBuild,
9 Marketplace,
11}
12
13impl AppType {
14 pub fn as_str(&self) -> &'static str {
16 match self {
17 AppType::SelfBuild => "self_build",
18 AppType::Marketplace => "marketplace",
19 }
20 }
21}
22
23impl Display for AppType {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 write!(f, "{}", self.as_str())
26 }
27}
28
29pub const API_PATH_PREFIX: &str = "/open-apis/";
31pub const APP_ACCESS_TOKEN_INTERNAL_URL_PATH: &str = "/open-apis/auth/v3/app_access_token/internal";
33pub const APP_ACCESS_TOKEN_URL_PATH: &str = "/open-apis/auth/v3/app_access_token";
35pub const TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH: &str =
37 "/open-apis/auth/v3/tenant_access_token/internal";
38pub const TENANT_ACCESS_TOKEN_URL_PATH: &str = "/open-apis/auth/v3/tenant_access_token";
40pub const APPLY_APP_TICKET_PATH: &str = "/open-apis/auth/v3/app_ticket/resend";
42
43#[derive(Default, Hash, Eq, PartialEq, Debug, Copy, Clone)]
44pub enum AccessTokenType {
48 #[default]
50 None,
51 App,
53 Tenant,
55 User,
57}
58
59impl AccessTokenType {
60 pub fn as_str(&self) -> &'static str {
62 match self {
63 AccessTokenType::None => "none_access_token",
64 AccessTokenType::App => "app_access_token",
65 AccessTokenType::Tenant => "tenant_access_token",
66 AccessTokenType::User => "user_access_token",
67 }
68 }
69}
70
71impl Display for AccessTokenType {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(f, "{}", self.as_str())
74 }
75}
76
77pub const PROJECT: &str = "open-lark";
79pub const VERSION: &str = env!("CARGO_PKG_VERSION");
81
82pub const FEISHU_BASE_URL: &str = "https://open.feishu.cn";
84pub const LARK_BASE_URL: &str = "https://open.larksuite.com";
86
87pub const DEFAULT_CONTENT_TYPE: &str = "application/json; charset=utf-8";
89pub const FILE_CONTENT_TYPE: &str = "multipart/form-data";
91pub const USER_AGENT_HEADER: &str = "User-Agent";
93
94pub const HTTP_HEADER_KEY_REQUEST_ID: &str = "X-Request-Id";
96pub const HTTP_HEADER_REQUEST_ID: &str = "Request-Id";
98
99pub const HTTP_HEADER_KEY_LOG_ID: &str = "X-Tt-Logid";
101pub const CONTENT_TYPE_HEADER: &str = "Content-Type";
103pub const CONTENT_TYPE_JSON: &str = "application/json";
105pub const CUSTOM_REQUEST_ID: &str = "Open-Lark-Request-Id";
107pub const APP_TICKET_KEY_PREFIX: &str = "app_ticket";
109pub const APP_ACCESS_TOKEN_KEY_PREFIX: &str = "app_access_token";
111pub const TENANT_ACCESS_TOKEN_KEY_PREFIX: &str = "tenant_access_token";
113pub const EXPIRY_DELTA: i32 = 60 * 3;
115pub const ERR_CODE_APP_TICKET_INVALID: i32 = 10012;
117pub const ERR_CODE_ACCESS_TOKEN_INVALID: i32 = 99991671;
119pub const ERR_CODE_APP_ACCESS_TOKEN_INVALID: i32 = 99991664;
121pub const ERR_CODE_TENANT_ACCESS_TOKEN_INVALID: i32 = 99991663;
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_app_type_default() {
130 assert_eq!(AppType::default(), AppType::SelfBuild);
131 }
132
133 #[test]
134 fn test_app_type_debug_clone() {
135 let app_type = AppType::Marketplace;
136 let cloned = app_type;
137 assert_eq!(format!("{:?}", app_type), "Marketplace");
138 assert_eq!(cloned, AppType::Marketplace);
139 }
140
141 #[test]
142 fn test_app_type_equality() {
143 assert_eq!(AppType::SelfBuild, AppType::SelfBuild);
144 assert_eq!(AppType::Marketplace, AppType::Marketplace);
145 assert_ne!(AppType::SelfBuild, AppType::Marketplace);
146 }
147
148 #[test]
149 fn test_access_token_type_default() {
150 assert_eq!(AccessTokenType::default(), AccessTokenType::None);
151 }
152
153 #[test]
154 fn test_access_token_type_display() {
155 assert_eq!(AccessTokenType::None.to_string(), "none_access_token");
156 assert_eq!(AccessTokenType::App.to_string(), "app_access_token");
157 assert_eq!(AccessTokenType::Tenant.to_string(), "tenant_access_token");
158 assert_eq!(AccessTokenType::User.to_string(), "user_access_token");
159 }
160
161 #[test]
162 fn test_access_token_type_debug_clone() {
163 let token_type = AccessTokenType::App;
164 let cloned = token_type;
165 assert_eq!(format!("{:?}", token_type), "App");
166 assert_eq!(cloned, AccessTokenType::App);
167 }
168
169 #[test]
170 fn test_access_token_type_equality() {
171 assert_eq!(AccessTokenType::None, AccessTokenType::None);
172 assert_eq!(AccessTokenType::App, AccessTokenType::App);
173 assert_eq!(AccessTokenType::Tenant, AccessTokenType::Tenant);
174 assert_eq!(AccessTokenType::User, AccessTokenType::User);
175 assert_ne!(AccessTokenType::App, AccessTokenType::Tenant);
176 }
177
178 #[test]
179 fn test_constants_values() {
180 assert_eq!(
182 APP_ACCESS_TOKEN_INTERNAL_URL_PATH,
183 "/open-apis/auth/v3/app_access_token/internal"
184 );
185 assert_eq!(
186 APP_ACCESS_TOKEN_URL_PATH,
187 "/open-apis/auth/v3/app_access_token"
188 );
189 assert_eq!(
190 TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH,
191 "/open-apis/auth/v3/tenant_access_token/internal"
192 );
193 assert_eq!(
194 TENANT_ACCESS_TOKEN_URL_PATH,
195 "/open-apis/auth/v3/tenant_access_token"
196 );
197 assert_eq!(
198 APPLY_APP_TICKET_PATH,
199 "/open-apis/auth/v3/app_ticket/resend"
200 );
201
202 assert_eq!(PROJECT, "open-lark");
204 assert!(
206 VERSION.contains('.'),
207 "Version should contain dots for proper semver"
208 );
209
210 assert_eq!(FEISHU_BASE_URL, "https://open.feishu.cn");
212 assert_eq!(LARK_BASE_URL, "https://open.larksuite.com");
213
214 assert_eq!(DEFAULT_CONTENT_TYPE, "application/json; charset=utf-8");
216 assert_eq!(FILE_CONTENT_TYPE, "multipart/form-data");
217 assert_eq!(CONTENT_TYPE_JSON, "application/json");
218
219 assert_eq!(USER_AGENT_HEADER, "User-Agent");
221 assert_eq!(HTTP_HEADER_KEY_REQUEST_ID, "X-Request-Id");
222 assert_eq!(HTTP_HEADER_REQUEST_ID, "Request-Id");
223 assert_eq!(HTTP_HEADER_KEY_LOG_ID, "X-Tt-Logid");
224 assert_eq!(CONTENT_TYPE_HEADER, "Content-Type");
225 assert_eq!(CUSTOM_REQUEST_ID, "Open-Lark-Request-Id");
226
227 assert_eq!(APP_TICKET_KEY_PREFIX, "app_ticket");
229 assert_eq!(APP_ACCESS_TOKEN_KEY_PREFIX, "app_access_token");
230 assert_eq!(TENANT_ACCESS_TOKEN_KEY_PREFIX, "tenant_access_token");
231
232 assert_eq!(EXPIRY_DELTA, 60 * 3);
234 assert_eq!(ERR_CODE_APP_TICKET_INVALID, 10012);
235 assert_eq!(ERR_CODE_ACCESS_TOKEN_INVALID, 99991671);
236 assert_eq!(ERR_CODE_APP_ACCESS_TOKEN_INVALID, 99991664);
237 assert_eq!(ERR_CODE_TENANT_ACCESS_TOKEN_INVALID, 99991663);
238 }
239
240 #[test]
241 fn test_version_format() {
242 let version_parts: Vec<&str> = VERSION.split('.').collect();
244 assert!(
245 version_parts.len() >= 2,
246 "Version should have at least major.minor format"
247 );
248
249 for part in &version_parts {
251 assert!(!part.is_empty(), "Version parts should not be empty");
252 assert!(
254 part.chars().next().unwrap().is_ascii_digit(),
255 "Version should start with digit"
256 );
257 }
258 }
259
260 #[test]
261 fn test_url_constants_format() {
262 assert!(FEISHU_BASE_URL.starts_with("https://"));
264 assert!(LARK_BASE_URL.starts_with("https://"));
265
266 assert!(APP_ACCESS_TOKEN_INTERNAL_URL_PATH.starts_with("/open-apis/"));
268 assert!(APP_ACCESS_TOKEN_URL_PATH.starts_with("/open-apis/"));
269 assert!(TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH.starts_with("/open-apis/"));
270 assert!(TENANT_ACCESS_TOKEN_URL_PATH.starts_with("/open-apis/"));
271 assert!(APPLY_APP_TICKET_PATH.starts_with("/open-apis/"));
272 }
273
274 #[test]
275 fn test_error_code_ranges() {
276 let error_codes = [
282 ERR_CODE_APP_TICKET_INVALID,
283 ERR_CODE_ACCESS_TOKEN_INVALID,
284 ERR_CODE_APP_ACCESS_TOKEN_INVALID,
285 ERR_CODE_TENANT_ACCESS_TOKEN_INVALID,
286 ];
287
288 for (i, &code1) in error_codes.iter().enumerate() {
289 for &code2 in error_codes.iter().skip(i + 1) {
290 assert_ne!(code1, code2, "Error codes should be unique");
291 }
292 }
293 }
294
295 #[test]
296 fn test_expiry_delta_reasonable() {
297 assert_eq!(EXPIRY_DELTA, 180);
299 }
302
303 #[test]
304 fn test_content_type_formats() {
305 assert!(DEFAULT_CONTENT_TYPE.contains("application/json"));
306 assert!(DEFAULT_CONTENT_TYPE.contains("charset=utf-8"));
307 assert!(FILE_CONTENT_TYPE.contains("multipart/form-data"));
308 assert_eq!(CONTENT_TYPE_JSON, "application/json");
309 }
310}