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