1use std::collections::{HashMap, HashSet};
10
11use crate::Runtime;
12use crate::functions::Function;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20#[non_exhaustive]
21pub enum Category {
22 Standard,
24 String,
25 Array,
26 Object,
27 Math,
28 Type,
29 Utility,
30 Validation,
31 Path,
32 Expression,
33 Text,
34 Hash,
35 Encoding,
36 Regex,
37 Url,
38 Uuid,
39 Rand,
40 Datetime,
41 Fuzzy,
42 Phonetic,
43 Geo,
44 Semver,
45 Network,
46 Ids,
47 Duration,
48 Color,
49 Computing,
50 MultiMatch,
51 Jsonpatch,
52 Format,
53 Language,
54 Discovery,
55 Units,
56}
57
58impl Category {
59 pub fn all() -> &'static [Category] {
61 &[
62 Category::Standard,
63 Category::String,
64 Category::Array,
65 Category::Object,
66 Category::Math,
67 Category::Type,
68 Category::Utility,
69 Category::Validation,
70 Category::Path,
71 Category::Expression,
72 Category::Text,
73 Category::Hash,
74 Category::Encoding,
75 Category::Regex,
76 Category::Url,
77 Category::Uuid,
78 Category::Rand,
79 Category::Datetime,
80 Category::Fuzzy,
81 Category::Phonetic,
82 Category::Geo,
83 Category::Semver,
84 Category::Network,
85 Category::Ids,
86 Category::Duration,
87 Category::Color,
88 Category::Computing,
89 Category::MultiMatch,
90 Category::Jsonpatch,
91 Category::Format,
92 Category::Language,
93 Category::Discovery,
94 Category::Units,
95 ]
96 }
97
98 pub fn is_available(&self) -> bool {
103 match self {
104 Category::Standard => true,
105 _ => cfg!(feature = "extensions"),
106 }
107 }
108
109 pub fn name(&self) -> &'static str {
111 match self {
112 Category::Standard => "standard",
113 Category::String => "string",
114 Category::Array => "array",
115 Category::Object => "object",
116 Category::Math => "math",
117 Category::Type => "type",
118 Category::Utility => "utility",
119 Category::Validation => "validation",
120 Category::Path => "path",
121 Category::Expression => "expression",
122 Category::Text => "text",
123 Category::Hash => "hash",
124 Category::Encoding => "encoding",
125 Category::Regex => "regex",
126 Category::Url => "url",
127 Category::Uuid => "uuid",
128 Category::Rand => "rand",
129 Category::Datetime => "datetime",
130 Category::Fuzzy => "fuzzy",
131 Category::Phonetic => "phonetic",
132 Category::Geo => "geo",
133 Category::Semver => "semver",
134 Category::Network => "network",
135 Category::Ids => "ids",
136 Category::Duration => "duration",
137 Category::Color => "color",
138 Category::Computing => "computing",
139 Category::MultiMatch => "multi-match",
140 Category::Jsonpatch => "jsonpatch",
141 Category::Format => "format",
142 Category::Language => "language",
143 Category::Discovery => "discovery",
144 Category::Units => "units",
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
154#[non_exhaustive]
155pub enum Feature {
156 Spec,
158 Core,
160 Fp,
162 Jep,
164 #[allow(non_camel_case_types)]
166 format,
167 #[allow(non_camel_case_types)]
169 env,
170 #[allow(non_camel_case_types)]
172 discovery,
173}
174
175impl Feature {
176 pub fn all() -> &'static [Feature] {
178 &[
179 Feature::Spec,
180 Feature::Core,
181 Feature::Fp,
182 Feature::Jep,
183 Feature::format,
184 Feature::env,
185 Feature::discovery,
186 ]
187 }
188
189 pub fn name(&self) -> &'static str {
191 match self {
192 Feature::Spec => "spec",
193 Feature::Core => "core",
194 Feature::Fp => "fp",
195 Feature::Jep => "jep",
196 Feature::format => "format",
197 Feature::env => "env",
198 Feature::discovery => "discovery",
199 }
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct FunctionInfo {
206 pub name: &'static str,
208 pub category: Category,
210 pub description: &'static str,
212 pub signature: &'static str,
214 pub example: &'static str,
216 pub is_standard: bool,
218 pub jep: Option<&'static str>,
220 pub aliases: &'static [&'static str],
222 pub features: &'static [Feature],
224}
225
226#[derive(Debug, Clone)]
228pub struct FunctionRegistry {
229 registered: HashMap<&'static str, FunctionInfo>,
231 disabled: HashSet<String>,
233 categories: HashSet<Category>,
235}
236
237impl Default for FunctionRegistry {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243impl FunctionRegistry {
244 pub fn new() -> Self {
246 Self {
247 registered: HashMap::new(),
248 disabled: HashSet::new(),
249 categories: HashSet::new(),
250 }
251 }
252
253 pub fn register_all(&mut self) -> &mut Self {
255 for category in Category::all() {
256 self.register_category(*category);
257 }
258 self
259 }
260
261 pub fn register_category(&mut self, category: Category) -> &mut Self {
263 self.categories.insert(category);
264 for info in get_category_functions(category) {
265 self.registered.insert(info.name, info);
266 }
267 self
268 }
269
270 pub fn disable_function(&mut self, name: &str) -> &mut Self {
272 self.disabled.insert(name.to_string());
273 self
274 }
275
276 pub fn enable_function(&mut self, name: &str) -> &mut Self {
278 self.disabled.remove(name);
279 self
280 }
281
282 pub fn is_enabled(&self, name: &str) -> bool {
284 self.registered.contains_key(name) && !self.disabled.contains(name)
285 }
286
287 pub fn get_function(&self, name: &str) -> Option<&FunctionInfo> {
289 if self.disabled.contains(name) {
290 None
291 } else {
292 self.registered.get(name)
293 }
294 }
295
296 pub fn functions(&self) -> impl Iterator<Item = &FunctionInfo> {
298 self.registered
299 .values()
300 .filter(|f| !self.disabled.contains(f.name))
301 }
302
303 pub fn functions_in_category(&self, category: Category) -> impl Iterator<Item = &FunctionInfo> {
305 self.registered
306 .values()
307 .filter(move |f| f.category == category && !self.disabled.contains(f.name))
308 }
309
310 pub fn categories(&self) -> impl Iterator<Item = &Category> {
312 self.categories.iter()
313 }
314
315 pub fn len(&self) -> usize {
317 self.registered.len() - self.disabled.len()
318 }
319
320 pub fn is_empty(&self) -> bool {
322 self.len() == 0
323 }
324
325 pub fn functions_with_feature(&self, feature: Feature) -> impl Iterator<Item = &FunctionInfo> {
327 self.registered
328 .values()
329 .filter(move |f| f.features.contains(&feature) && !self.disabled.contains(f.name))
330 }
331
332 pub fn spec_function_names(&self) -> impl Iterator<Item = &'static str> + '_ {
334 self.functions_with_feature(Feature::Spec).map(|f| f.name)
335 }
336
337 pub fn is_spec_function(&self, name: &str) -> bool {
339 self.registered
340 .get(name)
341 .is_some_and(|f| f.features.contains(&Feature::Spec))
342 }
343
344 pub fn get_function_by_name_or_alias(&self, name: &str) -> Option<&FunctionInfo> {
346 if let Some(info) = self.get_function(name) {
347 return Some(info);
348 }
349 self.registered
350 .values()
351 .find(|f| f.aliases.contains(&name) && !self.disabled.contains(f.name))
352 }
353
354 pub fn all_aliases(&self) -> impl Iterator<Item = (&'static str, &'static str)> + '_ {
356 self.registered
357 .values()
358 .flat_map(|f| f.aliases.iter().map(move |alias| (*alias, f.name)))
359 }
360
361 pub fn apply(&self, runtime: &mut Runtime) {
367 for category in &self.categories {
368 self.apply_category(runtime, *category);
369 }
370 }
371
372 #[allow(unused_variables)]
373 fn apply_category(&self, runtime: &mut Runtime, category: Category) {
374 let enabled_in_category: HashSet<&str> = self
375 .functions_in_category(category)
376 .map(|f| f.name)
377 .collect();
378
379 if enabled_in_category.is_empty() {
380 return;
381 }
382
383 #[cfg(feature = "extensions")]
387 match category {
388 Category::String => {
389 crate::extensions::string::register_filtered(runtime, &enabled_in_category)
390 }
391 Category::Array => {
392 crate::extensions::array::register_filtered(runtime, &enabled_in_category)
393 }
394 Category::Object => {
395 crate::extensions::object::register_filtered(runtime, &enabled_in_category)
396 }
397 Category::Math => {
398 crate::extensions::math::register_filtered(runtime, &enabled_in_category)
399 }
400 Category::Type => {
401 crate::extensions::type_conv::register_filtered(runtime, &enabled_in_category)
402 }
403 Category::Utility => {
404 crate::extensions::utility::register_filtered(runtime, &enabled_in_category)
405 }
406 Category::Validation => {
407 crate::extensions::validation::register_filtered(runtime, &enabled_in_category)
408 }
409 Category::Path => {
410 crate::extensions::path::register_filtered(runtime, &enabled_in_category)
411 }
412 Category::Expression => {
413 crate::extensions::expression::register_filtered(runtime, &enabled_in_category)
414 }
415 Category::Text => {
416 crate::extensions::text::register_filtered(runtime, &enabled_in_category)
417 }
418 Category::Hash => {
419 crate::extensions::hash::register_filtered(runtime, &enabled_in_category)
420 }
421 Category::Encoding => {
422 crate::extensions::encoding::register_filtered(runtime, &enabled_in_category)
423 }
424 Category::Regex => {
425 crate::extensions::regex_fns::register_filtered(runtime, &enabled_in_category)
426 }
427 Category::Url => {
428 crate::extensions::url_fns::register_filtered(runtime, &enabled_in_category)
429 }
430 Category::Uuid => {
431 crate::extensions::random::register_filtered(runtime, &enabled_in_category)
432 }
433 Category::Rand => {
434 crate::extensions::random::register_filtered(runtime, &enabled_in_category)
435 }
436 Category::Datetime => {
437 crate::extensions::datetime::register_filtered(runtime, &enabled_in_category)
438 }
439 Category::Fuzzy => {
440 crate::extensions::fuzzy::register_filtered(runtime, &enabled_in_category)
441 }
442 Category::Phonetic => {
443 crate::extensions::phonetic::register_filtered(runtime, &enabled_in_category)
444 }
445 Category::Geo => {
446 crate::extensions::geo::register_filtered(runtime, &enabled_in_category)
447 }
448 Category::Semver => {
449 crate::extensions::semver_fns::register_filtered(runtime, &enabled_in_category)
450 }
451 Category::Network => {
452 crate::extensions::network::register_filtered(runtime, &enabled_in_category)
453 }
454 Category::Ids => {
455 crate::extensions::ids::register_filtered(runtime, &enabled_in_category)
456 }
457 Category::Duration => {
458 crate::extensions::duration::register_filtered(runtime, &enabled_in_category)
459 }
460 Category::Color => {
461 crate::extensions::color::register_filtered(runtime, &enabled_in_category)
462 }
463 Category::Computing => {
464 crate::extensions::computing::register_filtered(runtime, &enabled_in_category)
465 }
466 Category::MultiMatch => {
467 crate::extensions::multi_match::register_filtered(runtime, &enabled_in_category)
468 }
469 Category::Jsonpatch => {
470 crate::extensions::jsonpatch::register_filtered(runtime, &enabled_in_category)
471 }
472 Category::Format => {
473 crate::extensions::format::register_filtered(runtime, &enabled_in_category)
474 }
475 Category::Language => {
476 crate::extensions::language::register_filtered(runtime, &enabled_in_category)
477 }
478 Category::Discovery => {
479 crate::extensions::discovery::register_filtered(runtime, &enabled_in_category)
480 }
481 Category::Units => {
482 crate::extensions::units::register_filtered(runtime, &enabled_in_category)
483 }
484 Category::Standard => {} }
486
487 #[cfg(not(feature = "extensions"))]
489 let _ = (runtime, category);
490 }
491}
492
493fn get_category_functions(category: Category) -> Vec<FunctionInfo> {
495 generated::FUNCTIONS
496 .iter()
497 .filter(|f| f.category == category)
498 .cloned()
499 .collect()
500}
501
502mod generated {
503 include!(concat!(env!("OUT_DIR"), "/registry_data.rs"));
504}
505
506pub fn register_if_enabled(
508 runtime: &mut Runtime,
509 name: &str,
510 enabled: &HashSet<&str>,
511 f: Box<dyn Function>,
512) {
513 if enabled.contains(name) {
514 runtime.register_function(name, f);
515 }
516}
517
518pub struct SynonymEntry {
524 pub term: &'static str,
526 pub targets: &'static [&'static str],
528}
529
530pub fn get_synonyms() -> &'static [SynonymEntry] {
532 static SYNONYMS: &[SynonymEntry] = &[
533 SynonymEntry {
534 term: "aggregate",
535 targets: &[
536 "group_by",
537 "group_by_expr",
538 "sum",
539 "avg",
540 "count",
541 "reduce",
542 "fold",
543 ],
544 },
545 SynonymEntry {
546 term: "group",
547 targets: &["group_by", "group_by_expr", "chunk", "partition"],
548 },
549 SynonymEntry {
550 term: "collect",
551 targets: &["group_by", "group_by_expr", "flatten", "merge"],
552 },
553 SynonymEntry {
554 term: "bucket",
555 targets: &["group_by", "group_by_expr", "chunk"],
556 },
557 SynonymEntry {
558 term: "count",
559 targets: &["length", "count_by", "count_if", "size"],
560 },
561 SynonymEntry {
562 term: "size",
563 targets: &["length", "count_by"],
564 },
565 SynonymEntry {
566 term: "len",
567 targets: &["length"],
568 },
569 SynonymEntry {
570 term: "concat",
571 targets: &["join", "merge", "combine"],
572 },
573 SynonymEntry {
574 term: "combine",
575 targets: &["join", "merge", "concat"],
576 },
577 SynonymEntry {
578 term: "substring",
579 targets: &["slice", "substr", "mid"],
580 },
581 SynonymEntry {
582 term: "cut",
583 targets: &["slice", "split", "trim"],
584 },
585 SynonymEntry {
586 term: "strip",
587 targets: &["trim", "trim_left", "trim_right"],
588 },
589 SynonymEntry {
590 term: "lowercase",
591 targets: &["lower", "to_lower", "downcase"],
592 },
593 SynonymEntry {
594 term: "uppercase",
595 targets: &["upper", "to_upper", "upcase"],
596 },
597 SynonymEntry {
598 term: "replace",
599 targets: &["substitute", "gsub", "regex_replace"],
600 },
601 SynonymEntry {
602 term: "find",
603 targets: &["contains", "index_of", "search", "match"],
604 },
605 SynonymEntry {
606 term: "search",
607 targets: &["contains", "find", "index_of", "regex_match"],
608 },
609 SynonymEntry {
610 term: "filter",
611 targets: &["select", "where", "find_all", "keep"],
612 },
613 SynonymEntry {
614 term: "select",
615 targets: &["filter", "map", "pluck"],
616 },
617 SynonymEntry {
618 term: "transform",
619 targets: &["map", "transform_values", "map_values"],
620 },
621 SynonymEntry {
622 term: "first",
623 targets: &["head", "take", "front"],
624 },
625 SynonymEntry {
626 term: "last",
627 targets: &["tail", "end", "back"],
628 },
629 SynonymEntry {
630 term: "remove",
631 targets: &["reject", "delete", "drop", "exclude"],
632 },
633 SynonymEntry {
634 term: "unique",
635 targets: &["distinct", "uniq", "dedupe", "deduplicate"],
636 },
637 SynonymEntry {
638 term: "dedupe",
639 targets: &["unique", "distinct", "uniq"],
640 },
641 SynonymEntry {
642 term: "shuffle",
643 targets: &["random", "randomize", "permute"],
644 },
645 SynonymEntry {
646 term: "order",
647 targets: &["sort", "sort_by", "order_by", "arrange"],
648 },
649 SynonymEntry {
650 term: "arrange",
651 targets: &["sort", "sort_by", "order_by"],
652 },
653 SynonymEntry {
654 term: "rank",
655 targets: &["sort", "sort_by", "order_by"],
656 },
657 SynonymEntry {
658 term: "average",
659 targets: &["avg", "mean", "arithmetic_mean"],
660 },
661 SynonymEntry {
662 term: "mean",
663 targets: &["avg", "average"],
664 },
665 SynonymEntry {
666 term: "total",
667 targets: &["sum", "add", "accumulate"],
668 },
669 SynonymEntry {
670 term: "add",
671 targets: &["sum", "plus", "addition"],
672 },
673 SynonymEntry {
674 term: "subtract",
675 targets: &["minus", "difference"],
676 },
677 SynonymEntry {
678 term: "multiply",
679 targets: &["times", "product", "mul"],
680 },
681 SynonymEntry {
682 term: "divide",
683 targets: &["quotient", "div"],
684 },
685 SynonymEntry {
686 term: "remainder",
687 targets: &["mod", "modulo", "modulus"],
688 },
689 SynonymEntry {
690 term: "power",
691 targets: &["pow", "exponent", "exp"],
692 },
693 SynonymEntry {
694 term: "absolute",
695 targets: &["abs", "magnitude"],
696 },
697 SynonymEntry {
698 term: "round",
699 targets: &["round", "round_to", "nearest"],
700 },
701 SynonymEntry {
702 term: "random",
703 targets: &["rand", "random_int", "random_float"],
704 },
705 SynonymEntry {
706 term: "date",
707 targets: &["now", "today", "parse_date", "format_date", "datetime"],
708 },
709 SynonymEntry {
710 term: "time",
711 targets: &["now", "time_now", "parse_time", "datetime"],
712 },
713 SynonymEntry {
714 term: "timestamp",
715 targets: &["now", "epoch", "unix_time", "to_epoch"],
716 },
717 SynonymEntry {
718 term: "format",
719 targets: &["format_date", "strftime", "date_format"],
720 },
721 SynonymEntry {
722 term: "parse",
723 targets: &["parse_date", "parse_time", "strptime", "from_string"],
724 },
725 SynonymEntry {
726 term: "convert",
727 targets: &[
728 "to_string",
729 "to_number",
730 "to_array",
731 "type",
732 "convert_temperature",
733 "convert_length",
734 "convert_mass",
735 "convert_volume",
736 ],
737 },
738 SynonymEntry {
739 term: "units",
740 targets: &[
741 "convert_temperature",
742 "convert_length",
743 "convert_mass",
744 "convert_volume",
745 ],
746 },
747 SynonymEntry {
748 term: "temperature",
749 targets: &["convert_temperature"],
750 },
751 SynonymEntry {
752 term: "weight",
753 targets: &["convert_mass"],
754 },
755 SynonymEntry {
756 term: "distance",
757 targets: &["convert_length"],
758 },
759 SynonymEntry {
760 term: "cast",
761 targets: &["to_string", "to_number", "to_bool"],
762 },
763 SynonymEntry {
764 term: "stringify",
765 targets: &["to_string", "string", "str"],
766 },
767 SynonymEntry {
768 term: "numberify",
769 targets: &["to_number", "number", "int", "float"],
770 },
771 SynonymEntry {
772 term: "object",
773 targets: &["from_items", "to_object", "merge", "object_from_items"],
774 },
775 SynonymEntry {
776 term: "dict",
777 targets: &["from_items", "to_object", "object"],
778 },
779 SynonymEntry {
780 term: "hash",
781 targets: &["md5", "sha256", "sha1", "crc32"],
782 },
783 SynonymEntry {
784 term: "encrypt",
785 targets: &["md5", "sha256", "sha1", "hmac"],
786 },
787 SynonymEntry {
788 term: "checksum",
789 targets: &["md5", "sha256", "crc32"],
790 },
791 SynonymEntry {
792 term: "encode",
793 targets: &["base64_encode", "url_encode", "hex_encode"],
794 },
795 SynonymEntry {
796 term: "decode",
797 targets: &["base64_decode", "url_decode", "hex_decode"],
798 },
799 SynonymEntry {
800 term: "escape",
801 targets: &["url_encode", "html_escape"],
802 },
803 SynonymEntry {
804 term: "unescape",
805 targets: &["url_decode", "html_unescape"],
806 },
807 SynonymEntry {
808 term: "check",
809 targets: &[
810 "is_string",
811 "is_number",
812 "is_array",
813 "is_object",
814 "validate",
815 ],
816 },
817 SynonymEntry {
818 term: "validate",
819 targets: &["is_email", "is_url", "is_uuid", "is_valid"],
820 },
821 SynonymEntry {
822 term: "test",
823 targets: &["regex_match", "contains", "starts_with", "ends_with"],
824 },
825 SynonymEntry {
826 term: "default",
827 targets: &["coalesce", "if_null", "or_else", "default_value"],
828 },
829 SynonymEntry {
830 term: "empty",
831 targets: &["is_empty", "blank", "null"],
832 },
833 SynonymEntry {
834 term: "null",
835 targets: &["is_null", "coalesce", "not_null"],
836 },
837 SynonymEntry {
838 term: "fallback",
839 targets: &["coalesce", "default", "or_else"],
840 },
841 SynonymEntry {
842 term: "equal",
843 targets: &["eq", "equals", "same"],
844 },
845 SynonymEntry {
846 term: "compare",
847 targets: &["eq", "lt", "gt", "lte", "gte", "cmp"],
848 },
849 SynonymEntry {
850 term: "between",
851 targets: &["range", "in_range", "clamp"],
852 },
853 SynonymEntry {
854 term: "copy",
855 targets: &["clone", "dup", "duplicate"],
856 },
857 SynonymEntry {
858 term: "debug",
859 targets: &["debug", "inspect", "dump", "print"],
860 },
861 SynonymEntry {
862 term: "reverse",
863 targets: &["reverse", "flip", "invert"],
864 },
865 SynonymEntry {
866 term: "repeat",
867 targets: &["repeat", "replicate", "times"],
868 },
869 SynonymEntry {
870 term: "uuid",
871 targets: &["uuid", "uuid4", "guid", "generate_uuid"],
872 },
873 SynonymEntry {
874 term: "id",
875 targets: &["uuid", "nanoid", "ulid", "unique_id"],
876 },
877 ];
878 SYNONYMS
879}
880
881pub fn lookup_synonyms(term: &str) -> Option<&'static [&'static str]> {
883 let term_lower = term.to_lowercase();
884 get_synonyms()
885 .iter()
886 .find(|s| s.term == term_lower)
887 .map(|s| s.targets)
888}
889
890pub fn expand_search_terms(query: &str) -> Vec<String> {
892 let mut expanded = Vec::new();
893 for word in query.split_whitespace() {
894 let word_lower = word.to_lowercase();
895 expanded.push(word_lower.clone());
896 if let Some(targets) = lookup_synonyms(&word_lower) {
897 for target in targets {
898 let target_str = (*target).to_string();
899 if !expanded.contains(&target_str) {
900 expanded.push(target_str);
901 }
902 }
903 }
904 }
905 expanded
906}