open_lark/core/trait_system/
service.rs1use crate::core::{config::Config, observability::OperationTracker, SDKResult};
2use std::future::Future;
3
4pub trait Service {
8 fn config(&self) -> &Config;
10
11 fn service_name() -> &'static str
13 where
14 Self: Sized;
15
16 fn service_version() -> &'static str
18 where
19 Self: Sized,
20 {
21 "v1"
22 }
23}
24
25pub trait ServiceObservability: Service {
29 fn log_operation_start(&self, operation: &str, params: Option<&str>)
31 where
32 Self: Sized,
33 {
34 tracing::debug!(
35 service_name = <Self as Service>::service_name(),
36 operation = operation,
37 params = params,
38 "Starting service operation"
39 );
40 }
41
42 fn log_operation_success(&self, operation: &str, duration_ms: u64)
44 where
45 Self: Sized,
46 {
47 tracing::info!(
48 service_name = <Self as Service>::service_name(),
49 operation = operation,
50 duration_ms = duration_ms,
51 "Service operation completed successfully"
52 );
53 }
54
55 fn log_operation_error(&self, operation: &str, error: &str, duration_ms: u64)
57 where
58 Self: Sized,
59 {
60 tracing::error!(
61 service_name = <Self as Service>::service_name(),
62 operation = operation,
63 duration_ms = duration_ms,
64 error = error,
65 "Service operation failed"
66 );
67 }
68}
69
70pub trait AsyncServiceOperation<T, R>: Service
74where
75 T: Send + Sync,
76 R: Send + Sync,
77{
78 fn execute_with_observability<F, Fut>(
80 &self,
81 operation_name: &str,
82 operation: F,
83 ) -> impl Future<Output = SDKResult<R>> + Send
84 where
85 F: FnOnce() -> Fut + Send,
86 Fut: Future<Output = SDKResult<R>> + Send,
87 Self: ServiceObservability + Sync + Sized,
88 {
89 let service_name = <Self as Service>::service_name();
90 async move {
91 let tracker = OperationTracker::start(service_name, operation_name);
92
93 match operation().await {
94 Ok(result) => {
95 tracker.success();
96 Ok(result)
97 }
98 Err(err) => {
99 tracker.error(&err.to_string());
100 Err(err)
101 }
102 }
103 }
104 }
105}
106
107pub trait ServiceBuilder<S: Service> {
111 fn build(config: Config) -> S;
113
114 fn build_default() -> S
116 where
117 Config: Default,
118 {
119 Self::build(Config::default())
120 }
121}
122
123pub trait ServiceHealthCheck: Service {
127 fn health_check(&self) -> impl Future<Output = SDKResult<ServiceHealthStatus>> + Send;
129
130 fn status_summary(&self) -> ServiceStatusSummary
132 where
133 Self: Sized,
134 {
135 ServiceStatusSummary {
136 service_name: <Self as Service>::service_name().to_string(),
137 version: <Self as Service>::service_version().to_string(),
138 config_valid: self.is_config_valid(),
139 }
140 }
141
142 fn is_config_valid(&self) -> bool {
144 let config = self.config();
145 !config.app_id.is_empty() && !config.app_secret.is_empty()
146 }
147}
148
149#[derive(Debug, Clone, PartialEq)]
151pub enum ServiceHealthStatus {
152 Healthy,
154 Degraded(String),
156 Unhealthy(String),
158}
159
160#[derive(Debug, Clone)]
162pub struct ServiceStatusSummary {
163 pub service_name: String,
165 pub version: String,
167 pub config_valid: bool,
169}
170
171pub trait ConfigurableService: Service {
175 fn update_config(&mut self, new_config: Config) -> SDKResult<()>;
177
178 fn validate_config(&self, config: &Config) -> SDKResult<()> {
180 if config.app_id.is_empty() {
181 return Err(crate::core::error::LarkAPIError::IllegalParamError(
182 "app_id cannot be empty".to_string(),
183 ));
184 }
185
186 if config.app_secret.is_empty() {
187 return Err(crate::core::error::LarkAPIError::IllegalParamError(
188 "app_secret cannot be empty".to_string(),
189 ));
190 }
191
192 Ok(())
193 }
194}
195
196pub trait CacheableService: Service {
200 type CacheKey: Send + Sync + std::hash::Hash + Eq + Clone;
202 type CacheValue: Send + Sync + Clone;
204
205 fn get_from_cache(&self, key: &Self::CacheKey) -> Option<Self::CacheValue>;
207
208 fn put_to_cache(&self, key: Self::CacheKey, value: Self::CacheValue);
210
211 fn clear_cache(&self);
213
214 fn cache_ttl(&self) -> u64 {
216 300 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::core::config::Config;
224
225 struct TestService {
227 config: Config,
228 }
229
230 impl Service for TestService {
231 fn config(&self) -> &Config {
232 &self.config
233 }
234
235 fn service_name() -> &'static str {
236 "test_service"
237 }
238
239 fn service_version() -> &'static str {
240 "v1.0"
241 }
242 }
243
244 impl ServiceObservability for TestService {}
245
246 impl ServiceBuilder<TestService> for TestService {
247 fn build(config: Config) -> TestService {
248 TestService { config }
249 }
250 }
251
252 impl AsyncServiceOperation<(), String> for TestService {}
253
254 #[test]
255 fn test_service_creation() {
256 let config = Config::builder()
257 .app_id("test_app")
258 .app_secret("test_secret")
259 .build();
260 let service = TestService::build(config.clone());
261
262 assert_eq!(service.config().app_id, "test_app");
263 assert_eq!(service.config().app_secret, "test_secret");
264 assert_eq!(TestService::service_name(), "test_service");
265 assert_eq!(TestService::service_version(), "v1.0");
266 }
267
268 #[test]
269 fn test_service_status_summary() {
270 let config = Config::builder()
271 .app_id("test_app")
272 .app_secret("test_secret")
273 .build();
274 let service = TestService::build(config);
275
276 struct HealthCheckableTestService {
278 inner: TestService,
279 }
280
281 impl Service for HealthCheckableTestService {
282 fn config(&self) -> &Config {
283 self.inner.config()
284 }
285
286 fn service_name() -> &'static str {
287 TestService::service_name()
288 }
289
290 fn service_version() -> &'static str {
291 TestService::service_version()
292 }
293 }
294
295 impl ServiceHealthCheck for HealthCheckableTestService {
296 async fn health_check(&self) -> SDKResult<ServiceHealthStatus> {
297 Ok(ServiceHealthStatus::Healthy)
298 }
299 }
300
301 let health_service = HealthCheckableTestService { inner: service };
302 let summary = health_service.status_summary();
303
304 assert_eq!(summary.service_name, "test_service");
305 assert_eq!(summary.version, "v1.0");
306 assert!(summary.config_valid);
307 }
308
309 #[test]
310 fn test_config_validation() {
311 let config = Config::builder()
312 .app_id("test_app")
313 .app_secret("test_secret")
314 .build();
315 let service = TestService::build(config);
316
317 struct ConfigurableTestService {
318 inner: TestService,
319 }
320
321 impl Service for ConfigurableTestService {
322 fn config(&self) -> &Config {
323 self.inner.config()
324 }
325
326 fn service_name() -> &'static str {
327 TestService::service_name()
328 }
329 }
330
331 impl ConfigurableService for ConfigurableTestService {
332 fn update_config(&mut self, new_config: Config) -> SDKResult<()> {
333 self.validate_config(&new_config)?;
334 self.inner.config = new_config;
335 Ok(())
336 }
337 }
338
339 let configurable_service = ConfigurableTestService { inner: service };
340
341 let valid_config = Config::builder()
343 .app_id("new_app")
344 .app_secret("new_secret")
345 .build();
346 assert!(configurable_service.validate_config(&valid_config).is_ok());
347
348 let invalid_config = Config::builder().app_id("").app_secret("secret").build();
350 assert!(configurable_service
351 .validate_config(&invalid_config)
352 .is_err());
353
354 let invalid_config = Config::builder().app_id("app").app_secret("").build();
356 assert!(configurable_service
357 .validate_config(&invalid_config)
358 .is_err());
359 }
360
361 #[test]
362 fn test_service_builder_default() {
363 let service = TestService::build_default();
365 assert!(service.config().app_id.is_empty());
367 }
368
369 #[tokio::test]
370 async fn test_async_service_operation() {
371 let config = Config::builder()
372 .app_id("test_app")
373 .app_secret("test_secret")
374 .build();
375 let service = TestService::build(config);
376
377 let result = service
379 .execute_with_observability("test_operation", || async { Ok("success".to_string()) })
380 .await;
381
382 assert!(result.is_ok());
383 assert_eq!(result.unwrap(), "success");
384
385 let result = service
387 .execute_with_observability("test_operation_fail", || async {
388 Err(crate::core::error::LarkAPIError::IllegalParamError(
389 "test error".to_string(),
390 ))
391 })
392 .await;
393
394 assert!(result.is_err());
395 }
396}