1use super::result::{CheckError, CheckResult};
4use super::traits::LightCheck;
5use crate::query::{CodeGraphV2, StdImplCache, TypeFlowGraphV2};
6use crate::symbol::{SymbolPath, SymbolRegistry};
7
8pub struct GraphChecker<'a> {
22 graph: &'a CodeGraphV2,
23 typeflow: &'a TypeFlowGraphV2,
24 registry: &'a SymbolRegistry,
25 std_impls: StdImplCache,
26}
27
28impl<'a> GraphChecker<'a> {
29 pub fn new(
31 graph: &'a CodeGraphV2,
32 typeflow: &'a TypeFlowGraphV2,
33 registry: &'a SymbolRegistry,
34 ) -> Self {
35 Self {
36 graph,
37 typeflow,
38 registry,
39 std_impls: StdImplCache::default(),
40 }
41 }
42
43 pub fn with_std_impls(
45 graph: &'a CodeGraphV2,
46 typeflow: &'a TypeFlowGraphV2,
47 registry: &'a SymbolRegistry,
48 std_impls: StdImplCache,
49 ) -> Self {
50 Self {
51 graph,
52 typeflow,
53 registry,
54 std_impls,
55 }
56 }
57
58 pub fn check_symbol_exists(&self, name: &str) -> bool {
75 if let Ok(path) = SymbolPath::parse(name) {
77 if self.registry.lookup(&path).is_some() {
78 return true;
79 }
80 }
81
82 !self.registry.find_by_name(name).is_empty()
84 }
85
86 pub fn check_symbol_with_suggestions(&self, name: &str) -> Result<(), CheckError> {
88 if self.check_symbol_exists(name) {
89 Ok(())
90 } else {
91 let suggestions = self.find_similar_symbols(name);
93 Err(CheckError::UnresolvedRef {
94 name: name.to_string(),
95 location: None,
96 suggestions,
97 })
98 }
99 }
100
101 pub fn check_symbol_unique(&self, name: &str) -> Result<crate::SymbolId, CheckError> {
118 if let Ok(path) = SymbolPath::parse(name) {
120 if let Some(id) = self.registry.lookup(&path) {
121 return Ok(id);
122 }
123 }
124
125 let matches = self.registry.find_by_name(name);
127
128 match matches.len() {
129 0 => {
130 let suggestions = self.find_similar_symbols(name);
131 Err(CheckError::UnresolvedRef {
132 name: name.to_string(),
133 location: None,
134 suggestions,
135 })
136 }
137 1 => Ok(matches[0]),
138 _ => {
139 let candidates: Vec<String> = matches
141 .iter()
142 .filter_map(|id| self.registry.resolve(*id).map(|p| p.to_string()))
143 .collect();
144 Err(CheckError::ambiguous_target(name, candidates))
145 }
146 }
147 }
148
149 fn find_similar_symbols(&self, name: &str) -> Vec<String> {
151 let name_lower = name.to_lowercase();
152 let mut suggestions = Vec::new();
153
154 for (_, path) in self.registry.iter() {
155 let sym_name = path.name();
156 if sym_name
158 .to_lowercase()
159 .starts_with(&name_lower[..name_lower.len().min(3)])
160 || sym_name.to_lowercase().contains(&name_lower)
161 {
162 suggestions.push(path.to_string());
163 if suggestions.len() >= 3 {
164 break;
165 }
166 }
167 }
168
169 suggestions
170 }
171
172 pub fn check_trait_impl(&self, type_name: &str, trait_name: &str) -> bool {
179 if self.std_impls.has_impl(type_name, trait_name) {
181 return true;
182 }
183
184 let type_id = self.registry.lookup_by_name(type_name);
186 let trait_id = self.registry.lookup_by_name(trait_name);
187
188 match (type_id, trait_id) {
189 (Some(tid), Some(trid)) => self.graph.implementors_of(trid).any(|id| id == tid),
190 _ => {
191 self.check_generic_std_impl(type_name, trait_name)
194 }
195 }
196 }
197
198 fn check_generic_std_impl(&self, type_name: &str, trait_name: &str) -> bool {
203 let base_type = type_name.split('<').next().unwrap_or(type_name).trim();
205
206 if self.std_impls.is_std_container(base_type) {
207 self.std_impls.has_impl(base_type, trait_name)
210 } else {
211 false
212 }
213 }
214
215 pub fn check_derive_possible(&self, target: &str, trait_name: &str) -> CheckResult {
225 let target_id = match self.registry.lookup_by_name(target) {
227 Some(id) => id,
228 None => {
229 return CheckResult::Error(vec![CheckError::type_not_found(target)]);
230 }
231 };
232
233 let children: Vec<_> = self.graph.children_of(target_id).collect();
235
236 if children.is_empty() {
237 return CheckResult::Ok;
239 }
240
241 let mut missing = Vec::new();
243
244 for child_id in children.into_iter() {
245 if let Some(field_type) = self.get_field_type(child_id) {
247 if !self.check_trait_impl(&field_type, trait_name) {
248 if !self.std_impls.is_primitive(&field_type)
250 && !self.std_impls.is_std_container(&field_type)
251 && self.registry.lookup_by_name(&field_type).is_none()
252 {
253 continue;
256 }
257 if self.registry.lookup_by_name(&field_type).is_some()
277 && !self.std_impls.is_primitive(&field_type)
278 && !self.std_impls.is_std_container(&field_type)
279 {
280 continue;
281 }
282 missing.push(field_type);
283 }
284 }
285 }
286
287 if missing.is_empty() {
288 CheckResult::Ok
289 } else {
290 CheckResult::Error(vec![CheckError::derive_failed(target, trait_name, missing)])
291 }
292 }
293
294 fn get_field_type(&self, field_id: crate::SymbolId) -> Option<String> {
299 for use_id in self.typeflow.types_used_by(field_id) {
301 if let Some(path) = self.registry.resolve(use_id) {
302 return Some(path.name().to_string());
303 }
304 }
305
306 None
307 }
308
309 pub fn check_field_exists(&self, type_name: &str, field_name: &str) -> Result<(), CheckError> {
326 let type_id = match self.registry.lookup_by_name(type_name) {
327 Some(id) => id,
328 None => {
329 return Err(CheckError::type_not_found(type_name));
330 }
331 };
332
333 let children: Vec<_> = self.graph.children_of(type_id).collect();
335 let mut available_fields = Vec::new();
336
337 for child_id in &children {
338 if let Some(path) = self.registry.resolve(*child_id) {
339 let name = path.name();
340 if name == field_name {
341 return Ok(());
342 }
343 available_fields.push(name.to_string());
344 }
345 }
346
347 Err(CheckError::field_not_found(
348 type_name,
349 field_name,
350 available_fields,
351 ))
352 }
353
354 pub fn check_method_exists(
359 &self,
360 type_name: &str,
361 method_name: &str,
362 ) -> Result<(), CheckError> {
363 let type_id = match self.registry.lookup_by_name(type_name) {
365 Some(id) => id,
366 None => {
367 return Err(CheckError::type_not_found(type_name));
368 }
369 };
370
371 let qualified_name = format!("{}::{}", type_name, method_name);
373 if self.registry.lookup_by_name(&qualified_name).is_some() {
374 return Ok(());
375 }
376
377 let mut available_methods = Vec::new();
379 for child_id in self.graph.children_of(type_id) {
380 if let Some(path) = self.registry.resolve(child_id) {
381 let name = path.name();
382 if let Some(kind) = self.registry.kind(child_id) {
384 if matches!(
385 kind,
386 crate::SymbolKind::Function | crate::SymbolKind::Method
387 ) {
388 if name == method_name {
389 return Ok(());
390 }
391 available_methods.push(name.to_string());
392 }
393 }
394 }
395 }
396
397 for (_, path) in self.registry.iter() {
399 let path_str = path.to_string();
400 let impl_pattern_correct = format!("<impl {}>", type_name);
405 if (path_str.contains(&format!("{}::{}", type_name, method_name))
406 || path_str.contains(&impl_pattern_correct))
407 && path.name() == method_name
408 {
409 return Ok(());
410 }
411 if path_str.contains(type_name) {
413 if let Some(kind) = self
414 .registry
415 .lookup(path)
416 .and_then(|id| self.registry.kind(id))
417 {
418 if matches!(
419 kind,
420 crate::SymbolKind::Function | crate::SymbolKind::Method
421 ) && !available_methods.contains(&path.name().to_string())
422 {
423 available_methods.push(path.name().to_string());
424 }
425 }
426 }
427 }
428
429 Err(CheckError::method_not_found(
430 type_name,
431 method_name,
432 available_methods,
433 ))
434 }
435
436 pub fn check_enum_variant_exists(
438 &self,
439 enum_name: &str,
440 variant_name: &str,
441 ) -> Result<(), CheckError> {
442 let enum_id = match self.registry.lookup_by_name(enum_name) {
443 Some(id) => id,
444 None => {
445 return Err(CheckError::type_not_found(enum_name));
446 }
447 };
448
449 let children: Vec<_> = self.graph.children_of(enum_id).collect();
451 let mut available_variants = Vec::new();
452
453 for child_id in &children {
454 if let Some(path) = self.registry.resolve(*child_id) {
455 let name = path.name();
456 if name == variant_name {
457 return Ok(());
458 }
459 available_variants.push(name.to_string());
460 }
461 }
462
463 Err(CheckError::variant_not_found(
464 enum_name,
465 variant_name,
466 available_variants,
467 ))
468 }
469
470 pub fn check_struct_fields_complete(
474 &self,
475 struct_name: &str,
476 provided_fields: &[&str],
477 ) -> Result<(), CheckError> {
478 let struct_id = match self.registry.lookup_by_name(struct_name) {
479 Some(id) => id,
480 None => {
481 return Err(CheckError::type_not_found(struct_name));
482 }
483 };
484
485 let children: Vec<_> = self.graph.children_of(struct_id).collect();
487 let mut missing_fields = Vec::new();
488
489 for child_id in &children {
490 if let Some(path) = self.registry.resolve(*child_id) {
491 let field_name = path.name();
492 if let Some(kind) = self.registry.kind(*child_id) {
494 if matches!(kind, crate::SymbolKind::Field)
495 && !provided_fields.contains(&field_name)
496 {
497 missing_fields.push(field_name.to_string());
498 }
499 }
500 }
501 }
502
503 if missing_fields.is_empty() {
504 Ok(())
505 } else {
506 Err(CheckError::missing_fields(struct_name, missing_fields))
507 }
508 }
509
510 pub fn get_function_arg_count(&self, fn_name: &str) -> Option<usize> {
514 let fn_id = self.registry.lookup_by_name(fn_name)?;
515
516 let param_count = self
518 .graph
519 .children_of(fn_id)
520 .filter(|child_id| {
521 self.registry
522 .kind(*child_id)
523 .map(|k| matches!(k, crate::SymbolKind::Parameter))
524 .unwrap_or(false)
525 })
526 .count();
527
528 Some(param_count)
529 }
530
531 pub fn check_function_arg_count(
533 &self,
534 fn_name: &str,
535 actual_count: usize,
536 ) -> Result<(), CheckError> {
537 match self.get_function_arg_count(fn_name) {
538 Some(expected) if expected != actual_count => Err(CheckError::arg_count_mismatch(
539 fn_name,
540 expected,
541 actual_count,
542 )),
543 Some(_) => Ok(()),
544 None => {
545 Ok(())
547 }
548 }
549 }
550
551 pub fn check_symbols_exist(&self, names: &[&str]) -> CheckResult {
555 let mut errors = Vec::new();
556
557 for name in names {
558 if !self.check_symbol_exists(name) {
559 errors.push(CheckError::unresolved(*name));
560 }
561 }
562
563 if errors.is_empty() {
564 CheckResult::Ok
565 } else {
566 CheckResult::Error(errors)
567 }
568 }
569
570 pub fn check_trait_impls(&self, checks: &[(&str, &str)]) -> CheckResult {
572 let mut errors = Vec::new();
573
574 for (type_name, trait_name) in checks {
575 if !self.check_trait_impl(type_name, trait_name) {
576 errors.push(CheckError::trait_not_impl(*type_name, *trait_name));
577 }
578 }
579
580 if errors.is_empty() {
581 CheckResult::Ok
582 } else {
583 CheckResult::Error(errors)
584 }
585 }
586
587 pub fn graph(&self) -> &CodeGraphV2 {
591 self.graph
592 }
593
594 pub fn registry(&self) -> &SymbolRegistry {
596 self.registry
597 }
598
599 pub fn std_impls(&self) -> &StdImplCache {
601 &self.std_impls
602 }
603}
604
605impl LightCheck for GraphChecker<'_> {
606 fn check_symbol_exists(&self, name: &str) -> bool {
607 GraphChecker::check_symbol_exists(self, name)
608 }
609
610 fn check_trait_impl(&self, type_name: &str, trait_name: &str) -> bool {
611 GraphChecker::check_trait_impl(self, type_name, trait_name)
612 }
613
614 fn check_derive_possible(&self, target: &str, trait_name: &str) -> CheckResult {
615 GraphChecker::check_derive_possible(self, target, trait_name)
616 }
617
618 fn pre_check(&self, mutation_type: &str, target: &str) -> CheckResult {
619 match mutation_type {
620 "AddDerive" => {
621 if !self.check_symbol_exists(target) {
623 return CheckResult::Error(vec![CheckError::type_not_found(target)]);
624 }
625 CheckResult::Ok
626 }
627 "RenameSymbol" => {
628 if !self.check_symbol_exists(target) {
630 return CheckResult::Error(vec![CheckError::unresolved(target)]);
631 }
632 CheckResult::Ok
633 }
634 _ => CheckResult::Ok,
635 }
636 }
637
638 fn post_check(&self, mutation_type: &str, target: &str) -> CheckResult {
639 match mutation_type {
640 "RenameSymbol" => {
641 if !self.check_symbol_exists(target) {
643 return CheckResult::Error(vec![CheckError::unresolved(target)]);
644 }
645 CheckResult::Ok
646 }
647 _ => CheckResult::Ok,
648 }
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655 use crate::query::GraphBuilderV2;
656 use crate::SymbolKind;
657
658 fn setup_test_env() -> (CodeGraphV2, TypeFlowGraphV2, SymbolRegistry) {
659 let mut registry = SymbolRegistry::new();
660 let mut builder = GraphBuilderV2::new(&mut registry);
661
662 let my_struct = builder
664 .add_symbol(
665 SymbolPath::parse("test::MyStruct").unwrap(),
666 SymbolKind::Struct,
667 )
668 .unwrap();
669 let field = builder
670 .add_symbol(
671 SymbolPath::parse("test::MyStruct::field").unwrap(),
672 SymbolKind::Field,
673 )
674 .unwrap();
675 let i32_type = builder
676 .add_symbol(
677 SymbolPath::parse("std::i32").unwrap(),
678 SymbolKind::TypeAlias,
679 )
680 .unwrap();
681
682 builder.add_contains(my_struct, field);
683
684 let graph = builder.build();
685 let mut typeflow = TypeFlowGraphV2::new();
686 typeflow.add_usage(
688 crate::query::UsageContext::FieldType,
689 crate::query::RefKind::Owned,
690 Some(i32_type),
691 Some(field),
692 );
693 (graph, typeflow, registry)
694 }
695
696 #[test]
697 fn test_check_symbol_exists() {
698 let (graph, typeflow, registry) = setup_test_env();
699 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
700
701 assert!(checker.check_symbol_exists("test::MyStruct"));
703
704 assert!(checker.check_symbol_exists("MyStruct"));
706
707 assert!(!checker.check_symbol_exists("NonExistent"));
709 }
710
711 #[test]
712 fn test_check_trait_impl_primitives() {
713 let (graph, typeflow, registry) = setup_test_env();
714 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
715
716 assert!(checker.check_trait_impl("i32", "Clone"));
718 assert!(checker.check_trait_impl("i32", "Default"));
719 assert!(checker.check_trait_impl("bool", "Eq"));
720
721 assert!(!checker.check_trait_impl("f64", "Eq"));
723 }
724
725 #[test]
726 fn test_check_trait_impl_std_types() {
727 let (graph, typeflow, registry) = setup_test_env();
728 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
729
730 assert!(checker.check_trait_impl("String", "Clone"));
731 assert!(checker.check_trait_impl("Vec", "Default"));
732 assert!(checker.check_trait_impl("HashMap", "Default"));
733 }
734
735 #[test]
736 fn test_batch_symbol_check() {
737 let (graph, typeflow, registry) = setup_test_env();
738 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
739
740 let result = checker.check_symbols_exist(&["MyStruct", "field"]);
742 assert!(result.is_ok());
743
744 let result = checker.check_symbols_exist(&["MyStruct", "NonExistent"]);
746 assert!(result.is_err());
747 assert_eq!(result.errors().len(), 1);
748 }
749
750 fn setup_extended_test_env() -> (CodeGraphV2, TypeFlowGraphV2, SymbolRegistry) {
753 let mut registry = SymbolRegistry::new();
754 let mut builder = GraphBuilderV2::new(&mut registry);
755
756 let payment = builder
758 .add_symbol(
759 SymbolPath::parse("test::Payment").unwrap(),
760 SymbolKind::Struct,
761 )
762 .unwrap();
763 let amount_field = builder
764 .add_symbol(
765 SymbolPath::parse("test::Payment::amount").unwrap(),
766 SymbolKind::Field,
767 )
768 .unwrap();
769 let processed_at_field = builder
770 .add_symbol(
771 SymbolPath::parse("test::Payment::processed_at").unwrap(),
772 SymbolKind::Field,
773 )
774 .unwrap();
775 builder.add_contains(payment, amount_field);
776 builder.add_contains(payment, processed_at_field);
777
778 let order_id = builder
780 .add_symbol(
781 SymbolPath::parse("test::OrderId").unwrap(),
782 SymbolKind::Struct,
783 )
784 .unwrap();
785 let new_method = builder
786 .add_symbol(
787 SymbolPath::parse("test::OrderId::new").unwrap(),
788 SymbolKind::Function,
789 )
790 .unwrap();
791 builder.add_contains(order_id, new_method);
792
793 let status = builder
795 .add_symbol(
796 SymbolPath::parse("test::ProductStatus").unwrap(),
797 SymbolKind::Enum,
798 )
799 .unwrap();
800 let active = builder
801 .add_symbol(
802 SymbolPath::parse("test::ProductStatus::Active").unwrap(),
803 SymbolKind::Variant,
804 )
805 .unwrap();
806 let discontinued = builder
807 .add_symbol(
808 SymbolPath::parse("test::ProductStatus::Discontinued").unwrap(),
809 SymbolKind::Variant,
810 )
811 .unwrap();
812 builder.add_contains(status, active);
813 builder.add_contains(status, discontinued);
814
815 let graph = builder.build();
816 let typeflow = TypeFlowGraphV2::new();
817 (graph, typeflow, registry)
818 }
819
820 #[test]
821 fn test_check_field_exists() {
822 let (graph, typeflow, registry) = setup_extended_test_env();
823 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
824
825 assert!(checker.check_field_exists("Payment", "amount").is_ok());
827 assert!(checker
828 .check_field_exists("Payment", "processed_at")
829 .is_ok());
830
831 let result = checker.check_field_exists("Payment", "created_at");
833 assert!(result.is_err());
834 if let Err(CheckError::FieldNotFound {
835 type_name,
836 field_name,
837 available_fields,
838 }) = result
839 {
840 assert_eq!(type_name, "Payment");
841 assert_eq!(field_name, "created_at");
842 assert!(available_fields.contains(&"amount".to_string()));
843 assert!(available_fields.contains(&"processed_at".to_string()));
844 } else {
845 panic!("Expected FieldNotFound error");
846 }
847
848 assert!(matches!(
850 checker.check_field_exists("NonExistent", "field"),
851 Err(CheckError::TypeNotFound { .. })
852 ));
853 }
854
855 #[test]
856 fn test_check_method_exists() {
857 let (graph, typeflow, registry) = setup_extended_test_env();
858 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
859
860 assert!(checker.check_method_exists("OrderId", "new").is_ok());
862
863 let result = checker.check_method_exists("OrderId", "as_u64");
865 assert!(result.is_err());
866 if let Err(CheckError::MethodNotFound {
867 type_name,
868 method_name,
869 ..
870 }) = result
871 {
872 assert_eq!(type_name, "OrderId");
873 assert_eq!(method_name, "as_u64");
874 } else {
875 panic!("Expected MethodNotFound error");
876 }
877 }
878
879 #[test]
880 fn test_check_enum_variant_exists() {
881 let (graph, typeflow, registry) = setup_extended_test_env();
882 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
883
884 assert!(checker
886 .check_enum_variant_exists("ProductStatus", "Active")
887 .is_ok());
888 assert!(checker
889 .check_enum_variant_exists("ProductStatus", "Discontinued")
890 .is_ok());
891
892 let result = checker.check_enum_variant_exists("ProductStatus", "Inactive");
894 assert!(result.is_err());
895 if let Err(CheckError::EnumVariantNotFound {
896 enum_name,
897 variant_name,
898 available_variants,
899 }) = result
900 {
901 assert_eq!(enum_name, "ProductStatus");
902 assert_eq!(variant_name, "Inactive");
903 assert!(available_variants.contains(&"Active".to_string()));
904 assert!(available_variants.contains(&"Discontinued".to_string()));
905 } else {
906 panic!("Expected EnumVariantNotFound error");
907 }
908 }
909
910 #[test]
911 fn test_check_struct_fields_complete() {
912 let (graph, typeflow, registry) = setup_extended_test_env();
913 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
914
915 let result = checker.check_struct_fields_complete("Payment", &["amount", "processed_at"]);
917 assert!(result.is_ok());
918
919 let result = checker.check_struct_fields_complete("Payment", &["amount"]);
921 assert!(result.is_err());
922 if let Err(CheckError::MissingRequiredField {
923 struct_name,
924 missing_fields,
925 }) = result
926 {
927 assert_eq!(struct_name, "Payment");
928 assert!(missing_fields.contains(&"processed_at".to_string()));
929 } else {
930 panic!("Expected MissingRequiredField error");
931 }
932 }
933
934 #[test]
944 fn test_check_derive_possible_user_defined_field_and_std_trait_skips() {
945 let mut registry = SymbolRegistry::new();
946 let mut builder = GraphBuilderV2::new(&mut registry);
947
948 let target = builder
952 .add_symbol(
953 SymbolPath::parse("test::Holder").unwrap(),
954 SymbolKind::Struct,
955 )
956 .unwrap();
957 let field = builder
958 .add_symbol(
959 SymbolPath::parse("test::Holder::inner").unwrap(),
960 SymbolKind::Field,
961 )
962 .unwrap();
963 let inner_ty = builder
964 .add_symbol(SymbolPath::parse("test::Inner").unwrap(), SymbolKind::Enum)
965 .unwrap();
966
967 builder.add_contains(target, field);
968
969 let graph = builder.build();
970 let mut typeflow = TypeFlowGraphV2::new();
971 typeflow.add_usage(
972 crate::query::UsageContext::FieldType,
973 crate::query::RefKind::Owned,
974 Some(inner_ty),
975 Some(field),
976 );
977
978 let checker = GraphChecker::new(&graph, &typeflow, ®istry);
979
980 let result = checker.check_derive_possible("Holder", "Debug");
983 assert!(
984 matches!(result, CheckResult::Ok),
985 "user-defined field + unindexed std trait must conservatively pass, got {:?}",
986 result
987 );
988 }
989
990 #[test]
991 fn test_check_error_display() {
992 let err = CheckError::field_not_found("Payment", "created_at", vec!["amount".into()]);
994 let msg = format!("{}", err);
995 assert!(msg.contains("field `created_at` not found"));
996 assert!(msg.contains("Payment"));
997 assert!(msg.contains("amount"));
998
999 let err = CheckError::method_not_found("OrderId", "as_u64", vec!["new".into()]);
1000 let msg = format!("{}", err);
1001 assert!(msg.contains("method `as_u64` not found"));
1002
1003 let err = CheckError::variant_not_found("Status", "Inactive", vec!["Active".into()]);
1004 let msg = format!("{}", err);
1005 assert!(msg.contains("variant `Inactive` not found"));
1006
1007 let err = CheckError::missing_fields("User", vec!["status".into(), "email".into()]);
1008 let msg = format!("{}", err);
1009 assert!(msg.contains("missing required field"));
1010 assert!(msg.contains("status"));
1011
1012 let err = CheckError::arg_count_mismatch("new", 0, 1);
1013 let msg = format!("{}", err);
1014 assert!(msg.contains("expects 0 argument"));
1015 assert!(msg.contains("1 provided"));
1016 }
1017}