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