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