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