Skip to main content

orion_error/core/
context.rs

1#[cfg(all(feature = "log", not(feature = "tracing")))]
2use log::{debug, error, info, trace, warn};
3use std::{
4    fmt::Display,
5    ops::{Deref, DerefMut},
6    path::{Path, PathBuf},
7};
8#[derive(Debug, Clone, PartialEq, Default)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum OperationResult {
11    Suc,
12    #[default]
13    Fail,
14    Cancel,
15}
16
17// 使用编译期模块路径作为默认日志 target,以提升可读性
18const DEFAULT_MOD_PATH: &str = module_path!();
19
20/// 在调用处展开 `module_path!()`,便于自动日志输出正确的模块路径。
21#[macro_export]
22macro_rules! op_context {
23    ($target:expr) => {
24        $crate::OperationContext::want($target).with_mod_path(module_path!())
25    };
26}
27
28#[derive(Debug, Clone, PartialEq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct OperationContext {
31    context: CallContext,
32    result: OperationResult,
33    exit_log: bool,
34    mod_path: String,
35    target: Option<String>,
36}
37impl Default for OperationContext {
38    fn default() -> Self {
39        Self {
40            context: CallContext::default(),
41            target: None,
42            result: OperationResult::Fail,
43            exit_log: false,
44            mod_path: DEFAULT_MOD_PATH.into(),
45        }
46    }
47}
48pub type WithContext = OperationContext;
49impl From<CallContext> for OperationContext {
50    fn from(value: CallContext) -> Self {
51        OperationContext {
52            context: value,
53            result: OperationResult::Fail,
54            target: None,
55            exit_log: false,
56            mod_path: DEFAULT_MOD_PATH.into(),
57        }
58    }
59}
60
61impl Drop for OperationContext {
62    fn drop(&mut self) {
63        if !self.exit_log {
64            return;
65        }
66
67        #[cfg(feature = "tracing")]
68        {
69            let ctx = self.format_context();
70            match self.result() {
71                OperationResult::Suc => {
72                    tracing::info!(
73                        target: "domain",
74                        mod_path = %self.mod_path,
75                        "suc! {ctx}"
76                    )
77                }
78                OperationResult::Fail => {
79                    tracing::error!(
80                        target: "domain",
81                        mod_path = %self.mod_path,
82                        "fail! {ctx}"
83                    )
84                }
85                OperationResult::Cancel => {
86                    tracing::warn!(
87                        target: "domain",
88                        mod_path = %self.mod_path,
89                        "cancel! {ctx}"
90                    )
91                }
92            }
93        }
94
95        #[cfg(all(feature = "log", not(feature = "tracing")))]
96        {
97            match self.result() {
98                OperationResult::Suc => {
99                    info!(target: self.mod_path.as_str(), "suc! {}", self.format_context());
100                }
101                OperationResult::Fail => {
102                    error!(target: self.mod_path.as_str(), "fail! {}", self.format_context());
103                }
104                OperationResult::Cancel => {
105                    warn!(target: self.mod_path.as_str(), "cancel! {}", self.format_context());
106                }
107            }
108        }
109    }
110}
111
112impl Display for OperationContext {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        if let Some(target) = &self.target {
115            writeln!(f, "target: {target} ")?;
116        }
117        for (i, (k, v)) in self.context().items.iter().enumerate() {
118            writeln!(f, "{}. {k}: {v} ", i + 1)?;
119        }
120        Ok(())
121    }
122}
123pub trait ContextRecord<S1, S2> {
124    fn record(&mut self, key: S1, val: S2);
125}
126
127impl<S1> ContextRecord<S1, String> for OperationContext
128where
129    S1: Into<String>,
130{
131    fn record(&mut self, key: S1, val: String) {
132        self.context.items.push((key.into(), val));
133    }
134}
135
136impl<S1> ContextRecord<S1, &str> for OperationContext
137where
138    S1: Into<String>,
139{
140    fn record(&mut self, key: S1, val: &str) {
141        self.context.items.push((key.into(), val.into()));
142    }
143}
144
145// Wrapper type for path values to avoid conflicts
146
147impl<S1> ContextRecord<S1, &PathBuf> for OperationContext
148where
149    S1: Into<String>,
150{
151    fn record(&mut self, key: S1, val: &PathBuf) {
152        self.context
153            .items
154            .push((key.into(), format!("{}", val.display())));
155    }
156}
157impl<S1> ContextRecord<S1, &Path> for OperationContext
158where
159    S1: Into<String>,
160{
161    fn record(&mut self, key: S1, val: &Path) {
162        self.context
163            .items
164            .push((key.into(), format!("{}", val.display())));
165    }
166}
167
168impl OperationContext {
169    pub fn context(&self) -> &CallContext {
170        &self.context
171    }
172
173    pub fn result(&self) -> &OperationResult {
174        &self.result
175    }
176
177    pub fn exit_log(&self) -> &bool {
178        &self.exit_log
179    }
180
181    pub fn mod_path(&self) -> &String {
182        &self.mod_path
183    }
184
185    pub fn target(&self) -> &Option<String> {
186        &self.target
187    }
188
189    pub fn new() -> Self {
190        Self {
191            target: None,
192            context: CallContext::default(),
193            result: OperationResult::Fail,
194            exit_log: false,
195            mod_path: DEFAULT_MOD_PATH.into(),
196        }
197    }
198    pub fn want<S: Into<String>>(target: S) -> Self {
199        Self {
200            target: Some(target.into()),
201            context: CallContext::default(),
202            result: OperationResult::Fail,
203            exit_log: false,
204            mod_path: DEFAULT_MOD_PATH.into(),
205        }
206    }
207    #[deprecated(since = "0.5.4", note = "use with_auto_log")]
208    pub fn with_exit_log(mut self) -> Self {
209        self.exit_log = true;
210        self
211    }
212    pub fn with_auto_log(mut self) -> Self {
213        self.exit_log = true;
214        self
215    }
216    pub fn with_mod_path<S: Into<String>>(mut self, path: S) -> Self {
217        self.mod_path = path.into();
218        self
219    }
220    #[deprecated(since = "0.5.4", note = "use record")]
221    pub fn with<S1: Into<String>, S2: Into<String>>(&mut self, key: S1, val: S2) {
222        self.context.items.push((key.into(), val.into()));
223    }
224
225    #[deprecated(since = "0.5.4", note = "use record")]
226    pub fn with_path<S1: Into<String>, S2: Into<PathBuf>>(&mut self, key: S1, val: S2) {
227        self.context
228            .items
229            .push((key.into(), format!("{}", val.into().display())));
230    }
231
232    pub fn with_want<S: Into<String>>(&mut self, target: S) {
233        self.target = Some(target.into())
234    }
235    /// 别名:设置目标资源/操作名,与 `with_want` 等效
236    pub fn set_target<S: Into<String>>(&mut self, target: S) {
237        self.with_want(target)
238    }
239    pub fn mark_suc(&mut self) {
240        self.result = OperationResult::Suc;
241    }
242    pub fn mark_cancel(&mut self) {
243        self.result = OperationResult::Cancel;
244    }
245
246    /// 格式化上下文信息,用于日志输出
247    #[cfg_attr(not(any(feature = "log", feature = "tracing")), allow(dead_code))]
248    fn format_context(&self) -> String {
249        let target = self.target.clone().unwrap_or_default();
250        if self.context.items.is_empty() {
251            return target;
252        }
253        if target.is_empty() {
254            let body = self.context.to_string();
255            body.strip_prefix('\n').unwrap_or(&body).to_string()
256        } else {
257            format!("{target}: {}", self.context)
258        }
259    }
260
261    /// 创建作用域 guard,默认为失败状态,需显式 `mark_success()`
262    pub fn scope(&mut self) -> OperationScope<'_> {
263        OperationScope {
264            ctx: self,
265            mark_success: false,
266        }
267    }
268
269    /// 创建作用域 guard,在作用域结束时自动标记成功
270    pub fn scoped_success(&mut self) -> OperationScope<'_> {
271        OperationScope {
272            ctx: self,
273            mark_success: true,
274        }
275    }
276
277    /// 记录日志信息,在无错误情况下也可以提供有价值的上下文信息
278    /// 注意:需要启用 `log` 或 `tracing` 特性
279    #[cfg(feature = "tracing")]
280    pub fn info<S: AsRef<str>>(&self, message: S) {
281        tracing::info!(
282            target: "domain",
283            mod_path = %self.mod_path,
284            "{}: {}",
285            self.format_context(),
286            message.as_ref()
287        );
288    }
289    #[cfg(all(feature = "log", not(feature = "tracing")))]
290    pub fn info<S: AsRef<str>>(&self, message: S) {
291        info!(target: self.mod_path.as_str(), "{}: {}", self.format_context(), message.as_ref());
292    }
293    #[cfg(not(any(feature = "log", feature = "tracing")))]
294    pub fn info<S: AsRef<str>>(&self, _message: S) {}
295
296    #[cfg(feature = "tracing")]
297    pub fn debug<S: AsRef<str>>(&self, message: S) {
298        tracing::debug!(
299            target: "domain",
300            mod_path = %self.mod_path,
301            "{}: {}",
302            self.format_context(),
303            message.as_ref()
304        );
305    }
306    #[cfg(all(feature = "log", not(feature = "tracing")))]
307    pub fn debug<S: AsRef<str>>(&self, message: S) {
308        debug!( target: self.mod_path.as_str(), "{}: {}", self.format_context(), message.as_ref());
309    }
310    #[cfg(not(any(feature = "log", feature = "tracing")))]
311    pub fn debug<S: AsRef<str>>(&self, _message: S) {}
312
313    #[cfg(feature = "tracing")]
314    pub fn warn<S: AsRef<str>>(&self, message: S) {
315        tracing::warn!(
316            target: "domain",
317            mod_path = %self.mod_path,
318            "{}: {}",
319            self.format_context(),
320            message.as_ref()
321        );
322    }
323    #[cfg(all(feature = "log", not(feature = "tracing")))]
324    pub fn warn<S: AsRef<str>>(&self, message: S) {
325        warn!( target: self.mod_path.as_str(), "{}: {}", self.format_context(), message.as_ref());
326    }
327    #[cfg(not(any(feature = "log", feature = "tracing")))]
328    pub fn warn<S: AsRef<str>>(&self, _message: S) {}
329
330    #[cfg(feature = "tracing")]
331    pub fn error<S: AsRef<str>>(&self, message: S) {
332        tracing::error!(
333            target: "domain",
334            mod_path = %self.mod_path,
335            "{}: {}",
336            self.format_context(),
337            message.as_ref()
338        );
339    }
340    #[cfg(all(feature = "log", not(feature = "tracing")))]
341    pub fn error<S: AsRef<str>>(&self, message: S) {
342        error!(target: self.mod_path.as_str(), "{}: {}", self.format_context(), message.as_ref());
343    }
344    #[cfg(not(any(feature = "log", feature = "tracing")))]
345    pub fn error<S: AsRef<str>>(&self, _message: S) {}
346
347    #[cfg(feature = "tracing")]
348    pub fn trace<S: AsRef<str>>(&self, message: S) {
349        tracing::trace!(
350            target: "domain",
351            mod_path = %self.mod_path,
352            "{}: {}",
353            self.format_context(),
354            message.as_ref()
355        );
356    }
357    #[cfg(all(feature = "log", not(feature = "tracing")))]
358    pub fn trace<S: AsRef<str>>(&self, message: S) {
359        trace!( target: self.mod_path.as_str(), "{}: {}", self.format_context(), message.as_ref());
360    }
361    #[cfg(not(any(feature = "log", feature = "tracing")))]
362    pub fn trace<S: AsRef<str>>(&self, _message: S) {}
363
364    /// 与文档示例一致的别名方法(调用上面的同名方法)
365    pub fn log_info<S: AsRef<str>>(&self, message: S) {
366        self.info(message)
367    }
368    pub fn log_debug<S: AsRef<str>>(&self, message: S) {
369        self.debug(message)
370    }
371    pub fn log_warn<S: AsRef<str>>(&self, message: S) {
372        self.warn(message)
373    }
374    pub fn log_error<S: AsRef<str>>(&self, message: S) {
375        self.error(message)
376    }
377    pub fn log_trace<S: AsRef<str>>(&self, message: S) {
378        self.trace(message)
379    }
380}
381
382pub struct OperationScope<'a> {
383    ctx: &'a mut OperationContext,
384    mark_success: bool,
385}
386
387impl<'a> OperationScope<'a> {
388    /// 显式标记成功
389    pub fn mark_success(&mut self) {
390        self.mark_success = true;
391    }
392
393    /// 保持失败状态(默认行为)
394    pub fn mark_failure(&mut self) {
395        self.mark_success = false;
396    }
397
398    /// 标记为取消并阻止成功写入
399    pub fn cancel(&mut self) {
400        self.ctx.mark_cancel();
401        self.mark_success = false;
402    }
403}
404
405impl<'a> Deref for OperationScope<'a> {
406    type Target = OperationContext;
407
408    fn deref(&self) -> &Self::Target {
409        self.ctx
410    }
411}
412
413impl<'a> DerefMut for OperationScope<'a> {
414    fn deref_mut(&mut self) -> &mut Self::Target {
415        self.ctx
416    }
417}
418
419impl Drop for OperationScope<'_> {
420    fn drop(&mut self) {
421        if self.mark_success {
422            self.ctx.mark_suc();
423        }
424    }
425}
426
427impl From<String> for OperationContext {
428    fn from(value: String) -> Self {
429        Self {
430            target: None,
431            context: CallContext::from(("key", value.to_string())),
432            result: OperationResult::Fail,
433            exit_log: false,
434            mod_path: DEFAULT_MOD_PATH.into(),
435        }
436    }
437}
438
439impl From<&PathBuf> for OperationContext {
440    fn from(value: &PathBuf) -> Self {
441        Self {
442            target: None,
443            context: CallContext::from(("path", format!("{}", value.display()))),
444            result: OperationResult::Fail,
445            exit_log: false,
446            mod_path: DEFAULT_MOD_PATH.into(),
447        }
448    }
449}
450
451impl From<&Path> for OperationContext {
452    fn from(value: &Path) -> Self {
453        Self {
454            target: None,
455            context: CallContext::from(("path", format!("{}", value.display()))),
456            result: OperationResult::Fail,
457            exit_log: false,
458            mod_path: DEFAULT_MOD_PATH.into(),
459        }
460    }
461}
462
463impl From<&str> for OperationContext {
464    fn from(value: &str) -> Self {
465        Self {
466            target: None,
467            context: CallContext::from(("key", value.to_string())),
468            result: OperationResult::Fail,
469            exit_log: false,
470            mod_path: DEFAULT_MOD_PATH.into(),
471        }
472    }
473}
474
475impl From<(&str, &str)> for OperationContext {
476    fn from(value: (&str, &str)) -> Self {
477        Self {
478            target: None,
479            context: CallContext::from((value.0, value.1)),
480            result: OperationResult::Fail,
481            exit_log: false,
482            mod_path: DEFAULT_MOD_PATH.into(),
483        }
484    }
485}
486
487impl From<(&str, String)> for OperationContext {
488    fn from(value: (&str, String)) -> Self {
489        Self {
490            target: None,
491            context: CallContext::from((value.0, value.1)),
492            result: OperationResult::Fail,
493            exit_log: false,
494            mod_path: DEFAULT_MOD_PATH.into(),
495        }
496    }
497}
498// Marker trait to exclude types that are already covered by other implementations
499trait NotAsRefStr: AsRef<Path> {}
500
501// Implement for concrete path types but not for &str
502impl NotAsRefStr for PathBuf {}
503impl NotAsRefStr for Path {}
504impl<T: AsRef<Path> + ?Sized> NotAsRefStr for &T where T: NotAsRefStr {}
505
506impl<V: AsRef<Path>> From<(&str, V)> for OperationContext
507where
508    V: NotAsRefStr,
509{
510    fn from(value: (&str, V)) -> Self {
511        Self {
512            target: None,
513            context: CallContext {
514                items: vec![(
515                    value.0.to_string(),
516                    format!("{}", value.1.as_ref().display()),
517                )],
518            },
519            result: OperationResult::Fail,
520            exit_log: false,
521            mod_path: DEFAULT_MOD_PATH.into(),
522        }
523    }
524}
525
526impl From<(String, String)> for OperationContext {
527    fn from(value: (String, String)) -> Self {
528        Self {
529            target: None,
530            context: CallContext::from((value.0, value.1)),
531            result: OperationResult::Fail,
532            exit_log: false,
533            mod_path: DEFAULT_MOD_PATH.into(),
534        }
535    }
536}
537
538impl From<&OperationContext> for OperationContext {
539    fn from(value: &OperationContext) -> Self {
540        value.clone()
541    }
542}
543
544#[derive(Default, Debug, Clone, PartialEq)]
545#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
546pub struct CallContext {
547    pub items: Vec<(String, String)>,
548}
549
550impl<K: AsRef<str>, V: AsRef<str>> From<(K, V)> for CallContext {
551    fn from(value: (K, V)) -> Self {
552        Self {
553            items: vec![(value.0.as_ref().to_string(), value.1.as_ref().to_string())],
554        }
555    }
556}
557
558pub trait ContextAdd<T> {
559    fn add_context(&mut self, val: T);
560}
561
562impl<K: Into<String>> ContextAdd<(K, String)> for OperationContext {
563    fn add_context(&mut self, val: (K, String)) {
564        self.record(val.0.into(), val.1);
565    }
566}
567impl<K: Into<String>> ContextAdd<(K, &String)> for OperationContext {
568    fn add_context(&mut self, val: (K, &String)) {
569        self.record(val.0.into(), val.1.clone());
570    }
571}
572impl<K: Into<String>> ContextAdd<(K, &str)> for OperationContext {
573    fn add_context(&mut self, val: (K, &str)) {
574        self.record(val.0.into(), val.1.to_string());
575    }
576}
577
578impl<K: Into<String>> ContextAdd<(K, &PathBuf)> for OperationContext {
579    fn add_context(&mut self, val: (K, &PathBuf)) {
580        self.record(val.0.into(), format!("{}", val.1.display()));
581    }
582}
583impl<K: Into<String>> ContextAdd<(K, &Path)> for OperationContext {
584    fn add_context(&mut self, val: (K, &Path)) {
585        self.record(val.0.into(), format!("{}", val.1.display()));
586    }
587}
588
589impl Display for CallContext {
590    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
591        if !self.items.is_empty() {
592            writeln!(f, "\ncall context:")?;
593        }
594        for (k, v) in &self.items {
595            writeln!(f, "\t{k} : {v}")?;
596        }
597        Ok(())
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604    use std::path::PathBuf;
605
606    #[test]
607    fn test_op_context_macro_sets_callsite_mod_path() {
608        let ctx = crate::op_context!("macro_target");
609        assert_eq!(*ctx.target(), Some("macro_target".to_string()));
610        assert_eq!(ctx.mod_path().as_str(), module_path!());
611    }
612
613    #[test]
614    fn test_withcontext_new() {
615        let ctx = OperationContext::new();
616        assert!(ctx.target.is_none());
617        assert_eq!(ctx.context().items.len(), 0);
618    }
619
620    #[test]
621    fn test_withcontext_want() {
622        let ctx = OperationContext::want("test_target");
623        assert_eq!(*ctx.target(), Some("test_target".to_string()));
624        assert_eq!(ctx.context().items.len(), 0);
625    }
626
627    #[test]
628    fn test_withcontext_with() {
629        let mut ctx = OperationContext::new();
630        ctx.record("key1", "value1");
631        ctx.record("key2", "value2");
632
633        assert_eq!(ctx.context().items.len(), 2);
634        assert_eq!(
635            ctx.context().items[0],
636            ("key1".to_string(), "value1".to_string())
637        );
638        assert_eq!(
639            ctx.context().items[1],
640            ("key2".to_string(), "value2".to_string())
641        );
642    }
643
644    #[test]
645    fn test_withcontext_with_path() {
646        let mut ctx = OperationContext::new();
647        let path = PathBuf::from("/test/path");
648        ctx.record("file_path", &path);
649
650        assert_eq!(ctx.context().items.len(), 1);
651        assert!(ctx.context().items[0].1.contains("/test/path"));
652    }
653
654    #[test]
655    fn test_withcontext_with_want() {
656        let mut ctx = OperationContext::new();
657        ctx.with_want("new_target");
658
659        assert_eq!(*ctx.target(), Some("new_target".to_string()));
660    }
661
662    #[test]
663    fn test_errcontext_from_string() {
664        let ctx = CallContext::from(("key".to_string(), "test_string".to_string()));
665        assert_eq!(ctx.items.len(), 1);
666        assert_eq!(ctx.items[0], ("key".to_string(), "test_string".to_string()));
667    }
668
669    #[test]
670    fn test_errcontext_from_str() {
671        let ctx = CallContext::from(("key", "test_str"));
672        assert_eq!(ctx.items.len(), 1);
673        assert_eq!(ctx.items[0], ("key".to_string(), "test_str".to_string()));
674    }
675
676    #[test]
677    fn test_errcontext_from_string_pair() {
678        let ctx = CallContext::from(("key1".to_string(), "value1".to_string()));
679        assert_eq!(ctx.items.len(), 1);
680        assert_eq!(ctx.items[0], ("key1".to_string(), "value1".to_string()));
681    }
682
683    #[test]
684    fn test_errcontext_from_str_pair() {
685        let ctx = CallContext::from(("key1", "value1"));
686        assert_eq!(ctx.items.len(), 1);
687        assert_eq!(ctx.items[0], ("key1".to_string(), "value1".to_string()));
688    }
689
690    #[test]
691    fn test_errcontext_from_mixed_pair() {
692        let ctx = CallContext::from(("key1", "value1".to_string()));
693        assert_eq!(ctx.items.len(), 1);
694        assert_eq!(ctx.items[0], ("key1".to_string(), "value1".to_string()));
695    }
696
697    #[test]
698    fn test_errcontext_default() {
699        let ctx = CallContext::default();
700        assert_eq!(ctx.items.len(), 0);
701    }
702
703    #[test]
704    fn test_errcontext_display_single() {
705        let ctx = CallContext::from(("key", "test"));
706        let display = format!("{ctx}");
707        assert!(display.contains("call context:"));
708        assert!(display.contains("key : test"));
709    }
710
711    #[test]
712    fn test_errcontext_display_multiple() {
713        let mut ctx = CallContext::default();
714        ctx.items.push(("key1".to_string(), "value1".to_string()));
715        ctx.items.push(("key2".to_string(), "value2".to_string()));
716        let display = format!("{ctx}");
717        assert!(display.contains("call context:"));
718        assert!(display.contains("key1 : value1"));
719        assert!(display.contains("key2 : value2"));
720    }
721
722    #[test]
723    fn test_errcontext_display_empty() {
724        let ctx = CallContext::default();
725        let display = format!("{ctx}");
726        assert_eq!(display, "");
727    }
728
729    #[test]
730    fn test_withcontext_from_string() {
731        let ctx = OperationContext::from("test_string".to_string());
732        assert!(ctx.target.is_none());
733        assert_eq!(ctx.context().items.len(), 1);
734        assert_eq!(
735            ctx.context().items[0],
736            ("key".to_string(), "test_string".to_string())
737        );
738    }
739
740    #[test]
741    fn test_withcontext_from_str() {
742        let ctx = OperationContext::from("test_str".to_string());
743        assert!(ctx.target.is_none());
744        assert_eq!(ctx.context().items.len(), 1);
745        assert_eq!(
746            ctx.context().items[0],
747            ("key".to_string(), "test_str".to_string())
748        );
749    }
750
751    #[test]
752    fn test_withcontext_from_pathbuf() {
753        let path = PathBuf::from("/test/path");
754        let ctx = OperationContext::from(&path);
755        assert!(ctx.target.is_none());
756        assert_eq!(ctx.context().items.len(), 1);
757        assert!(ctx.context().items[0].1.contains("/test/path"));
758    }
759
760    #[test]
761    fn test_withcontext_from_path() {
762        let path = "/test/path";
763        let ctx = OperationContext::from(path);
764        assert!(ctx.target.is_none());
765        assert_eq!(ctx.context().items.len(), 1);
766        assert!(ctx.context().items[0].1.contains("/test/path"));
767    }
768
769    #[test]
770    fn test_withcontext_from_string_pair() {
771        let ctx = OperationContext::from(("key1".to_string(), "value1".to_string()));
772        assert!(ctx.target.is_none());
773        assert_eq!(ctx.context().items.len(), 1);
774        assert_eq!(
775            ctx.context().items[0],
776            ("key1".to_string(), "value1".to_string())
777        );
778    }
779
780    #[test]
781    fn test_withcontext_from_str_pair() {
782        let ctx = OperationContext::from(("key1", "value1"));
783        assert!(ctx.target.is_none());
784        assert_eq!(ctx.context().items.len(), 1);
785        assert_eq!(
786            ctx.context().items[0],
787            ("key1".to_string(), "value1".to_string())
788        );
789    }
790
791    #[test]
792    fn test_withcontext_from_mixed_pair() {
793        let ctx = OperationContext::from(("key1", "value1".to_string()));
794        assert!(ctx.target.is_none());
795        assert_eq!(ctx.context().items.len(), 1);
796        assert_eq!(
797            ctx.context().items[0],
798            ("key1".to_string(), "value1".to_string())
799        );
800    }
801
802    #[test]
803    fn test_withcontext_from_path_pair() {
804        let path = PathBuf::from("/test/path");
805        let ctx = OperationContext::from(("file", path.to_string_lossy().as_ref()));
806        assert!(ctx.target.is_none());
807        assert_eq!(ctx.context().items.len(), 1);
808        assert!(ctx.context().items[0].0.contains("file"));
809        assert!(ctx.context().items[0].1.contains("/test/path"));
810    }
811
812    #[test]
813    fn test_withcontext_display_with_target() {
814        let mut ctx = OperationContext::want("test_target");
815        ctx.record("key1", "value1");
816        let display = format!("{ctx}");
817        assert!(display.contains("target: test_target"));
818        assert!(display.contains("1. key1: value1"));
819    }
820
821    #[test]
822    fn test_withcontext_display_without_target() {
823        let mut ctx = OperationContext::new();
824        ctx.record("key1", "value1");
825        let display = format!("{ctx}");
826        assert!(!display.contains("target:"));
827        assert!(display.contains("1. key1: value1"));
828    }
829
830    #[test]
831    fn test_withcontext_from_errcontext() {
832        let err_ctx = CallContext::from(("key1", "value1"));
833        let ctx = OperationContext::from(err_ctx);
834        assert!(ctx.target.is_none());
835        assert_eq!(ctx.context().items.len(), 1);
836        assert_eq!(
837            ctx.context().items[0],
838            ("key1".to_string(), "value1".to_string())
839        );
840    }
841
842    #[test]
843    fn test_withcontext_from_withcontext() {
844        let mut ctx1 = OperationContext::want("target1");
845        ctx1.record("key1", "value1");
846        let ctx2 = OperationContext::from(&ctx1);
847        assert_eq!(*ctx2.target(), Some("target1".to_string()));
848        assert_eq!(ctx2.context().items.len(), 1);
849        assert_eq!(
850            ctx2.context().items[0],
851            ("key1".to_string(), "value1".to_string())
852        );
853    }
854
855    #[test]
856    fn test_withcontext_from_str_path_pair() {
857        let path = PathBuf::from("/test/path");
858        let ctx = OperationContext::from(("file", &path));
859        assert_eq!(ctx.context().items.len(), 1);
860        assert_eq!(ctx.context().items[0].0, "file");
861        assert!(ctx.context().items[0].1.contains("/test/path"));
862    }
863
864    #[test]
865    fn test_withcontext_from_str_pathbuf_pair() {
866        let path = PathBuf::from("/test/pathbuf");
867        let ctx = OperationContext::from(("file", path));
868        assert_eq!(ctx.context().items.len(), 1);
869        assert_eq!(ctx.context().items[0].0, "file");
870        assert!(ctx.context().items[0].1.contains("/test/pathbuf"));
871    }
872
873    // ContextAdd trait tests are commented out due to trait implementation issues
874    // These tests will be revisited when the ContextAdd trait is properly implemented
875
876    #[test]
877    fn test_withcontext_edge_cases() {
878        let ctx1 = OperationContext::from("".to_string());
879        assert_eq!(ctx1.context().items.len(), 1);
880        assert_eq!(ctx1.context().items[0], ("key".to_string(), "".to_string()));
881
882        let ctx2 = OperationContext::from(("".to_string(), "".to_string()));
883        assert_eq!(ctx2.context().items.len(), 1);
884        assert_eq!(ctx2.context().items[0], ("".to_string(), "".to_string()));
885    }
886
887    #[test]
888    fn test_errcontext_equality() {
889        let ctx1 = CallContext::from(("key1", "value1"));
890        let ctx2 = CallContext::from(("key1", "value1"));
891        let ctx3 = CallContext::from(("key1", "value2"));
892
893        assert_eq!(ctx1, ctx2);
894        assert_ne!(ctx1, ctx3);
895    }
896
897    #[test]
898    fn test_withcontext_equality() {
899        let ctx1 = OperationContext::from(("key1", "value1"));
900        let ctx2 = OperationContext::from(("key1", "value1"));
901        let ctx3 = OperationContext::from(("key1", "value2"));
902
903        assert_eq!(ctx1, ctx2);
904        assert_ne!(ctx1, ctx3);
905    }
906
907    #[test]
908    fn test_withcontext_clone() {
909        let mut ctx = OperationContext::want("target");
910        ctx.record("key", "value");
911
912        let cloned = ctx.clone();
913        assert_eq!(ctx.target(), cloned.target());
914        assert_eq!(ctx.context().items.len(), cloned.context().items.len());
915        assert_eq!(ctx.context().items[0], cloned.context().items[0]);
916    }
917
918    #[test]
919    fn test_withcontext_with_types() {
920        let mut ctx = OperationContext::new();
921
922        // 测试各种类型转换
923        ctx.record("string_key", "string_value");
924        ctx.record("string_key", 42.to_string()); // 数字转字符串
925        ctx.record("bool_key", true.to_string()); // 布尔转字符串
926
927        assert_eq!(ctx.context().items.len(), 3);
928
929        // 验证最后一个添加的值
930        assert_eq!(
931            ctx.context().items[2],
932            ("bool_key".to_string(), "true".to_string())
933        );
934    }
935
936    #[test]
937    fn test_mark_suc() {
938        let mut ctx = OperationContext::new();
939        assert!(ctx.result == OperationResult::Fail);
940
941        ctx.mark_suc();
942        assert!(ctx.result == OperationResult::Suc);
943    }
944
945    #[test]
946    fn test_with_exit_log() {
947        let ctx = OperationContext::new().with_auto_log();
948        assert!(ctx.exit_log);
949
950        let ctx2 = OperationContext::want("test").with_auto_log();
951        assert!(ctx2.exit_log);
952        assert_eq!(*ctx2.target(), Some("test".to_string()));
953    }
954
955    #[test]
956    fn test_scope_marks_success() {
957        let mut ctx = OperationContext::want("scope_success");
958        {
959            let _scope = ctx.scoped_success();
960        }
961        assert!(matches!(ctx.result(), OperationResult::Suc));
962    }
963
964    #[test]
965    fn test_scope_preserves_failure() {
966        let mut ctx = OperationContext::want("scope_fail");
967        {
968            let mut scope = ctx.scoped_success();
969            scope.mark_failure();
970        }
971        assert!(matches!(ctx.result(), OperationResult::Fail));
972    }
973
974    #[test]
975    fn test_scope_cancel() {
976        let mut ctx = OperationContext::want("scope_cancel");
977        {
978            let mut scope = ctx.scoped_success();
979            scope.cancel();
980        }
981        assert!(matches!(ctx.result(), OperationResult::Cancel));
982    }
983
984    #[test]
985    fn test_format_context_with_target() {
986        let mut ctx = OperationContext::want("test_target");
987        ctx.record("key1", "value1");
988
989        let formatted = ctx.format_context();
990        assert_eq!(formatted, "test_target: \ncall context:\n\tkey1 : value1\n");
991    }
992
993    #[test]
994    fn test_format_context_without_target() {
995        let mut ctx = OperationContext::new();
996        ctx.record("key1", "value1");
997
998        let formatted = ctx.format_context();
999        assert_eq!(formatted, "call context:\n\tkey1 : value1\n");
1000    }
1001
1002    #[test]
1003    fn test_format_context_empty() {
1004        let ctx = OperationContext::new();
1005        let formatted = ctx.format_context();
1006        assert_eq!(formatted, "");
1007    }
1008
1009    #[test]
1010    fn test_format_context_with_target_only() {
1011        let ctx = OperationContext::want("test_target");
1012        let formatted = ctx.format_context();
1013        assert_eq!(formatted, "test_target");
1014    }
1015
1016    #[test]
1017    fn test_logging_methods() {
1018        let ctx = OperationContext::want("test_target");
1019
1020        // 这些方法主要测试它们不会panic,实际日志输出需要日志框架支持
1021        ctx.info("info message");
1022        ctx.debug("debug message");
1023        ctx.warn("warn message");
1024        ctx.error("error message");
1025        ctx.trace("trace message");
1026    }
1027
1028    #[test]
1029    fn test_logging_methods_with_empty_context() {
1030        let ctx = OperationContext::new();
1031
1032        // 测试空上下文时的日志方法
1033        ctx.info("info message");
1034        ctx.debug("debug message");
1035        ctx.warn("warn message");
1036        ctx.error("error message");
1037        ctx.trace("trace message");
1038    }
1039
1040    #[test]
1041    fn test_context_add_trait() {
1042        let mut ctx = OperationContext::new();
1043
1044        // 测试ContextAdd trait的实现
1045        ctx.add_context(("key1", "value1"));
1046        ctx.add_context(("key2", "value2"));
1047
1048        assert_eq!(ctx.context().items.len(), 2);
1049        assert_eq!(
1050            ctx.context().items[0],
1051            ("key1".to_string(), "value1".to_string())
1052        );
1053        assert_eq!(
1054            ctx.context().items[1],
1055            ("key2".to_string(), "value2".to_string())
1056        );
1057    }
1058
1059    #[test]
1060    fn test_drop_trait_with_success() {
1061        {
1062            let mut ctx = OperationContext::want("test_drop").with_auto_log();
1063            ctx.record("operation", "test");
1064            ctx.mark_suc(); // 标记为成功
1065                            // ctx 在这里离开作用域,会触发Drop trait
1066        }
1067        // 注意:Drop trait的日志输出需要日志框架配置才能看到
1068        // 这里主要测试Drop trait不会panic
1069    }
1070
1071    #[test]
1072    fn test_drop_trait_with_failure() {
1073        {
1074            let mut ctx = OperationContext::want("test_drop_fail").with_auto_log();
1075            ctx.record("operation", "test_fail");
1076            // 不调用mark_suc,保持is_suc = false
1077            // ctx 在这里离开作用域,会触发Drop trait
1078        }
1079        // 注意:Drop trait的日志输出需要日志框架配置才能看到
1080        // 这里主要测试Drop trait不会panic
1081    }
1082
1083    #[test]
1084    fn test_drop_trait_without_exit_log() {
1085        {
1086            let mut ctx = OperationContext::want("test_no_log");
1087            ctx.record("operation", "no_log");
1088            ctx.mark_suc();
1089            // exit_log = false,不会触发日志输出
1090            // ctx 在这里离开作用域,Drop trait应该什么都不做
1091        }
1092        // 测试通过即可
1093    }
1094
1095    #[test]
1096    fn test_complex_context_scenario() {
1097        // 模拟一个复杂的操作场景
1098        let mut ctx = OperationContext::want("user_registration").with_auto_log();
1099
1100        // 添加各种上下文信息
1101        ctx.record("user_id", "12345");
1102        ctx.record("email", "test@example.com");
1103        ctx.record("role", "user");
1104
1105        // 记录各种级别的日志
1106        ctx.info("开始用户注册流程");
1107        ctx.debug("验证用户输入");
1108        ctx.warn("检测到潜在的安全风险");
1109
1110        // 模拟操作成功
1111        ctx.mark_suc();
1112        ctx.info("用户注册成功");
1113
1114        // 验证上下文状态
1115        assert!(ctx.result == OperationResult::Suc);
1116        assert!(ctx.exit_log);
1117        assert_eq!(*ctx.target(), Some("user_registration".to_string()));
1118        assert_eq!(ctx.context().items.len(), 3);
1119
1120        // 验证format_context输出
1121        let formatted = ctx.format_context();
1122        assert!(formatted.contains("user_registration"));
1123        assert!(formatted.contains("user_id"));
1124        assert!(formatted.contains("email"));
1125        assert!(formatted.contains("role"));
1126    }
1127
1128    #[cfg(feature = "serde")]
1129    #[test]
1130    fn test_context_serialization() {
1131        let mut ctx = OperationContext::want("serialization_test");
1132        ctx.record("key1", "value1");
1133        ctx.record("key2", "value2");
1134
1135        // 测试序列化
1136        let serialized = serde_json::to_string(&ctx).expect("序列化失败");
1137        assert!(serialized.contains("serialization_test"));
1138        assert!(serialized.contains("key1"));
1139        assert!(serialized.contains("value1"));
1140
1141        // 测试反序列化
1142        let deserialized: OperationContext =
1143            serde_json::from_str(&serialized).expect("反序列化失败");
1144        assert_eq!(ctx, deserialized);
1145    }
1146
1147    #[test]
1148    fn test_context_with_special_characters() {
1149        let mut ctx = OperationContext::new();
1150
1151        // 测试特殊字符
1152        ctx.record("key_with_spaces", "value with spaces");
1153        ctx.record("key_with_unicode", "值包含中文");
1154        ctx.record("key_with_symbols", "value@#$%^&*()");
1155
1156        assert_eq!(ctx.context().items.len(), 3);
1157        assert_eq!(
1158            ctx.context().items[0],
1159            (
1160                "key_with_spaces".to_string(),
1161                "value with spaces".to_string()
1162            )
1163        );
1164        assert_eq!(
1165            ctx.context().items[1],
1166            ("key_with_unicode".to_string(), "值包含中文".to_string())
1167        );
1168        assert_eq!(
1169            ctx.context().items[2],
1170            ("key_with_symbols".to_string(), "value@#$%^&*()".to_string())
1171        );
1172
1173        // 测试显示
1174        let display = format!("{ctx}");
1175        assert!(display.contains("key_with_spaces"));
1176        assert!(display.contains("值包含中文"));
1177        assert!(display.contains("value@#$%^&*()"));
1178    }
1179
1180    #[test]
1181    fn test_context_builder_pattern() {
1182        // 测试构建器模式的使用
1183        let ctx = OperationContext::want("builder_test").with_auto_log();
1184
1185        assert_eq!(*ctx.target(), Some("builder_test".to_string()));
1186        assert!(ctx.exit_log);
1187    }
1188
1189    #[test]
1190    fn test_context_multiple_with_calls() {
1191        let mut ctx = OperationContext::new();
1192
1193        // 多次调用with方法
1194        ctx.record("key1", "value1");
1195        ctx.record("key2", "value2");
1196        ctx.record("key3", "value3");
1197        ctx.record("key1", "new_value1"); // 覆盖key1
1198
1199        // 注意:当前实现允许重复的key,这是预期的行为
1200        assert_eq!(ctx.context().items.len(), 4);
1201        assert_eq!(
1202            ctx.context().items[0],
1203            ("key1".to_string(), "value1".to_string())
1204        );
1205        assert_eq!(
1206            ctx.context().items[3],
1207            ("key1".to_string(), "new_value1".to_string())
1208        );
1209    }
1210
1211    #[test]
1212    fn test_context_from_various_types() {
1213        // 测试从各种类型创建OperationContext
1214        let ctx1 = OperationContext::from("simple_string");
1215        assert_eq!(
1216            ctx1.context().items[0],
1217            ("key".to_string(), "simple_string".to_string())
1218        );
1219
1220        let ctx2 = OperationContext::from(("custom_key", "custom_value"));
1221        assert_eq!(
1222            ctx2.context().items[0],
1223            ("custom_key".to_string(), "custom_value".to_string())
1224        );
1225
1226        let path = PathBuf::from("/test/path/file.txt");
1227        let ctx3 = OperationContext::from(&path);
1228        assert!(ctx3.context().items[0].0.contains("path"));
1229        assert!(ctx3.context().items[0].1.contains("/test/path/file.txt"));
1230    }
1231
1232    // ContextTake trait 测试用例
1233    #[test]
1234    fn test_context_take_with_string_types() {
1235        let mut ctx = OperationContext::new();
1236
1237        // 测试字符串类型的ContextTake实现
1238        ctx.record("string_key", "string_value");
1239        ctx.record("string_key2", String::from("string_value2"));
1240        ctx.record(String::from("string_key3"), "string_value3");
1241        ctx.record(String::from("string_key4"), String::from("string_value4"));
1242
1243        assert_eq!(ctx.context().items.len(), 4);
1244        assert_eq!(
1245            ctx.context().items[0],
1246            ("string_key".to_string(), "string_value".to_string())
1247        );
1248        assert_eq!(
1249            ctx.context().items[1],
1250            ("string_key2".to_string(), "string_value2".to_string())
1251        );
1252        assert_eq!(
1253            ctx.context().items[2],
1254            ("string_key3".to_string(), "string_value3".to_string())
1255        );
1256        assert_eq!(
1257            ctx.context().items[3],
1258            ("string_key4".to_string(), "string_value4".to_string())
1259        );
1260    }
1261
1262    #[test]
1263    fn test_context_take_with_numeric_types() {
1264        let mut ctx = OperationContext::new();
1265
1266        // 测试数字类型的ContextTake实现(需要转换为字符串)
1267        ctx.record("int_key", 42.to_string());
1268        ctx.record("float_key", 3.24.to_string());
1269        ctx.record("bool_key", true.to_string());
1270
1271        assert_eq!(ctx.context().items.len(), 3);
1272        assert_eq!(
1273            ctx.context().items[0],
1274            ("int_key".to_string(), "42".to_string())
1275        );
1276        assert_eq!(
1277            ctx.context().items[1],
1278            ("float_key".to_string(), "3.24".to_string())
1279        );
1280        assert_eq!(
1281            ctx.context().items[2],
1282            ("bool_key".to_string(), "true".to_string())
1283        );
1284    }
1285
1286    #[test]
1287    fn test_context_take_with_path_context() {
1288        let mut ctx = OperationContext::new();
1289
1290        // 测试PathContext包装类型的ContextTake实现
1291        let path1 = PathBuf::from("/test/path1.txt");
1292        let path2 = Path::new("/test/path2.txt");
1293
1294        ctx.record("file1", &path1);
1295        ctx.record("file2", path2);
1296
1297        assert_eq!(ctx.context().items.len(), 2);
1298        assert_eq!(ctx.context().items[0].0, "file1");
1299        assert!(ctx.context().items[0].1.contains("/test/path1.txt"));
1300        assert_eq!(ctx.context().items[1].0, "file2");
1301        assert!(ctx.context().items[1].1.contains("/test/path2.txt"));
1302    }
1303
1304    #[test]
1305    fn test_context_take_mixed_types() {
1306        let mut ctx = OperationContext::new();
1307
1308        // 测试混合使用字符串和PathContext类型
1309        ctx.record("name", "test_user");
1310        ctx.record("age", 25.to_string());
1311        ctx.record("config_file", &PathBuf::from("/etc/config.toml"));
1312        ctx.record("status", "active");
1313
1314        assert_eq!(ctx.context().items.len(), 4);
1315        assert_eq!(
1316            ctx.context().items[0],
1317            ("name".to_string(), "test_user".to_string())
1318        );
1319        assert_eq!(
1320            ctx.context().items[1],
1321            ("age".to_string(), "25".to_string())
1322        );
1323        assert_eq!(ctx.context().items[2].0, "config_file");
1324        assert!(ctx.context().items[2].1.contains("/etc/config.toml"));
1325        assert_eq!(
1326            ctx.context().items[3],
1327            ("status".to_string(), "active".to_string())
1328        );
1329    }
1330
1331    #[test]
1332    fn test_context_take_edge_cases() {
1333        let mut ctx = OperationContext::new();
1334
1335        // 测试边界情况
1336        ctx.record("", ""); // 空字符串
1337        ctx.record("empty_value", ""); // 空值
1338        ctx.record("", "empty_key"); // 空键
1339        ctx.record("special_chars", "@#$%^&*()"); // 特殊字符
1340        ctx.record("unicode", "测试中文字符"); // Unicode字符
1341
1342        assert_eq!(ctx.context().items.len(), 5);
1343        assert_eq!(ctx.context().items[0], ("".to_string(), "".to_string()));
1344        assert_eq!(
1345            ctx.context().items[1],
1346            ("empty_value".to_string(), "".to_string())
1347        );
1348        assert_eq!(
1349            ctx.context().items[2],
1350            ("".to_string(), "empty_key".to_string())
1351        );
1352        assert_eq!(
1353            ctx.context().items[3],
1354            ("special_chars".to_string(), "@#$%^&*()".to_string())
1355        );
1356        assert_eq!(
1357            ctx.context().items[4],
1358            ("unicode".to_string(), "测试中文字符".to_string())
1359        );
1360    }
1361
1362    #[test]
1363    fn test_context_take_multiple_calls() {
1364        let mut ctx = OperationContext::new();
1365
1366        // 测试多次调用take方法
1367        ctx.record("key1", "value1");
1368        ctx.record("key2", "value2");
1369        ctx.record("key1", "new_value1"); // 覆盖key1
1370        ctx.record("key3", &PathBuf::from("/path/file.txt"));
1371        ctx.record("key2", &PathBuf::from("/path/file2.txt")); // 覆盖key2
1372
1373        // 注意:当前实现允许重复的key,这是预期的行为
1374        assert_eq!(ctx.context().items.len(), 5);
1375        assert_eq!(
1376            ctx.context().items[0],
1377            ("key1".to_string(), "value1".to_string())
1378        );
1379        assert_eq!(
1380            ctx.context().items[1],
1381            ("key2".to_string(), "value2".to_string())
1382        );
1383        assert_eq!(
1384            ctx.context().items[2],
1385            ("key1".to_string(), "new_value1".to_string())
1386        );
1387        assert_eq!(ctx.context().items[3].0, "key3");
1388        assert!(ctx.context().items[3].1.contains("/path/file.txt"));
1389        assert_eq!(ctx.context().items[4].0, "key2");
1390        assert!(ctx.context().items[4].1.contains("/path/file2.txt"));
1391    }
1392
1393    #[test]
1394    fn test_context_take_with_existing_context() {
1395        // 创建一个已有上下文的OperationContext
1396        let mut ctx = OperationContext::from(("existing_key", "existing_value"));
1397
1398        // 使用ContextTake添加更多上下文
1399        ctx.record("new_key1", "new_value1");
1400        ctx.record("new_key2", &PathBuf::from("/new/path.txt"));
1401
1402        assert_eq!(ctx.context().items.len(), 3);
1403        assert_eq!(
1404            ctx.context().items[0],
1405            ("existing_key".to_string(), "existing_value".to_string())
1406        );
1407        assert_eq!(
1408            ctx.context().items[1],
1409            ("new_key1".to_string(), "new_value1".to_string())
1410        );
1411        assert_eq!(ctx.context().items[2].0, "new_key2");
1412        assert!(ctx.context().items[2].1.contains("/new/path.txt"));
1413    }
1414}