1use colored::Colorize;
30
31#[cfg(feature = "wasm-plugins")]
32use crate::error::WasmError;
33use crate::error::{
34 ConfigError, DynamicCliError, ExecutionError, ParseError, RegistryError, ValidationError,
35};
36
37fn color_error(s: &str) -> String {
43 s.red().bold().to_string()
44}
45
46fn color_question(s: &str) -> String {
48 s.yellow().bold().to_string()
49}
50
51fn color_bullet(s: &str) -> String {
53 s.cyan().to_string()
54}
55
56fn color_suggestion(s: &str) -> String {
58 s.green().to_string()
59}
60
61fn color_info(s: &str) -> String {
63 s.blue().bold().to_string()
64}
65
66fn color_type_name(s: &str) -> String {
68 s.cyan().to_string()
69}
70
71fn color_arg_name(s: &str) -> String {
73 s.yellow().to_string()
74}
75
76fn color_value(s: &str) -> String {
78 s.red().to_string()
79}
80
81fn color_dimmed(s: &str) -> String {
83 s.dimmed().to_string()
84}
85
86pub fn display_error(error: &DynamicCliError) {
107 eprintln!("{}", format_error(error));
108}
109
110pub fn format_error(error: &DynamicCliError) -> String {
144 let mut output = String::new();
145
146 output.push_str(&format!("{} ", color_error("Error:")));
147
148 match error {
149 DynamicCliError::Parse(e) => format_parse_error(&mut output, e),
150 DynamicCliError::Config(e) => format_config_error(&mut output, e),
151 DynamicCliError::Validation(e) => format_validation_error(&mut output, e),
152 DynamicCliError::Execution(e) => format_execution_error(&mut output, e),
153 DynamicCliError::Registry(e) => format_registry_error(&mut output, e),
154 #[cfg(feature = "wasm-plugins")]
155 DynamicCliError::Wasm(e) => format_wasm_error(&mut output, e),
156 DynamicCliError::Io(e) => output.push_str(&format!("{}\n", e)),
157 }
158
159 output
160}
161
162fn format_parse_error(output: &mut String, error: &ParseError) {
168 output.push_str(&format!("{}\n", error));
169
170 match error {
171 ParseError::UnknownCommand {
172 suggestions, hint, ..
173 } => {
174 if !suggestions.is_empty() {
175 output.push_str(&format!("\n{} Did you mean:\n", color_question("?")));
176 for s in suggestions {
177 output.push_str(&format!(
178 " {} {}\n",
179 color_bullet("•"),
180 color_suggestion(s)
181 ));
182 }
183 }
184 append_suggestion(output, hint.as_deref());
185 }
186
187 ParseError::UnknownOption { suggestions, .. } if !suggestions.is_empty() => {
188 output.push_str(&format!("\n{} Did you mean:\n", color_question("?")));
189 for s in suggestions {
190 output.push_str(&format!(
191 " {} {}\n",
192 color_bullet("•"),
193 color_suggestion(s)
194 ));
195 }
196 }
197
198 ParseError::TypeParseError {
199 arg_name,
200 expected_type,
201 value,
202 ..
203 } => {
204 output.push_str(&format!(
205 "\n{} Expected type {} for argument {}, got: {}\n",
206 color_info("ℹ"),
207 color_type_name(expected_type),
208 color_arg_name(arg_name),
209 color_value(value)
210 ));
211 }
212
213 ParseError::MissingArgument { suggestion, .. }
214 | ParseError::MissingOption { suggestion, .. }
215 | ParseError::TooManyArguments { suggestion, .. }
216 | ParseError::UnknownOptionParameter { suggestion, .. }
217 | ParseError::MissingRequiredOptionParameter { suggestion, .. }
218 | ParseError::UnknownDiscriminant { suggestion, .. }
219 | ParseError::DuplicateOptionOccurrence { suggestion, .. } => {
220 append_suggestion(output, suggestion.as_deref());
221 }
222
223 _ => {}
224 }
225}
226
227fn format_config_error(output: &mut String, error: &ConfigError) {
229 match error {
230 ConfigError::YamlParse {
231 source,
232 line,
233 column,
234 } => {
235 output.push_str(&format!("{}\n", source));
236 if let (Some(l), Some(c)) = (line, column) {
237 output.push_str(&format!(
238 " {} line {}, column {}\n",
239 color_dimmed("at"),
240 color_arg_name(&l.to_string()),
241 color_arg_name(&c.to_string())
242 ));
243 }
244 }
245
246 ConfigError::JsonParse {
247 source,
248 line,
249 column,
250 } => {
251 output.push_str(&format!("{}\n", source));
252 output.push_str(&format!(
253 " {} line {}, column {}\n",
254 color_dimmed("at"),
255 color_arg_name(&line.to_string()),
256 color_arg_name(&column.to_string())
257 ));
258 }
259
260 ConfigError::InvalidSchema {
261 reason,
262 path,
263 suggestion,
264 } => {
265 output.push_str(&format!("{}\n", reason));
266 if let Some(p) = path {
267 output.push_str(&format!(
268 " {} {}\n",
269 color_dimmed("in"),
270 color_type_name(p)
271 ));
272 }
273 append_suggestion(output, suggestion.as_deref());
274 }
275
276 ConfigError::FileNotFound { suggestion, .. }
277 | ConfigError::UnsupportedFormat { suggestion, .. }
278 | ConfigError::DuplicateCommand { suggestion, .. }
279 | ConfigError::UnknownType { suggestion, .. }
280 | ConfigError::Inconsistency { suggestion, .. } => {
281 output.push_str(&format!("{}\n", error));
282 append_suggestion(output, suggestion.as_deref());
283 }
284 }
285}
286
287fn format_validation_error(output: &mut String, error: &ValidationError) {
289 output.push_str(&format!("{}\n", error));
290
291 let suggestion = match error {
292 ValidationError::FileNotFound { suggestion, .. } => suggestion.as_deref(),
293 ValidationError::OutOfRange { suggestion, .. } => suggestion.as_deref(),
294 ValidationError::CustomConstraint { suggestion, .. } => suggestion.as_deref(),
295 ValidationError::MissingDependency { suggestion, .. } => suggestion.as_deref(),
296 ValidationError::MutuallyExclusive { suggestion, .. } => suggestion.as_deref(),
297 ValidationError::InvalidExtension { .. } => None,
299 };
300
301 append_suggestion(output, suggestion);
302}
303
304fn format_execution_error(output: &mut String, error: &ExecutionError) {
306 output.push_str(&format!("{}\n", error));
307
308 let suggestion = match error {
309 ExecutionError::HandlerNotFound { suggestion, .. } => suggestion.as_deref(),
310 ExecutionError::ContextDowncastFailed { suggestion, .. } => suggestion.as_deref(),
311 ExecutionError::InvalidContextState { suggestion, .. } => suggestion.as_deref(),
312 ExecutionError::CommandFailed(_) | ExecutionError::Interrupted => None,
314 };
315
316 append_suggestion(output, suggestion);
317}
318
319fn format_registry_error(output: &mut String, error: &RegistryError) {
321 output.push_str(&format!("{}\n", error));
322
323 let suggestion = match error {
324 RegistryError::DuplicateRegistration { suggestion, .. } => suggestion.as_deref(),
325 RegistryError::DuplicateAlias { suggestion, .. } => suggestion.as_deref(),
326 RegistryError::MissingHandler { suggestion, .. } => suggestion.as_deref(),
327 RegistryError::FaultToleranceMismatch { suggestion, .. } => suggestion.as_deref(),
328 };
329
330 append_suggestion(output, suggestion);
331}
332
333#[cfg(feature = "wasm-plugins")]
340fn format_wasm_error(output: &mut String, error: &WasmError) {
341 output.push_str(&format!("{}\n", error));
342
343 let suggestion = match error {
344 WasmError::LoadFailed { suggestion, .. } => suggestion.as_deref(),
345 WasmError::FunctionNotFound { suggestion, .. } => suggestion.as_deref(),
346 WasmError::GuestError { .. } => None,
347 WasmError::SerializationFailed(_) => None,
348 WasmError::MemoryAccessFailed { .. } => None,
349 };
350
351 append_suggestion(output, suggestion);
352}
353
354fn append_suggestion(output: &mut String, suggestion: Option<&str>) {
368 if let Some(s) = suggestion {
369 output.push_str(&format!(" {} {}\n", color_info("ℹ"), s));
370 }
371}
372
373#[cfg(test)]
378mod tests {
379 use super::*;
380 use std::path::PathBuf;
381
382 #[test]
385 fn test_format_config_file_not_found_contains_path() {
386 let error: DynamicCliError = ConfigError::FileNotFound {
387 path: PathBuf::from("test.yaml"),
388 suggestion: None,
389 }
390 .into();
391
392 let formatted = format_error(&error);
393 assert!(formatted.contains("Error:"));
394 assert!(formatted.contains("test.yaml"));
395 }
396
397 #[test]
398 fn test_format_config_file_not_found_with_suggestion() {
399 let error: DynamicCliError = ConfigError::FileNotFound {
400 path: PathBuf::from("test.yaml"),
401 suggestion: Some("Verify the path.".to_string()),
402 }
403 .into();
404
405 let formatted = format_error(&error);
406 assert!(formatted.contains("Verify the path."));
407 }
408
409 #[test]
410 fn test_format_config_file_not_found_no_suggestion_no_hint_line() {
411 let error: DynamicCliError = ConfigError::FileNotFound {
412 path: PathBuf::from("test.yaml"),
413 suggestion: None,
414 }
415 .into();
416
417 let formatted = format_error(&error);
418 assert!(!formatted.contains('ℹ'));
420 }
421
422 #[test]
423 fn test_format_config_unsupported_format_with_suggestion() {
424 let error: DynamicCliError = ConfigError::UnsupportedFormat {
425 extension: ".toml".to_string(),
426 suggestion: Some("Use .yaml instead.".to_string()),
427 }
428 .into();
429
430 let formatted = format_error(&error);
431 assert!(formatted.contains(".toml"));
432 assert!(formatted.contains("Use .yaml instead."));
433 }
434
435 #[test]
436 fn test_format_config_yaml_parse_contains_location() {
437 let yaml_error = serde_yaml::from_str::<serde_yaml::Value>("invalid: [")
438 .err()
439 .unwrap();
440
441 let error: DynamicCliError = ConfigError::yaml_parse_with_location(yaml_error).into();
442 let formatted = format_error(&error);
443 assert!(formatted.contains("Error:"));
444 }
445
446 #[test]
447 fn test_format_config_invalid_schema_with_path_and_suggestion() {
448 let error: DynamicCliError = ConfigError::InvalidSchema {
449 reason: "missing field".to_string(),
450 path: Some("commands[0]".to_string()),
451 suggestion: Some("Add a name field.".to_string()),
452 }
453 .into();
454
455 let formatted = format_error(&error);
456 assert!(formatted.contains("missing field"));
457 assert!(formatted.contains("commands[0]"));
458 assert!(formatted.contains("Add a name field."));
459 }
460
461 #[test]
464 fn test_format_parse_unknown_command_with_suggestions() {
465 let error: DynamicCliError = ParseError::UnknownCommand {
466 command: "simulat".to_string(),
467 suggestions: vec!["simulate".to_string(), "validation".to_string()],
468 hint: None,
469 }
470 .into();
471
472 let formatted = format_error(&error);
473 assert!(formatted.contains("Unknown command"));
474 assert!(formatted.contains("simulat"));
475 assert!(formatted.contains("Did you mean"));
476 assert!(formatted.contains("simulate"));
477 }
478
479 #[test]
480 fn test_format_parse_unknown_command_no_suggestions() {
481 let error: DynamicCliError = ParseError::UnknownCommand {
482 command: "xyz".to_string(),
483 suggestions: vec![],
484 hint: None,
485 }
486 .into();
487
488 let formatted = format_error(&error);
489 assert!(formatted.contains("xyz"));
490 assert!(!formatted.contains("Did you mean"));
491 assert!(!formatted.contains('ℹ'), "no hint, no hint line");
492 assert!(
493 !formatted.contains("help"),
494 "the error itself names no help"
495 );
496 }
497
498 #[test]
499 fn test_format_parse_unknown_command_hint_after_suggestions() {
500 let error: DynamicCliError = ParseError::UnknownCommand {
501 command: "simulat".to_string(),
502 suggestions: vec!["simulate".to_string()],
503 hint: Some("Type ':help' for available commands".to_string()),
504 }
505 .into();
506
507 let formatted = format_error(&error);
508 let suggestion_at = formatted.find("simulate").expect("suggestion shown");
509 let hint_at = formatted
510 .find("Type ':help' for available commands")
511 .expect("hint shown");
512 assert!(suggestion_at < hint_at, "hint comes after the suggestions");
513 assert!(formatted.contains('ℹ'));
514 }
515
516 #[test]
517 fn test_format_parse_unknown_command_hint_without_suggestions() {
518 let error: DynamicCliError = ParseError::UnknownCommand {
519 command: "xyz".to_string(),
520 suggestions: vec![],
521 hint: Some("Run with '--help' for available commands".to_string()),
522 }
523 .into();
524
525 let formatted = format_error(&error);
526 assert!(!formatted.contains("Did you mean"));
527 assert!(formatted.contains("Run with '--help' for available commands"));
528 }
529
530 #[test]
531 fn test_format_parse_missing_argument_with_suggestion() {
532 let error: DynamicCliError = ParseError::MissingArgument {
533 argument: "file".to_string(),
534 command: "process".to_string(),
535 suggestion: Some("Run --help process to see required arguments.".to_string()),
536 }
537 .into();
538
539 let formatted = format_error(&error);
540 assert!(formatted.contains("file"));
541 assert!(formatted.contains("Run --help process"));
542 }
543
544 #[test]
545 fn test_format_parse_missing_option_with_suggestion() {
546 let error: DynamicCliError = ParseError::MissingOption {
547 option: "output".to_string(),
548 command: "export".to_string(),
549 suggestion: Some("Run --help export to see required options.".to_string()),
550 }
551 .into();
552
553 let formatted = format_error(&error);
554 assert!(formatted.contains("output"));
555 assert!(formatted.contains("Run --help export"));
556 }
557
558 #[test]
559 fn test_format_parse_too_many_arguments_with_suggestion() {
560 let error: DynamicCliError = ParseError::TooManyArguments {
561 command: "run".to_string(),
562 expected: 1,
563 got: 3,
564 suggestion: Some("Run --help run for the expected usage.".to_string()),
565 }
566 .into();
567
568 let formatted = format_error(&error);
569 assert!(formatted.contains("run"));
570 assert!(formatted.contains("Run --help run"));
571 }
572
573 #[test]
576 fn test_format_parse_unknown_option_parameter_with_suggestion() {
577 let error: DynamicCliError = ParseError::UnknownOptionParameter {
578 option: "output".to_string(),
579 discriminant: "csv".to_string(),
580 key: "compression".to_string(),
581 valid_keys: vec!["file".to_string(), "resolution".to_string()],
582 suggestion: Some("Run --help export to see valid keys for --output csv.".to_string()),
583 }
584 .into();
585
586 let formatted = format_error(&error);
587 assert!(formatted.contains("compression"));
588 assert!(formatted.contains("csv"));
589 assert!(formatted.contains("file"));
590 assert!(formatted.contains("resolution"));
591 assert!(formatted.contains("Run --help export"));
592 }
593
594 #[test]
595 fn test_format_parse_missing_required_option_parameter_with_suggestion() {
596 let error: DynamicCliError = ParseError::MissingRequiredOptionParameter {
597 option: "output".to_string(),
598 discriminant: "csv".to_string(),
599 key: "file".to_string(),
600 suggestion: Some(
601 "Run --help export to see required keys for --output csv.".to_string(),
602 ),
603 }
604 .into();
605
606 let formatted = format_error(&error);
607 assert!(formatted.contains("file"));
608 assert!(formatted.contains("csv"));
609 assert!(formatted.contains("Run --help export"));
610 }
611
612 #[test]
613 fn test_format_parse_unknown_discriminant_with_suggestion() {
614 let error: DynamicCliError = ParseError::UnknownDiscriminant {
615 option: "output".to_string(),
616 value: "xml".to_string(),
617 valid_choices: vec!["csv".to_string(), "plot".to_string()],
618 suggestion: Some("Run --help export to see valid --output kinds.".to_string()),
619 }
620 .into();
621
622 let formatted = format_error(&error);
623 assert!(formatted.contains("xml"));
624 assert!(formatted.contains("csv"));
625 assert!(formatted.contains("plot"));
626 assert!(formatted.contains("Run --help export"));
627 }
628
629 #[test]
630 fn test_format_parse_duplicate_option_occurrence_with_suggestion() {
631 let error: DynamicCliError = ParseError::DuplicateOptionOccurrence {
632 option: "output".to_string(),
633 discriminant: "csv".to_string(),
634 params: vec![("file".to_string(), "results.csv".to_string())],
635 suggestion: Some(
636 "Remove one of the two identical --output csv occurrences.".to_string(),
637 ),
638 }
639 .into();
640
641 let formatted = format_error(&error);
642 assert!(formatted.contains("results.csv"));
643 assert!(formatted.contains("csv"));
644 assert!(formatted.contains("Remove one of the two identical"));
645 }
646
647 #[test]
648 fn test_debug_new_parse_error_variants_dd024() {
649 let variants: Vec<ParseError> = vec![
652 ParseError::UnknownOptionParameter {
653 option: "output".to_string(),
654 discriminant: "csv".to_string(),
655 key: "compression".to_string(),
656 valid_keys: vec!["file".to_string()],
657 suggestion: None,
658 },
659 ParseError::MissingRequiredOptionParameter {
660 option: "output".to_string(),
661 discriminant: "csv".to_string(),
662 key: "file".to_string(),
663 suggestion: None,
664 },
665 ParseError::UnknownDiscriminant {
666 option: "output".to_string(),
667 value: "xml".to_string(),
668 valid_choices: vec!["csv".to_string()],
669 suggestion: None,
670 },
671 ParseError::DuplicateOptionOccurrence {
672 option: "output".to_string(),
673 discriminant: "csv".to_string(),
674 params: vec![("file".to_string(), "results.csv".to_string())],
675 suggestion: None,
676 },
677 ];
678
679 for variant in variants {
680 let debug_str = format!("{:?}", variant);
681 assert!(!debug_str.is_empty());
682 }
683 }
684
685 #[test]
686 fn test_format_parse_type_parse_error_shows_info_block() {
687 let error: DynamicCliError = ParseError::TypeParseError {
688 arg_name: "count".to_string(),
689 expected_type: "integer".to_string(),
690 value: "abc".to_string(),
691 details: None,
692 }
693 .into();
694
695 let formatted = format_error(&error);
696 assert!(formatted.contains("integer"));
697 assert!(formatted.contains("count"));
698 assert!(formatted.contains("abc"));
699 }
700
701 #[test]
704 fn test_format_validation_file_not_found_with_suggestion() {
705 let error: DynamicCliError = ValidationError::FileNotFound {
706 path: PathBuf::from("data.csv"),
707 arg_name: "input".to_string(),
708 suggestion: Some("Check that the file exists.".to_string()),
709 }
710 .into();
711
712 let formatted = format_error(&error);
713 assert!(formatted.contains("data.csv"));
714 assert!(formatted.contains("Check that the file exists."));
715 }
716
717 #[test]
718 fn test_format_validation_out_of_range_with_suggestion() {
719 let error: DynamicCliError = ValidationError::OutOfRange {
720 arg_name: "percentage".to_string(),
721 value: 150.0,
722 min: 0.0,
723 max: 100.0,
724 suggestion: Some("Value must be between 0 and 100.".to_string()),
725 }
726 .into();
727
728 let formatted = format_error(&error);
729 assert!(formatted.contains("percentage"));
730 assert!(formatted.contains("Value must be between 0 and 100."));
731 }
732
733 #[test]
734 fn test_format_validation_mutually_exclusive_with_suggestion() {
735 let error: DynamicCliError = ValidationError::MutuallyExclusive {
736 arg1: "--verbose".to_string(),
737 arg2: "--quiet".to_string(),
738 suggestion: Some("Remove one of the two conflicting options.".to_string()),
739 }
740 .into();
741
742 let formatted = format_error(&error);
743 assert!(formatted.contains("--verbose"));
744 assert!(formatted.contains("Remove one of the two conflicting options."));
745 }
746
747 #[test]
748 fn test_format_validation_missing_dependency_with_suggestion() {
749 let error: DynamicCliError = ValidationError::MissingDependency {
750 arg_name: "format".to_string(),
751 required_arg: "output".to_string(),
752 suggestion: Some("Add --output to your command.".to_string()),
753 }
754 .into();
755
756 let formatted = format_error(&error);
757 assert!(formatted.contains("format"));
758 assert!(formatted.contains("Add --output to your command."));
759 }
760
761 #[test]
762 fn test_format_validation_invalid_extension_no_suggestion_line() {
763 let error: DynamicCliError = ValidationError::InvalidExtension {
765 arg_name: "input".to_string(),
766 path: PathBuf::from("data.png"),
767 expected: vec![".csv".to_string(), ".tsv".to_string()],
768 }
769 .into();
770
771 let formatted = format_error(&error);
772 assert!(formatted.contains("data.png"));
773 assert!(!formatted.contains('ℹ'));
774 }
775
776 #[test]
779 fn test_format_execution_handler_not_found_with_suggestion() {
780 let error: DynamicCliError = ExecutionError::HandlerNotFound {
781 command: "run".to_string(),
782 implementation: "run_handler".to_string(),
783 suggestion: Some(
784 "Ensure .register_sync_handler(\"run_handler\", ...) was called.".to_string(),
785 ),
786 }
787 .into();
788
789 let formatted = format_error(&error);
790 assert!(formatted.contains("run"));
791 assert!(formatted.contains("run_handler"));
792 assert!(formatted.contains("register_sync_handler"));
793 }
794
795 #[test]
796 fn test_format_execution_context_downcast_failed_with_suggestion() {
797 let error: DynamicCliError = ExecutionError::ContextDowncastFailed {
798 expected_type: "MyCtx".to_string(),
799 suggestion: Some("Check the context type.".to_string()),
800 }
801 .into();
802
803 let formatted = format_error(&error);
804 assert!(formatted.contains("MyCtx"));
805 assert!(formatted.contains("Check the context type."));
806 }
807
808 #[test]
809 fn test_format_execution_interrupted_no_suggestion() {
810 let error: DynamicCliError = ExecutionError::Interrupted.into();
811 let formatted = format_error(&error);
812 assert!(formatted.contains("interrupted"));
813 assert!(!formatted.contains('ℹ'));
814 }
815
816 #[test]
819 fn test_format_registry_missing_handler_with_suggestion() {
820 let error: DynamicCliError = RegistryError::MissingHandler {
821 command: "export".to_string(),
822 suggestion: Some(
823 "Call .register_sync_handler(\"export\", ...) before running.".to_string(),
824 ),
825 }
826 .into();
827
828 let formatted = format_error(&error);
829 assert!(formatted.contains("export"));
830 assert!(formatted.contains("register_sync_handler"));
831 }
832
833 #[test]
834 fn test_format_registry_duplicate_registration_with_suggestion() {
835 let error: DynamicCliError = RegistryError::DuplicateRegistration {
836 name: "run".to_string(),
837 suggestion: Some("Command names must be unique.".to_string()),
838 }
839 .into();
840
841 let formatted = format_error(&error);
842 assert!(formatted.contains("run"));
843 assert!(formatted.contains("Command names must be unique."));
844 }
845
846 #[test]
847 fn test_format_registry_duplicate_alias_with_suggestion() {
848 let error: DynamicCliError = RegistryError::DuplicateAlias {
849 alias: "r".to_string(),
850 existing_command: "run".to_string(),
851 suggestion: Some("Choose a different alias.".to_string()),
852 }
853 .into();
854
855 let formatted = format_error(&error);
856 assert!(formatted.contains("run"));
857 assert!(formatted.contains("Choose a different alias."));
858 }
859
860 #[test]
861 fn test_format_registry_fault_tolerance_mismatch_with_suggestion() {
862 let error: DynamicCliError =
863 RegistryError::fault_tolerance_mismatch("solve", false, true).into();
864
865 let formatted = format_error(&error);
866 assert!(formatted.contains("solve"));
867 assert!(formatted.contains("continue_on_failure: false"));
868 }
869
870 #[cfg(feature = "wasm-plugins")]
873 #[test]
874 fn test_format_wasm_function_not_found_with_suggestion() {
875 let error: DynamicCliError = WasmError::FunctionNotFound {
876 function: "dcli_dealloc".to_string(),
877 module: "plugin.wasm".to_string(),
878 suggestion: Some("Export `dcli_dealloc(ptr: i32, size: i32)`.".to_string()),
879 }
880 .into();
881
882 let formatted = format_error(&error);
883 assert!(formatted.contains("dcli_dealloc"));
884 assert!(formatted.contains("Export `dcli_dealloc"));
885 }
886
887 #[cfg(feature = "wasm-plugins")]
888 #[test]
889 fn test_format_wasm_guest_error_without_suggestion_line() {
890 let error: DynamicCliError = WasmError::GuestError {
891 code: 1,
892 message: Some("invalid argument".to_string()),
893 }
894 .into();
895
896 let formatted = format_error(&error);
897 assert!(formatted.contains("invalid argument"));
898 assert!(!formatted.contains('ℹ'));
900 }
901
902 #[test]
905 fn test_display_error_does_not_panic() {
906 let error: DynamicCliError = ConfigError::FileNotFound {
907 path: PathBuf::from("test.yaml"),
908 suggestion: None,
909 }
910 .into();
911 display_error(&error);
913 }
914}