1use std::collections::HashMap;
8
9use super::types::{FieldDescriptor, FieldType, Operator};
10
11#[derive(Debug, Clone)]
26pub struct FieldRegistry {
27 fields: HashMap<String, FieldDescriptor>,
29 aliases: HashMap<String, String>,
31}
32
33impl FieldRegistry {
34 #[must_use]
36 pub fn new() -> Self {
37 Self {
38 fields: HashMap::new(),
39 aliases: HashMap::new(),
40 }
41 }
42
43 #[must_use]
55 pub fn with_core_fields() -> Self {
56 let mut registry = Self::new();
57
58 for field in core_fields() {
59 registry.add_field(field);
60 }
61
62 registry.add_alias("file", "path"); registry.add_alias("language", "lang"); registry
67 }
68
69 pub fn add_field(&mut self, descriptor: FieldDescriptor) {
73 self.fields.insert(descriptor.name.to_string(), descriptor);
74 }
75
76 pub fn add_alias(&mut self, alias: impl Into<String>, canonical: impl Into<String>) {
90 let alias = alias.into();
91 let canonical = canonical.into();
92
93 assert!(
95 self.fields.contains_key(&canonical),
96 "Cannot add alias '{alias}' -> '{canonical}': canonical field '{canonical}' does not exist"
97 );
98
99 self.aliases.insert(alias, canonical);
100 }
101
102 #[inline]
106 #[must_use]
107 pub fn get(&self, name: &str) -> Option<&FieldDescriptor> {
108 if let Some(descriptor) = self.fields.get(name) {
110 return Some(descriptor);
111 }
112
113 if let Some(canonical) = self.aliases.get(name) {
115 return self.fields.get(canonical);
116 }
117
118 None
119 }
120
121 pub fn resolve_canonical<'a>(&'a self, name: &'a str) -> Option<&'a str> {
128 if self.fields.contains_key(name) {
130 return Some(name);
131 }
132
133 self.aliases.get(name).map(std::string::String::as_str)
135 }
136
137 pub fn field_names(&self) -> Vec<&str> {
139 self.fields
140 .keys()
141 .map(std::string::String::as_str)
142 .collect()
143 }
144
145 #[inline]
147 #[must_use]
148 pub fn contains(&self, name: &str) -> bool {
149 self.fields.contains_key(name) || self.aliases.contains_key(name)
150 }
151
152 #[must_use]
154 pub fn len(&self) -> usize {
155 self.fields.len()
156 }
157
158 #[must_use]
160 pub fn is_empty(&self) -> bool {
161 self.fields.is_empty()
162 }
163
164 #[must_use = "collision information should be logged or handled"]
186 pub fn add_plugin_fields(&mut self, fields: &[FieldDescriptor]) -> Vec<String> {
187 let mut collisions = Vec::new();
188
189 for field in fields {
190 if self.contains(field.name) {
191 log::debug!(
193 "Plugin field collision: '{}' already exists, skipping plugin version",
194 field.name
195 );
196 collisions.push(field.name.to_string());
197 } else {
198 log::debug!("Registering plugin field: '{}'", field.name);
200 self.add_field(field.clone());
201 }
202 }
203
204 if collisions.is_empty() {
205 log::debug!(
206 "Plugin field registration complete: {} fields added, no collisions",
207 fields.len()
208 );
209 } else {
210 log::debug!(
211 "Plugin field registration complete: {} fields added, {} collisions ({})",
212 fields.len() - collisions.len(),
213 collisions.len(),
214 collisions.join(", ")
215 );
216 }
217
218 collisions
219 }
220
221 #[must_use]
230 pub fn plugin_ids(&self) -> Vec<String> {
231 Vec::new()
233 }
234}
235
236impl Default for FieldRegistry {
237 fn default() -> Self {
238 Self::with_core_fields()
239 }
240}
241
242#[must_use]
261#[allow(clippy::too_many_lines)] pub fn core_fields() -> Vec<FieldDescriptor> {
263 vec![
264 FieldDescriptor {
265 name: "kind",
266 field_type: FieldType::Enum(vec![
267 "function",
268 "method",
269 "class",
270 "struct",
271 "trait",
272 "enum",
273 "interface",
274 "module",
275 "variable",
276 "constant",
277 "type",
278 "namespace",
279 "property",
280 "parameter",
281 "import",
282 ]),
283 operators: &[Operator::Equal, Operator::Regex],
284 indexed: true,
285 doc: "Node type: function, class, method, struct, etc.",
286 },
287 FieldDescriptor {
288 name: "name",
289 field_type: FieldType::String,
290 operators: &[Operator::Equal, Operator::Regex],
291 indexed: true,
292 doc: "Node name (exact match with ':' or regex with '~=')",
293 },
294 FieldDescriptor {
295 name: "path",
296 field_type: FieldType::Path,
297 operators: &[Operator::Equal, Operator::Regex],
298 indexed: true,
299 doc: "File path (glob pattern with ':' or regex with '~=')",
300 },
301 FieldDescriptor {
302 name: "lang",
303 field_type: FieldType::String,
304 operators: &[Operator::Equal, Operator::Regex],
305 indexed: true,
306 doc: "Programming language",
307 },
308 FieldDescriptor {
309 name: "repo",
310 field_type: FieldType::String,
311 operators: &[Operator::Equal],
312 indexed: false,
313 doc: "Repository filter (glob pattern, e.g., 'repo:backend-*'). Used in workspace queries to filter results by repository name.",
314 },
315 FieldDescriptor {
316 name: "parent",
317 field_type: FieldType::String,
318 operators: &[Operator::Equal, Operator::Regex],
319 indexed: false,
320 doc: "Parent symbol name (exact match with ':' or regex with '~='). Matches symbols with a specific parent (e.g., methods in a class).",
321 },
322 FieldDescriptor {
323 name: "scope",
324 field_type: FieldType::Enum(vec!["file", "module", "class", "function", "block"]),
325 operators: &[Operator::Equal],
326 indexed: false,
327 doc: "Scope type: file, module, class, function, block",
328 },
329 FieldDescriptor {
330 name: "text",
331 field_type: FieldType::String,
332 operators: &[Operator::Regex],
333 indexed: false,
334 doc: "Full-text search in symbol body (code + comments). NOT indexed - use indexed fields first for performance!",
335 },
336 FieldDescriptor {
338 name: "callers",
339 field_type: FieldType::String,
340 operators: &[Operator::Equal],
341 indexed: true,
342 doc: "Match symbols that call the specified function (requires index with relations)",
343 },
344 FieldDescriptor {
345 name: "callees",
346 field_type: FieldType::String,
347 operators: &[Operator::Equal],
348 indexed: true,
349 doc: "Match symbols called by the specified function (requires index with relations)",
350 },
351 FieldDescriptor {
352 name: "imports",
353 field_type: FieldType::String,
354 operators: &[Operator::Equal],
355 indexed: true,
356 doc: "Match files that import the specified module (requires index with relations)",
357 },
358 FieldDescriptor {
359 name: "exports",
360 field_type: FieldType::String,
361 operators: &[Operator::Equal],
362 indexed: true,
363 doc: "Match files that export the specified symbol (requires index with relations)",
364 },
365 FieldDescriptor {
366 name: "returns",
367 field_type: FieldType::String,
368 operators: &[Operator::Equal, Operator::Regex],
369 indexed: true,
370 doc: "Match functions with the specified return type (requires index with type metadata)",
371 },
372 FieldDescriptor {
374 name: "impl",
375 field_type: FieldType::String,
376 operators: &[Operator::Equal],
377 indexed: true,
378 doc: "Match types implementing a trait (e.g., impl:Debug finds types that implement Debug)",
379 },
380 FieldDescriptor {
382 name: "references",
383 field_type: FieldType::String,
384 operators: &[Operator::Equal],
385 indexed: true,
386 doc: "Match symbols that have cross-file references (requires graph reference edges). Example: references:connect finds symbols named 'connect' that are referenced elsewhere.",
387 },
388 FieldDescriptor {
390 name: "scope.type",
391 field_type: FieldType::Enum(vec![
392 "module",
393 "function",
394 "class",
395 "struct",
396 "method",
397 "block",
398 "namespace",
399 "trait",
400 "impl",
401 "interface",
402 "enum",
403 ]),
404 operators: &[Operator::Equal, Operator::Regex],
405 indexed: false,
406 doc: "Type of the scope containing this symbol (e.g., 'function', 'class', 'module'). Evaluated via sequential scan over file_scopes + scope_id.",
407 },
408 FieldDescriptor {
409 name: "scope.name",
410 field_type: FieldType::String,
411 operators: &[Operator::Equal, Operator::Regex],
412 indexed: false,
413 doc: "Name of the scope containing this symbol (e.g., 'UserService', 'connect'). Evaluated via sequential scan over file_scopes + scope_id.",
414 },
415 FieldDescriptor {
416 name: "scope.parent",
417 field_type: FieldType::String,
418 operators: &[Operator::Equal, Operator::Regex],
419 indexed: false,
420 doc: "Name of the parent scope (immediate containing scope). Evaluated via sequential scan over file_scopes + scope_id.",
421 },
422 FieldDescriptor {
423 name: "scope.ancestor",
424 field_type: FieldType::String,
425 operators: &[Operator::Equal, Operator::Regex],
426 indexed: false,
427 doc: "Name of any ancestor scope (walks up the scope hierarchy). Evaluated via sequential scan over file_scopes + scope_id.",
428 },
429 FieldDescriptor {
431 name: "duplicates",
432 field_type: FieldType::Enum(vec!["body", "function", "signature", "struct"]),
433 operators: &[Operator::Equal],
434 indexed: false,
435 doc: "Find duplicate code: duplicates:body (function bodies), duplicates:signature (return types), duplicates:struct (struct layouts)",
436 },
437 FieldDescriptor {
438 name: "unused",
439 field_type: FieldType::Enum(vec!["public", "private", "function", "struct", "all"]),
440 operators: &[Operator::Equal],
441 indexed: false,
442 doc: "Find unused symbols: unused:public, unused:private, unused:function, unused:struct, or unused:all",
443 },
444 FieldDescriptor {
445 name: "circular",
446 field_type: FieldType::Enum(vec!["calls", "imports", "all"]),
447 operators: &[Operator::Equal],
448 indexed: false,
449 doc: "Find circular dependencies: circular:calls (mutual recursion), circular:imports (import cycles), circular:all",
450 },
451 FieldDescriptor {
453 name: "async",
454 field_type: FieldType::Bool,
455 operators: &[Operator::Equal],
456 indexed: false,
457 doc: "Whether the function/method is async: async:true or async:false",
458 },
459 FieldDescriptor {
460 name: "static",
461 field_type: FieldType::Bool,
462 operators: &[Operator::Equal],
463 indexed: false,
464 doc: "Whether the member is static: static:true or static:false",
465 },
466 FieldDescriptor {
467 name: "visibility",
468 field_type: FieldType::String,
469 operators: &[Operator::Equal],
470 indexed: false,
471 doc: "Visibility modifier: visibility:pub, visibility:private, etc.",
472 },
473 FieldDescriptor {
474 name: "returns",
475 field_type: FieldType::String,
476 operators: &[Operator::Equal],
477 indexed: false,
478 doc: "Return type filter: returns:bool, returns:Optional<User>, etc. Uses substring matching.",
479 },
480 ]
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn test_new_registry_is_empty() {
489 let registry = FieldRegistry::new();
490 assert!(registry.is_empty());
491 assert_eq!(registry.len(), 0);
492 }
493
494 #[test]
495 fn test_with_core_fields() {
496 let registry = FieldRegistry::with_core_fields();
497 assert!(!registry.is_empty());
498 assert_eq!(registry.len(), 25); }
500
501 #[test]
502 fn test_default_has_core_fields() {
503 let registry = FieldRegistry::default();
504 assert_eq!(registry.len(), 25); }
506
507 #[test]
508 fn test_core_field_kind_exists() {
509 let registry = FieldRegistry::with_core_fields();
510 let kind_field = registry.get("kind");
511 assert!(kind_field.is_some());
512
513 let kind = kind_field.unwrap();
514 assert_eq!(kind.name, "kind");
515 assert!(kind.indexed);
516 assert!(kind.supports_operator(&Operator::Equal));
517 assert!(kind.supports_operator(&Operator::Regex));
518 assert!(!kind.supports_operator(&Operator::Greater));
519 }
520
521 #[test]
522 fn test_core_field_name_exists() {
523 let registry = FieldRegistry::with_core_fields();
524 let name_field = registry.get("name");
525 assert!(name_field.is_some());
526
527 let name = name_field.unwrap();
528 assert_eq!(name.name, "name");
529 assert!(name.indexed);
530 assert!(matches!(name.field_type, FieldType::String));
531 }
532
533 #[test]
534 fn test_core_field_path_exists() {
535 let registry = FieldRegistry::with_core_fields();
536 let path_field = registry.get("path");
537 assert!(path_field.is_some());
538
539 let path = path_field.unwrap();
540 assert_eq!(path.name, "path");
541 assert!(path.indexed);
542 assert!(matches!(path.field_type, FieldType::Path));
543 }
544
545 #[test]
546 fn test_core_field_lang_exists() {
547 let registry = FieldRegistry::with_core_fields();
548 let lang_field = registry.get("lang");
549 assert!(lang_field.is_some());
550
551 let lang = lang_field.unwrap();
552 assert_eq!(lang.name, "lang");
553 assert!(lang.indexed);
554 assert!(matches!(lang.field_type, FieldType::String));
555 assert!(lang.supports_operator(&Operator::Equal));
556 assert!(lang.supports_operator(&Operator::Regex));
557 }
558
559 #[test]
560 fn test_core_field_scope_exists() {
561 let registry = FieldRegistry::with_core_fields();
562 let scope_field = registry.get("scope");
563 assert!(scope_field.is_some());
564
565 let scope = scope_field.unwrap();
566 assert_eq!(scope.name, "scope");
567 assert!(!scope.indexed); }
569
570 #[test]
571 fn test_core_field_text_exists() {
572 let registry = FieldRegistry::with_core_fields();
573 let text_field = registry.get("text");
574 assert!(text_field.is_some());
575
576 let text = text_field.unwrap();
577 assert_eq!(text.name, "text");
578 assert!(!text.indexed); assert!(text.supports_operator(&Operator::Regex));
580 assert!(!text.supports_operator(&Operator::Equal));
581 }
582
583 #[test]
584 fn test_get_nonexistent_field() {
585 let registry = FieldRegistry::with_core_fields();
586 assert!(registry.get("nonexistent").is_none());
587 }
588
589 #[test]
590 fn test_add_custom_field() {
591 let mut registry = FieldRegistry::new();
592
593 let custom_field = FieldDescriptor {
594 name: "async",
595 field_type: FieldType::Bool,
596 operators: &[Operator::Equal],
597 indexed: false,
598 doc: "Whether function is async",
599 };
600
601 registry.add_field(custom_field);
602
603 assert_eq!(registry.len(), 1);
604 assert!(registry.contains("async"));
605
606 let async_field = registry.get("async").unwrap();
607 assert_eq!(async_field.name, "async");
608 assert!(matches!(async_field.field_type, FieldType::Bool));
609 }
610
611 #[test]
612 fn test_add_field_replaces_existing() {
613 let mut registry = FieldRegistry::with_core_fields();
614 let original_len = registry.len();
615
616 let custom_kind = FieldDescriptor {
618 name: "kind",
619 field_type: FieldType::String,
620 operators: &[Operator::Equal],
621 indexed: false,
622 doc: "Custom kind field",
623 };
624
625 registry.add_field(custom_kind);
626
627 assert_eq!(registry.len(), original_len);
629
630 let kind_field = registry.get("kind").unwrap();
631 assert!(matches!(kind_field.field_type, FieldType::String));
632 assert!(!kind_field.indexed);
633 }
634
635 #[test]
636 fn test_field_names() {
637 let registry = FieldRegistry::with_core_fields();
638 let names = registry.field_names();
639
640 assert_eq!(names.len(), 25); assert!(names.contains(&"kind"));
642 assert!(names.contains(&"name"));
643 assert!(names.contains(&"path"));
644 assert!(names.contains(&"lang"));
645 assert!(names.contains(&"scope"));
646 assert!(names.contains(&"text"));
647 assert!(names.contains(&"callers"));
648 assert!(names.contains(&"callees"));
649 assert!(names.contains(&"imports"));
650 assert!(names.contains(&"exports"));
651 assert!(names.contains(&"returns"));
652 assert!(names.contains(&"references"));
653 assert!(names.contains(&"repo"));
654 assert!(names.contains(&"parent"));
655 assert!(names.contains(&"scope.type")); assert!(names.contains(&"scope.name")); assert!(names.contains(&"scope.parent")); assert!(names.contains(&"scope.ancestor")); }
660
661 #[test]
662 fn test_contains() {
663 let registry = FieldRegistry::with_core_fields();
664
665 assert!(registry.contains("kind"));
666 assert!(registry.contains("name"));
667 assert!(registry.contains("async"));
669 assert!(registry.contains("static"));
670 assert!(registry.contains("visibility"));
671 assert!(!registry.contains("nonexistent_field"));
672 }
673
674 #[test]
675 fn test_kind_enum_values() {
676 let registry = FieldRegistry::with_core_fields();
677 let kind_field = registry.get("kind").unwrap();
678
679 if let FieldType::Enum(values) = &kind_field.field_type {
680 assert!(values.contains(&"function"));
681 assert!(values.contains(&"method"));
682 assert!(values.contains(&"class"));
683 assert!(values.contains(&"struct"));
684 assert!(values.contains(&"trait"));
685 assert!(values.contains(&"enum"));
686 assert!(values.contains(&"interface"));
687 assert!(values.contains(&"module"));
688 } else {
689 panic!("Expected Enum field type for 'kind'");
690 }
691 }
692
693 #[test]
694 fn test_lang_enum_values() {
695 let registry = FieldRegistry::with_core_fields();
696 let lang_field = registry.get("lang").unwrap();
697
698 assert!(matches!(lang_field.field_type, FieldType::String));
699 assert!(lang_field.supports_operator(&Operator::Equal));
700 assert!(lang_field.supports_operator(&Operator::Regex));
701 }
702
703 #[test]
704 fn test_scope_enum_values() {
705 let registry = FieldRegistry::with_core_fields();
706 let scope_field = registry.get("scope").unwrap();
707
708 if let FieldType::Enum(values) = &scope_field.field_type {
709 assert!(values.contains(&"file"));
710 assert!(values.contains(&"module"));
711 assert!(values.contains(&"class"));
712 assert!(values.contains(&"function"));
713 assert!(values.contains(&"block"));
714 assert_eq!(values.len(), 5);
715 } else {
716 panic!("Expected Enum field type for 'scope'");
717 }
718 }
719
720 #[test]
723 fn test_add_plugin_fields_no_collision() {
724 let mut registry = FieldRegistry::with_core_fields();
725 let original_len = registry.len();
726
727 let plugin_fields = vec![
729 FieldDescriptor {
730 name: "plugin_async",
731 field_type: FieldType::Bool,
732 operators: &[Operator::Equal],
733 indexed: false,
734 doc: "Plugin-specific async field",
735 },
736 FieldDescriptor {
737 name: "plugin_visibility",
738 field_type: FieldType::String,
739 operators: &[Operator::Equal],
740 indexed: false,
741 doc: "Plugin-specific visibility field",
742 },
743 ];
744
745 let collisions = registry.add_plugin_fields(&plugin_fields);
746
747 assert_eq!(collisions.len(), 0);
749
750 assert_eq!(registry.len(), original_len + 2);
752 assert!(registry.contains("plugin_async"));
753 assert!(registry.contains("plugin_visibility"));
754 }
755
756 #[test]
757 fn test_add_plugin_fields_with_collision() {
758 let mut registry = FieldRegistry::with_core_fields();
759 let original_len = registry.len();
760
761 let plugin_fields = vec![
762 FieldDescriptor {
763 name: "kind", field_type: FieldType::String,
765 operators: &[Operator::Equal],
766 indexed: false,
767 doc: "Custom kind field",
768 },
769 FieldDescriptor {
770 name: "plugin_unique",
771 field_type: FieldType::Bool,
772 operators: &[Operator::Equal],
773 indexed: false,
774 doc: "Unique plugin field",
775 },
776 ];
777
778 let collisions = registry.add_plugin_fields(&plugin_fields);
779
780 assert_eq!(collisions.len(), 1);
782 assert_eq!(collisions[0], "kind");
783
784 assert_eq!(registry.len(), original_len + 1);
786 assert!(registry.contains("plugin_unique"));
787
788 let kind_field = registry.get("kind").unwrap();
790 assert!(matches!(kind_field.field_type, FieldType::Enum(_)));
791 assert!(kind_field.indexed);
792 }
793
794 #[test]
795 fn test_add_plugin_fields_all_collisions() {
796 let mut registry = FieldRegistry::with_core_fields();
797 let original_len = registry.len();
798
799 let plugin_fields = vec![
800 FieldDescriptor {
801 name: "kind",
802 field_type: FieldType::String,
803 operators: &[Operator::Equal],
804 indexed: false,
805 doc: "Custom kind",
806 },
807 FieldDescriptor {
808 name: "name",
809 field_type: FieldType::String,
810 operators: &[Operator::Equal],
811 indexed: false,
812 doc: "Custom name",
813 },
814 ];
815
816 let collisions = registry.add_plugin_fields(&plugin_fields);
817
818 assert_eq!(collisions.len(), 2);
820 assert!(collisions.contains(&"kind".to_string()));
821 assert!(collisions.contains(&"name".to_string()));
822
823 assert_eq!(registry.len(), original_len);
825 }
826
827 #[test]
828 fn test_plugin_ids_placeholder() {
829 let registry = FieldRegistry::with_core_fields();
830 let ids = registry.plugin_ids();
831
832 assert_eq!(ids.len(), 0);
834 }
835
836 #[test]
839 fn test_alias_file_to_path() {
840 let registry = FieldRegistry::with_core_fields();
841
842 let via_alias = registry.get("file");
844 let direct = registry.get("path");
845
846 assert!(via_alias.is_some());
847 assert!(direct.is_some());
848
849 assert_eq!(via_alias.unwrap().name, direct.unwrap().name);
851 assert_eq!(via_alias.unwrap().name, "path");
852 }
853
854 #[test]
855 fn test_alias_language_to_lang() {
856 let registry = FieldRegistry::with_core_fields();
857
858 let via_alias = registry.get("language");
860 let direct = registry.get("lang");
861
862 assert!(via_alias.is_some());
863 assert!(direct.is_some());
864
865 assert_eq!(via_alias.unwrap().name, direct.unwrap().name);
867 assert_eq!(via_alias.unwrap().name, "lang");
868 }
869
870 #[test]
871 fn test_contains_recognizes_aliases() {
872 let registry = FieldRegistry::with_core_fields();
873
874 assert!(registry.contains("path"));
876 assert!(registry.contains("lang"));
877
878 assert!(registry.contains("file"));
880 assert!(registry.contains("language"));
881 }
882
883 #[test]
884 fn test_resolve_canonical_with_direct_field() {
885 let registry = FieldRegistry::with_core_fields();
886
887 assert_eq!(registry.resolve_canonical("kind"), Some("kind"));
888 assert_eq!(registry.resolve_canonical("path"), Some("path"));
889 }
890
891 #[test]
892 fn test_resolve_canonical_with_alias() {
893 let registry = FieldRegistry::with_core_fields();
894
895 assert_eq!(registry.resolve_canonical("file"), Some("path"));
896 assert_eq!(registry.resolve_canonical("language"), Some("lang"));
897 }
898
899 #[test]
900 fn test_resolve_canonical_with_nonexistent() {
901 let registry = FieldRegistry::with_core_fields();
902
903 assert_eq!(registry.resolve_canonical("nonexistent"), None);
904 assert_eq!(registry.resolve_canonical("unknown_alias"), None);
905 }
906
907 #[test]
908 fn test_add_alias_to_custom_field() {
909 let mut registry = FieldRegistry::new();
910
911 let custom_field = FieldDescriptor {
912 name: "async",
913 field_type: FieldType::Bool,
914 operators: &[Operator::Equal],
915 indexed: false,
916 doc: "Whether function is async",
917 };
918
919 registry.add_field(custom_field);
920 registry.add_alias("is_async", "async");
921
922 assert!(registry.contains("async"));
924 assert!(registry.contains("is_async"));
925
926 let via_alias = registry.get("is_async").unwrap();
927 assert_eq!(via_alias.name, "async");
928 }
929
930 #[test]
931 #[should_panic(expected = "canonical field 'nonexistent' does not exist")]
932 fn test_add_alias_to_nonexistent_field_panics() {
933 let mut registry = FieldRegistry::new();
934 registry.add_alias("bad_alias", "nonexistent");
935 }
936
937 #[test]
938 fn test_core_field_repo_exists() {
939 let registry = FieldRegistry::with_core_fields();
940 let repo_field = registry.get("repo");
941 assert!(repo_field.is_some());
942
943 let repo = repo_field.unwrap();
944 assert_eq!(repo.name, "repo");
945 assert!(!repo.indexed); assert!(matches!(repo.field_type, FieldType::String));
947 assert!(repo.supports_operator(&Operator::Equal));
948 assert!(!repo.supports_operator(&Operator::Greater));
949 }
950
951 #[test]
952 fn test_core_field_parent_exists() {
953 let registry = FieldRegistry::with_core_fields();
954 let parent_field = registry.get("parent");
955 assert!(parent_field.is_some());
956
957 let parent = parent_field.unwrap();
958 assert_eq!(parent.name, "parent");
959 assert!(!parent.indexed); assert!(matches!(parent.field_type, FieldType::String));
961 assert!(parent.supports_operator(&Operator::Equal));
962 assert!(parent.supports_operator(&Operator::Regex));
963 }
964
965 #[test]
966 fn test_alias_does_not_affect_field_count() {
967 let registry = FieldRegistry::with_core_fields();
968
969 assert_eq!(registry.len(), 25); assert!(registry.contains("file"));
974 assert!(registry.contains("path"));
975 assert!(registry.contains("language"));
976 assert!(registry.contains("lang"));
977 }
978
979 #[test]
982 fn test_scope_type_field_exists() {
983 let registry = FieldRegistry::with_core_fields();
984 let field = registry.get("scope.type");
985 assert!(field.is_some());
986
987 let scope_type = field.unwrap();
988 assert_eq!(scope_type.name, "scope.type");
989 assert!(!scope_type.indexed); assert!(matches!(scope_type.field_type, FieldType::Enum(_)));
991 assert!(scope_type.supports_operator(&Operator::Equal));
992 assert!(scope_type.supports_operator(&Operator::Regex));
993 }
994
995 #[test]
996 fn test_scope_name_field_exists() {
997 let registry = FieldRegistry::with_core_fields();
998 let field = registry.get("scope.name");
999 assert!(field.is_some());
1000
1001 let scope_name = field.unwrap();
1002 assert_eq!(scope_name.name, "scope.name");
1003 assert!(!scope_name.indexed); assert!(matches!(scope_name.field_type, FieldType::String));
1005 assert!(scope_name.supports_operator(&Operator::Equal));
1006 assert!(scope_name.supports_operator(&Operator::Regex));
1007 }
1008
1009 #[test]
1010 fn test_scope_parent_field_exists() {
1011 let registry = FieldRegistry::with_core_fields();
1012 let field = registry.get("scope.parent");
1013 assert!(field.is_some());
1014
1015 let scope_parent = field.unwrap();
1016 assert_eq!(scope_parent.name, "scope.parent");
1017 assert!(!scope_parent.indexed); assert!(matches!(scope_parent.field_type, FieldType::String));
1019 assert!(scope_parent.supports_operator(&Operator::Equal));
1020 assert!(scope_parent.supports_operator(&Operator::Regex));
1021 }
1022
1023 #[test]
1024 fn test_scope_ancestor_field_exists() {
1025 let registry = FieldRegistry::with_core_fields();
1026 let field = registry.get("scope.ancestor");
1027 assert!(field.is_some());
1028
1029 let scope_ancestor = field.unwrap();
1030 assert_eq!(scope_ancestor.name, "scope.ancestor");
1031 assert!(!scope_ancestor.indexed); assert!(matches!(scope_ancestor.field_type, FieldType::String));
1033 assert!(scope_ancestor.supports_operator(&Operator::Equal));
1034 assert!(scope_ancestor.supports_operator(&Operator::Regex));
1035 }
1036}