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