1#![allow(clippy::explicit_counter_loop)]
5#![allow(clippy::approx_constant)]
6
7use crate::value::JValue;
8use indexmap::IndexMap;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13pub enum FunctionError {
14 #[error("Argument error: {0}")]
15 ArgumentError(String),
16
17 #[error("Type error: {0}")]
18 TypeError(String),
19
20 #[error("Runtime error: {0}")]
21 RuntimeError(String),
22}
23
24fn js_slice<T: Clone>(arr: &[T], start: i64, end: Option<i64>) -> Vec<T> {
29 let len = arr.len() as i64;
30 let s = if start < 0 {
31 (len + start).max(0) as usize
32 } else {
33 start.min(len) as usize
34 };
35 let e = match end {
36 Some(end) => {
37 if end < 0 {
38 (len + end).max(0) as usize
39 } else {
40 (end.min(len)) as usize
41 }
42 }
43 None => arr.len(),
44 };
45 if s >= e {
46 return Vec::new();
47 }
48 arr[s..e].to_vec()
49}
50
51pub mod string {
52 use super::*;
53 use regex::Regex;
54
55 pub fn extract_regex(value: &JValue) -> Option<(String, String)> {
57 match value {
58 JValue::Regex { pattern, flags } => Some((pattern.to_string(), flags.to_string())),
59 _ => None,
60 }
61 }
62
63 pub fn build_regex(pattern: &str, flags: &str) -> Result<Regex, FunctionError> {
65 let mut regex_pattern = String::new();
67
68 if !flags.is_empty() {
70 regex_pattern.push_str("(?");
71 if flags.contains('i') {
72 regex_pattern.push('i'); }
74 if flags.contains('m') {
75 regex_pattern.push('m'); }
77 if flags.contains('s') {
78 regex_pattern.push('s'); }
80 regex_pattern.push(')');
81 }
82
83 regex_pattern.push_str(pattern);
84
85 Regex::new(®ex_pattern)
86 .map_err(|e| FunctionError::ArgumentError(format!("Invalid regex: {}", e)))
87 }
88
89 pub fn string(value: &JValue, prettify: Option<bool>) -> Result<JValue, FunctionError> {
98 if value.is_undefined() {
100 return Ok(JValue::string(""));
101 }
102 if value.is_function() {
103 return Ok(JValue::string(""));
104 }
105
106 let result = match value {
107 JValue::String(s) => s.to_string(),
108 JValue::Number(n) => {
109 let f = *n;
110 if !f.is_finite() {
112 return Err(FunctionError::RuntimeError(format!(
113 "D3001: Attempting to invoke string function with non-finite number: {}",
114 f
115 )));
116 }
117
118 if f.fract() == 0.0 && f.abs() < (i64::MAX as f64) {
120 (f as i64).to_string()
121 } else {
122 format_number_with_precision(f)
125 }
126 }
127 JValue::Bool(b) => b.to_string(),
128 JValue::Null => {
129 "null".to_string()
132 }
133 JValue::Array(_) | JValue::Object(_) => {
134 let indent = if prettify.unwrap_or(false) {
137 Some(2)
138 } else {
139 None
140 };
141 stringify_value_custom(value, indent)?
142 }
143 _ => String::new(),
144 };
145 Ok(JValue::string(result))
146 }
147
148 fn format_number_with_precision(f: f64) -> String {
156 let formatted = format!("{:.14e}", f);
159
160 if let Ok(parsed) = formatted.parse::<f64>() {
163 if parsed.abs() >= 1e-6 && parsed.abs() < 1e21 {
165 let s = format!("{}", parsed);
167 if s.contains('.') {
169 let parts: Vec<&str> = s.split('.').collect();
170 if parts.len() == 2 {
171 let int_part = parts[0];
172 let frac_part = parts[1];
173 let total_digits = int_part.trim_start_matches('-').len() + frac_part.len();
174
175 if total_digits > 15 {
176 let sig_figs = 15 - int_part.trim_start_matches('-').len();
178 if sig_figs > 0 && sig_figs <= frac_part.len() {
179 let truncated_frac = &frac_part[..sig_figs];
180 let trimmed = truncated_frac.trim_end_matches('0');
182 if trimmed.is_empty() {
183 return int_part.to_string();
184 } else {
185 return format!("{}.{}", int_part, trimmed);
186 }
187 }
188 }
189 }
190 }
191 s
192 } else {
193 let exp_str = format!("{:e}", parsed);
196 if exp_str.contains('e') && !exp_str.contains("e-") && !exp_str.contains("e+") {
198 exp_str.replace('e', "e+")
199 } else {
200 exp_str
201 }
202 }
203 } else {
204 format!("{}", f)
206 }
207 }
208
209 fn stringify_value_custom(
216 value: &JValue,
217 indent: Option<usize>,
218 ) -> Result<String, FunctionError> {
219 let transformed = transform_for_stringify(value);
221
222 let result = if indent.is_some() {
223 serde_json::to_string_pretty(&transformed)
224 .map_err(|e| FunctionError::RuntimeError(format!("JSON stringify error: {}", e)))?
225 } else {
226 serde_json::to_string(&transformed)
227 .map_err(|e| FunctionError::RuntimeError(format!("JSON stringify error: {}", e)))?
228 };
229 Ok(result)
230 }
231
232 fn transform_for_stringify(value: &JValue) -> JValue {
234 match value {
235 JValue::Number(n) => {
236 let f = *n;
237 if f.fract() == 0.0 && f.is_finite() && f.abs() < (1i64 << 53) as f64 {
239 value.clone()
241 } else {
242 let formatted = format_number_with_precision(f);
244 if let Ok(parsed) = formatted.parse::<f64>() {
245 JValue::Number(parsed)
246 } else {
247 value.clone()
248 }
249 }
250 }
251 JValue::Array(arr) => {
252 let transformed: Vec<JValue> = arr
253 .iter()
254 .map(|v| {
255 if v.is_function() {
256 return JValue::string("");
257 }
258 transform_for_stringify(v)
259 })
260 .collect();
261 JValue::array(transformed)
262 }
263 JValue::Object(obj) => {
264 if value.is_function() {
265 return JValue::string("");
266 }
267
268 let transformed: IndexMap<String, JValue> = obj
269 .iter()
270 .map(|(k, v)| {
271 if v.is_function() {
272 return (k.clone(), JValue::string(""));
273 }
274 (k.clone(), transform_for_stringify(v))
275 })
276 .collect();
277 JValue::object(transformed)
278 }
279 _ => value.clone(),
280 }
281 }
282
283 pub fn length(s: &str) -> Result<JValue, FunctionError> {
286 Ok(JValue::Number(s.chars().count() as f64))
287 }
288
289 pub fn uppercase(s: &str) -> Result<JValue, FunctionError> {
291 Ok(JValue::string(s.to_uppercase()))
292 }
293
294 pub fn lowercase(s: &str) -> Result<JValue, FunctionError> {
296 Ok(JValue::string(s.to_lowercase()))
297 }
298
299 pub fn substring(s: &str, start: i64, length: Option<i64>) -> Result<JValue, FunctionError> {
305 let chars: Vec<char> = s.chars().collect();
306 let str_len = chars.len() as i64;
307
308 let start = if str_len + start < 0 { 0 } else { start };
311
312 if let Some(len) = length {
313 if len <= 0 {
315 return Ok(JValue::string(""));
316 }
317 let end = if start >= 0 {
320 start + len
321 } else {
322 str_len + start + len
323 };
324 let slice = js_slice(&chars, start, Some(end));
327 Ok(JValue::string(slice.iter().collect::<String>()))
328 } else {
329 let slice = js_slice(&chars, start, None);
332 Ok(JValue::string(slice.iter().collect::<String>()))
333 }
334 }
335
336 pub fn substring_before(s: &str, separator: &str) -> Result<JValue, FunctionError> {
338 if separator.is_empty() {
339 return Ok(JValue::string(""));
340 }
341
342 let result = s.split(separator).next().unwrap_or(s).to_string();
343 Ok(JValue::string(result))
344 }
345
346 pub fn substring_after(s: &str, separator: &str) -> Result<JValue, FunctionError> {
348 if separator.is_empty() {
349 return Ok(JValue::string(s));
350 }
351
352 if let Some(pos) = s.find(separator) {
353 let result = s[pos + separator.len()..].to_string();
354 Ok(JValue::string(result))
355 } else {
356 Ok(JValue::string(s))
358 }
359 }
360
361 pub fn trim(s: &str) -> Result<JValue, FunctionError> {
366 use regex::Regex;
367 use std::sync::OnceLock;
368
369 static WS_REGEX: OnceLock<Regex> = OnceLock::new();
370 let ws_regex = WS_REGEX.get_or_init(|| Regex::new(r"[ \t\n\r]+").unwrap());
371
372 let normalized = ws_regex.replace_all(s, " ");
373 Ok(JValue::string(normalized.trim()))
374 }
375
376 pub fn contains(s: &str, pattern: &JValue) -> Result<JValue, FunctionError> {
378 if let Some((pat, flags)) = extract_regex(pattern) {
380 let re = build_regex(&pat, &flags)?;
381 return Ok(JValue::Bool(re.is_match(s)));
382 }
383
384 let pat = match pattern {
386 JValue::String(s) => &**s,
387 _ => {
388 return Err(FunctionError::TypeError(
389 "contains() requires string arguments".to_string(),
390 ))
391 }
392 };
393
394 Ok(JValue::Bool(s.contains(pat)))
395 }
396
397 pub fn split(
400 s: &str,
401 separator: &JValue,
402 limit: Option<usize>,
403 ) -> Result<JValue, FunctionError> {
404 if let Some((pattern, flags)) = extract_regex(separator) {
406 let re = build_regex(&pattern, &flags)?;
407
408 let parts: Vec<JValue> = re.split(s).map(JValue::string).collect();
409
410 let result = if let Some(lim) = limit {
412 parts.into_iter().take(lim).collect()
413 } else {
414 parts
415 };
416
417 return Ok(JValue::array(result));
418 }
419
420 let sep = match separator {
422 JValue::String(s) => &**s,
423 _ => {
424 return Err(FunctionError::TypeError(
425 "split() requires string arguments".to_string(),
426 ))
427 }
428 };
429
430 if sep.is_empty() {
431 let chars: Vec<JValue> = s.chars().map(|c| JValue::string(c.to_string())).collect();
433 let result = if let Some(lim) = limit {
435 chars.into_iter().take(lim).collect()
436 } else {
437 chars
438 };
439 return Ok(JValue::array(result));
440 }
441
442 let parts: Vec<JValue> = s.split(sep).map(JValue::string).collect();
443
444 let result = if let Some(lim) = limit {
446 parts.into_iter().take(lim).collect()
447 } else {
448 parts
449 };
450
451 Ok(JValue::array(result))
452 }
453
454 pub fn join(arr: &[JValue], separator: Option<&str>) -> Result<JValue, FunctionError> {
456 let sep = separator.unwrap_or("");
457 let parts: Result<Vec<String>, FunctionError> = arr
458 .iter()
459 .map(|v| match v {
460 JValue::String(s) => Ok(s.to_string()),
461 JValue::Number(n) => Ok(format_join_number(*n)),
462 JValue::Bool(b) => Ok(b.to_string()),
463 JValue::Null => Ok(String::new()),
464 _ => Err(FunctionError::TypeError(
465 "Cannot join array containing objects or nested arrays".to_string(),
466 )),
467 })
468 .collect();
469
470 let parts = parts?;
471 Ok(JValue::string(parts.join(sep)))
472 }
473
474 fn format_join_number(n: f64) -> String {
476 if n.fract() == 0.0 && n.abs() < (i64::MAX as f64) {
477 (n as i64).to_string()
478 } else {
479 n.to_string()
480 }
481 }
482
483 fn substitute_capture_groups(
486 replacement: &str,
487 full_match: &str,
488 groups: &[Option<regex::Match>],
489 ) -> String {
490 let mut result = String::new();
491 let mut position = 0;
492 let chars: Vec<char> = replacement.chars().collect();
493
494 while position < chars.len() {
495 if chars[position] == '$' {
496 position += 1;
497
498 if position >= chars.len() {
499 result.push('$');
501 break;
502 }
503
504 let next_ch = chars[position];
505
506 if next_ch == '$' {
507 result.push('$');
509 position += 1;
510 } else if next_ch == '0' {
511 result.push_str(full_match);
513 position += 1;
514 } else if next_ch.is_ascii_digit() {
515 let max_digits = if groups.is_empty() {
518 1
519 } else {
520 ((groups.len() as f64).log10().floor() as usize) + 1
522 };
523
524 let mut digits_end = position;
526 let mut digit_count = 0;
527 while digits_end < chars.len()
528 && chars[digits_end].is_ascii_digit()
529 && digit_count < max_digits
530 {
531 digits_end += 1;
532 digit_count += 1;
533 }
534
535 if digit_count > 0 {
536 let num_str: String = chars[position..digits_end].iter().collect();
538 let mut group_num = num_str.parse::<usize>().unwrap();
539
540 let mut used_digits = digit_count;
543 if max_digits > 1 && group_num > groups.len() && digit_count > 1 {
544 let fallback_str: String =
545 chars[position..digits_end - 1].iter().collect();
546 if let Ok(fallback_num) = fallback_str.parse::<usize>() {
547 group_num = fallback_num;
548 used_digits = digit_count - 1;
549 }
550 }
551
552 if groups.is_empty() {
554 position += used_digits;
557 } else if group_num > 0 && group_num <= groups.len() {
558 if let Some(m) = &groups[group_num - 1] {
560 result.push_str(m.as_str());
561 }
562 position += used_digits;
564 } else {
565 position += used_digits;
568 }
569 } else {
570 result.push('$');
572 }
573 } else {
574 result.push('$');
576 }
578 } else {
579 result.push(chars[position]);
580 position += 1;
581 }
582 }
583
584 result
585 }
586
587 pub fn replace(
589 s: &str,
590 pattern: &JValue,
591 replacement: &str,
592 limit: Option<usize>,
593 ) -> Result<JValue, FunctionError> {
594 if let Some((pat, flags)) = extract_regex(pattern) {
596 let re = build_regex(&pat, &flags)?;
597
598 let mut count = 0;
599 let mut last_match = 0;
600 let mut output = String::new();
601
602 for cap in re.captures_iter(s) {
603 if limit.is_some_and(|lim| count >= lim) {
604 break;
605 }
606
607 let m = cap.get(0).unwrap();
608
609 if m.as_str().is_empty() {
611 return Err(FunctionError::RuntimeError(
612 "D1004: Regular expression matches zero length string".to_string(),
613 ));
614 }
615
616 output.push_str(&s[last_match..m.start()]);
617
618 let groups: Vec<Option<regex::Match>> =
620 (1..cap.len()).map(|i| cap.get(i)).collect();
621
622 let substituted = substitute_capture_groups(replacement, m.as_str(), &groups);
624 output.push_str(&substituted);
625
626 last_match = m.end();
627 count += 1;
628 }
629
630 output.push_str(&s[last_match..]);
631 return Ok(JValue::string(output));
632 }
633
634 let pat = match pattern {
636 JValue::String(s) => &**s,
637 _ => {
638 return Err(FunctionError::TypeError(
639 "replace() requires string arguments".to_string(),
640 ))
641 }
642 };
643
644 if pat.is_empty() {
645 return Err(FunctionError::RuntimeError(
646 "D3010: Pattern cannot be empty".to_string(),
647 ));
648 }
649
650 let result = if let Some(lim) = limit {
651 let mut remaining = s;
652 let mut output = String::new();
653 let mut count = 0;
654
655 while count < lim {
656 if let Some(pos) = remaining.find(pat) {
657 output.push_str(&remaining[..pos]);
658 output.push_str(replacement);
659 remaining = &remaining[pos + pat.len()..];
660 count += 1;
661 } else {
662 output.push_str(remaining);
663 break;
664 }
665 }
666 if count == lim {
667 output.push_str(remaining);
668 }
669 output
670 } else {
671 s.replace(pat, replacement)
672 };
673
674 Ok(JValue::string(result))
675 }
676}
677
678pub mod boolean {
680 use super::*;
681
682 pub fn boolean(value: &JValue) -> Result<JValue, FunctionError> {
693 Ok(JValue::Bool(to_boolean(value)))
694 }
695
696 fn to_boolean(value: &JValue) -> bool {
698 match value {
699 JValue::Null | JValue::Undefined => false,
700 JValue::Bool(b) => *b,
701 JValue::Number(n) => *n != 0.0,
702 JValue::String(s) => !s.is_empty(),
703 JValue::Array(arr) => {
704 if arr.len() == 1 {
705 to_boolean(&arr[0])
706 } else {
707 arr.iter().any(to_boolean)
709 }
710 }
711 JValue::Object(obj) => !obj.is_empty(),
712 JValue::Lambda { .. } | JValue::Builtin { .. } => false,
713 JValue::Regex { .. } => true,
714 }
715 }
716}
717
718pub mod numeric {
720 use super::*;
721
722 pub fn number(value: &JValue) -> Result<JValue, FunctionError> {
725 match value {
726 JValue::Number(n) => {
727 let f = *n;
728 if !f.is_finite() {
729 return Err(FunctionError::RuntimeError(
730 "D3030: Cannot convert infinite number".to_string(),
731 ));
732 }
733 Ok(JValue::Number(f))
734 }
735 JValue::String(s) => {
736 let trimmed = s.trim();
737
738 if let Some(stripped) = trimmed
740 .strip_prefix("0x")
741 .or_else(|| trimmed.strip_prefix("0X"))
742 {
743 return i64::from_str_radix(stripped, 16)
745 .map(|n| JValue::Number(n as f64))
746 .map_err(|_| {
747 FunctionError::RuntimeError(format!(
748 "D3030: Cannot convert '{}' to number",
749 s
750 ))
751 });
752 } else if let Some(stripped) = trimmed
753 .strip_prefix("0o")
754 .or_else(|| trimmed.strip_prefix("0O"))
755 {
756 return i64::from_str_radix(stripped, 8)
758 .map(|n| JValue::Number(n as f64))
759 .map_err(|_| {
760 FunctionError::RuntimeError(format!(
761 "D3030: Cannot convert '{}' to number",
762 s
763 ))
764 });
765 } else if let Some(stripped) = trimmed
766 .strip_prefix("0b")
767 .or_else(|| trimmed.strip_prefix("0B"))
768 {
769 return i64::from_str_radix(stripped, 2)
771 .map(|n| JValue::Number(n as f64))
772 .map_err(|_| {
773 FunctionError::RuntimeError(format!(
774 "D3030: Cannot convert '{}' to number",
775 s
776 ))
777 });
778 }
779
780 match trimmed.parse::<f64>() {
782 Ok(n) => {
783 if !n.is_finite() {
785 return Err(FunctionError::RuntimeError(format!(
786 "D3030: Cannot convert '{}' to number",
787 s
788 )));
789 }
790 Ok(JValue::Number(n))
791 }
792 Err(_) => Err(FunctionError::RuntimeError(format!(
793 "D3030: Cannot convert '{}' to number",
794 s
795 ))),
796 }
797 }
798 JValue::Bool(true) => Ok(JValue::Number(1.0)),
799 JValue::Bool(false) => Ok(JValue::Number(0.0)),
800 JValue::Null => Err(FunctionError::RuntimeError(
801 "D3030: Cannot convert null to number".to_string(),
802 )),
803 _ => Err(FunctionError::RuntimeError(
804 "D3030: Cannot convert array or object to number".to_string(),
805 )),
806 }
807 }
808
809 pub fn sum(arr: &[JValue]) -> Result<JValue, FunctionError> {
811 if arr.is_empty() {
812 return Ok(JValue::Number(0.0));
813 }
814
815 let mut total = 0.0;
816 for value in arr {
817 match value {
818 JValue::Number(n) => {
819 total += n;
820 }
821 _ => {
822 return Err(FunctionError::TypeError(format!(
823 "sum() requires all array elements to be numbers, got: {:?}",
824 value
825 )))
826 }
827 }
828 }
829 Ok(JValue::Number(total))
830 }
831
832 pub fn max(arr: &[JValue]) -> Result<JValue, FunctionError> {
834 if arr.is_empty() {
835 return Ok(JValue::Null);
836 }
837
838 let mut max_val = f64::NEG_INFINITY;
839 for value in arr {
840 match value {
841 JValue::Number(n) => {
842 if *n > max_val {
843 max_val = *n;
844 }
845 }
846 _ => {
847 return Err(FunctionError::TypeError(
848 "max() requires all array elements to be numbers".to_string(),
849 ))
850 }
851 }
852 }
853 Ok(JValue::Number(max_val))
854 }
855
856 pub fn min(arr: &[JValue]) -> Result<JValue, FunctionError> {
858 if arr.is_empty() {
859 return Ok(JValue::Null);
860 }
861
862 let mut min_val = f64::INFINITY;
863 for value in arr {
864 match value {
865 JValue::Number(n) => {
866 if *n < min_val {
867 min_val = *n;
868 }
869 }
870 _ => {
871 return Err(FunctionError::TypeError(
872 "min() requires all array elements to be numbers".to_string(),
873 ))
874 }
875 }
876 }
877 Ok(JValue::Number(min_val))
878 }
879
880 pub fn average(arr: &[JValue]) -> Result<JValue, FunctionError> {
882 if arr.is_empty() {
883 return Ok(JValue::Null);
884 }
885
886 let sum_result = sum(arr)?;
887 if let JValue::Number(n) = sum_result {
888 let avg = n / arr.len() as f64;
889 Ok(JValue::Number(avg))
890 } else {
891 Err(FunctionError::RuntimeError("Sum failed".to_string()))
892 }
893 }
894
895 pub fn abs(n: f64) -> Result<JValue, FunctionError> {
897 Ok(JValue::Number(n.abs()))
898 }
899
900 pub fn floor(n: f64) -> Result<JValue, FunctionError> {
902 Ok(JValue::Number(n.floor()))
903 }
904
905 pub fn ceil(n: f64) -> Result<JValue, FunctionError> {
907 Ok(JValue::Number(n.ceil()))
908 }
909
910 pub fn round(n: f64, precision: Option<i32>) -> Result<JValue, FunctionError> {
920 let prec = precision.unwrap_or(0);
921
922 let multiplier = 10_f64.powi(prec);
924 let scaled = n * multiplier;
925
926 let floor_val = scaled.floor();
928 let frac = scaled - floor_val;
929
930 let epsilon = 1e-10;
932 let result = if (frac - 0.5).abs() < epsilon {
933 let floor_int = floor_val as i64;
935 if floor_int % 2 == 0 {
936 floor_val } else {
938 floor_val + 1.0 }
940 } else if frac > 0.5 {
941 floor_val + 1.0 } else {
943 floor_val };
945
946 let final_result = result / multiplier;
948
949 Ok(JValue::Number(final_result))
950 }
951
952 pub fn sqrt(n: f64) -> Result<JValue, FunctionError> {
954 if n < 0.0 {
955 return Err(FunctionError::ArgumentError(
956 "Cannot take square root of negative number".to_string(),
957 ));
958 }
959 Ok(JValue::Number(n.sqrt()))
960 }
961
962 pub fn power(base: f64, exponent: f64) -> Result<JValue, FunctionError> {
964 let result = base.powf(exponent);
965 if result.is_nan() || result.is_infinite() {
966 return Err(FunctionError::RuntimeError(
967 "Power operation resulted in invalid number".to_string(),
968 ));
969 }
970 Ok(JValue::Number(result))
971 }
972
973 pub fn format_number(
976 value: f64,
977 picture: &str,
978 options: Option<&JValue>,
979 ) -> Result<JValue, FunctionError> {
980 let mut decimal_separator = '.';
982 let mut grouping_separator = ',';
983 let mut zero_digit = '0';
984 let mut percent_symbol = "%".to_string();
985 let mut per_mille_symbol = "\u{2030}".to_string();
986 let digit_char = '#';
987 let pattern_separator = ';';
988
989 if let Some(JValue::Object(opts)) = options {
991 if let Some(JValue::String(s)) = opts.get("decimal-separator") {
992 decimal_separator = s.chars().next().unwrap_or('.');
993 }
994 if let Some(JValue::String(s)) = opts.get("grouping-separator") {
995 grouping_separator = s.chars().next().unwrap_or(',');
996 }
997 if let Some(JValue::String(s)) = opts.get("zero-digit") {
998 zero_digit = s.chars().next().unwrap_or('0');
999 }
1000 if let Some(JValue::String(s)) = opts.get("percent") {
1001 percent_symbol = s.to_string();
1002 }
1003 if let Some(JValue::String(s)) = opts.get("per-mille") {
1004 per_mille_symbol = s.to_string();
1005 }
1006 }
1007
1008 let sub_pictures: Vec<&str> = picture.split(pattern_separator).collect();
1010 if sub_pictures.len() > 2 {
1011 return Err(FunctionError::ArgumentError(
1012 "D3080: Too many pattern separators in picture string".to_string(),
1013 ));
1014 }
1015
1016 let parts = parse_picture(
1018 sub_pictures[0],
1019 decimal_separator,
1020 grouping_separator,
1021 zero_digit,
1022 digit_char,
1023 &percent_symbol,
1024 &per_mille_symbol,
1025 )?;
1026
1027 let is_negative = value < 0.0;
1029 let mut abs_value = value.abs();
1030
1031 if parts.has_percent {
1033 abs_value *= 100.0;
1034 } else if parts.has_per_mille {
1035 abs_value *= 1000.0;
1036 }
1037
1038 let formatted = apply_number_picture(
1040 abs_value,
1041 &parts,
1042 decimal_separator,
1043 grouping_separator,
1044 zero_digit,
1045 )?;
1046
1047 let result = if is_negative {
1049 if sub_pictures.len() == 2 {
1050 let neg_parts = parse_picture(
1052 sub_pictures[1],
1053 decimal_separator,
1054 grouping_separator,
1055 zero_digit,
1056 digit_char,
1057 &percent_symbol,
1058 &per_mille_symbol,
1059 )?;
1060 let neg_formatted = apply_number_picture(
1061 abs_value,
1062 &neg_parts,
1063 decimal_separator,
1064 grouping_separator,
1065 zero_digit,
1066 )?;
1067 format!("{}{}{}", neg_parts.prefix, neg_formatted, neg_parts.suffix)
1068 } else {
1069 format!("-{}{}{}", parts.prefix, formatted, parts.suffix)
1071 }
1072 } else {
1073 format!("{}{}{}", parts.prefix, formatted, parts.suffix)
1074 };
1075
1076 Ok(JValue::string(result))
1077 }
1078
1079 fn is_digit_in_family(c: char, zero_digit: char) -> bool {
1081 if c.is_ascii_digit() {
1082 return true;
1083 }
1084 let zero_code = zero_digit as u32;
1086 let c_code = c as u32;
1087 c_code >= zero_code && c_code < zero_code + 10
1088 }
1089
1090 fn parse_picture(
1092 picture: &str,
1093 decimal_sep: char,
1094 grouping_sep: char,
1095 zero_digit: char,
1096 digit_char: char,
1097 percent_symbol: &str,
1098 per_mille_symbol: &str,
1099 ) -> Result<PictureParts, FunctionError> {
1100 let chars: Vec<char> = picture.chars().collect();
1102
1103 let prefix_end = chars
1107 .iter()
1108 .position(|&c| {
1109 c == decimal_sep
1110 || c == grouping_sep
1111 || c == digit_char
1112 || is_digit_in_family(c, zero_digit)
1113 })
1114 .unwrap_or(chars.len());
1115 let prefix: String = chars[..prefix_end].iter().collect();
1116
1117 let suffix_start = chars
1119 .iter()
1120 .rposition(|&c| {
1121 c == decimal_sep
1122 || c == grouping_sep
1123 || c == digit_char
1124 || is_digit_in_family(c, zero_digit)
1125 })
1126 .map(|pos| pos + 1)
1127 .unwrap_or(chars.len());
1128 let suffix: String = chars[suffix_start..].iter().collect();
1129
1130 let active: String = chars[prefix_end..suffix_start].iter().collect();
1132
1133 let exponent_pos = active.find('e').or_else(|| active.find('E'));
1135 let (mantissa_part, exponent_part): (String, String) = if let Some(pos) = exponent_pos {
1136 (active[..pos].to_string(), active[pos + 1..].to_string())
1137 } else {
1138 (active.clone(), String::new())
1139 };
1140
1141 let mantissa_chars: Vec<char> = mantissa_part.chars().collect();
1143 let decimal_pos = mantissa_chars.iter().position(|&c| c == decimal_sep);
1144 let (integer_part, fractional_part): (String, String) = if let Some(pos) = decimal_pos {
1145 (
1146 mantissa_chars[..pos].iter().collect(),
1147 mantissa_chars[pos + 1..].iter().collect(),
1148 )
1149 } else {
1150 (mantissa_part.clone(), String::new())
1151 };
1152
1153 if active.matches(decimal_sep).count() > 1 {
1155 return Err(FunctionError::ArgumentError(
1156 "D3081: Multiple decimal separators in picture".to_string(),
1157 ));
1158 }
1159
1160 if let Some(pos) = decimal_pos {
1162 if pos > 0 && active.chars().nth(pos - 1) == Some(grouping_sep) {
1163 return Err(FunctionError::ArgumentError(
1164 "D3087: Grouping separator adjacent to decimal separator".to_string(),
1165 ));
1166 }
1167 if pos + 1 < active.len() && active.chars().nth(pos + 1) == Some(grouping_sep) {
1168 return Err(FunctionError::ArgumentError(
1169 "D3087: Grouping separator adjacent to decimal separator".to_string(),
1170 ));
1171 }
1172 }
1173
1174 let grouping_str = format!("{}{}", grouping_sep, grouping_sep);
1176 if picture.contains(&grouping_str) {
1177 return Err(FunctionError::ArgumentError(
1178 "D3089: Consecutive grouping separators in picture".to_string(),
1179 ));
1180 }
1181
1182 let has_percent = picture.contains(percent_symbol);
1184 let has_per_mille = picture.contains(per_mille_symbol);
1185
1186 if picture.matches(percent_symbol).count() > 1 {
1188 return Err(FunctionError::ArgumentError(
1189 "D3082: Multiple percent signs in picture".to_string(),
1190 ));
1191 }
1192
1193 if picture.matches(per_mille_symbol).count() > 1 {
1195 return Err(FunctionError::ArgumentError(
1196 "D3083: Multiple per-mille signs in picture".to_string(),
1197 ));
1198 }
1199
1200 if has_percent && has_per_mille {
1202 return Err(FunctionError::ArgumentError(
1203 "D3084: Cannot have both percent and per-mille in picture".to_string(),
1204 ));
1205 }
1206
1207 if !integer_part.is_empty() && integer_part.ends_with(grouping_sep) {
1209 return Err(FunctionError::ArgumentError(
1210 "D3088: Integer part ends with grouping separator".to_string(),
1211 ));
1212 }
1213
1214 let has_digit_in_integer = integer_part
1216 .chars()
1217 .any(|c| is_digit_in_family(c, zero_digit) || c == digit_char);
1218 let has_digit_in_fractional = fractional_part
1219 .chars()
1220 .any(|c| is_digit_in_family(c, zero_digit) || c == digit_char);
1221 if !has_digit_in_integer && !has_digit_in_fractional {
1222 return Err(FunctionError::ArgumentError(
1223 "D3085: Picture must contain at least one digit".to_string(),
1224 ));
1225 }
1226
1227 let min_integer_digits = integer_part
1229 .chars()
1230 .filter(|&c| is_digit_in_family(c, zero_digit))
1231 .count();
1232
1233 let min_fractional_digits = fractional_part
1235 .chars()
1236 .filter(|&c| is_digit_in_family(c, zero_digit))
1237 .count();
1238 let mut max_fractional_digits = fractional_part
1239 .chars()
1240 .filter(|&c| is_digit_in_family(c, zero_digit) || c == digit_char)
1241 .count();
1242
1243 if decimal_pos.is_some() && max_fractional_digits == 0 {
1246 max_fractional_digits = 1;
1247 }
1248
1249 let mut grouping_positions = Vec::new();
1251 let int_chars: Vec<char> = integer_part.chars().collect();
1252 for (i, &c) in int_chars.iter().enumerate() {
1253 if c == grouping_sep {
1254 let digits_to_right = int_chars[i + 1..]
1256 .iter()
1257 .filter(|&&ch| is_digit_in_family(ch, zero_digit) || ch == digit_char)
1258 .count();
1259 grouping_positions.push(digits_to_right);
1260 }
1261 }
1262
1263 let regular_grouping = if grouping_positions.is_empty() {
1265 0
1266 } else if grouping_positions.len() == 1 {
1267 grouping_positions[0]
1268 } else {
1269 let first_interval = grouping_positions[0];
1271 if grouping_positions.iter().all(|&p| {
1272 grouping_positions.iter().filter(|&&x| x == p).count()
1273 == grouping_positions.len() / first_interval
1274 || (p % first_interval == 0 && grouping_positions.contains(&first_interval))
1275 }) {
1276 first_interval
1277 } else {
1278 0 }
1280 };
1281
1282 let mut fractional_grouping_positions = Vec::new();
1284 let frac_chars: Vec<char> = fractional_part.chars().collect();
1285 for (i, &c) in frac_chars.iter().enumerate() {
1286 if c == grouping_sep {
1287 let digits_to_left = frac_chars[..i]
1289 .iter()
1290 .filter(|&&ch| is_digit_in_family(ch, zero_digit) || ch == digit_char)
1291 .count();
1292 fractional_grouping_positions.push(digits_to_left);
1293 }
1294 }
1295
1296 let min_exponent_digits = if !exponent_part.is_empty() {
1298 exponent_part
1299 .chars()
1300 .filter(|&c| is_digit_in_family(c, zero_digit))
1301 .count()
1302 } else {
1303 0
1304 };
1305
1306 if !exponent_part.is_empty()
1308 && exponent_part
1309 .chars()
1310 .any(|c| !is_digit_in_family(c, zero_digit))
1311 {
1312 return Err(FunctionError::ArgumentError(
1313 "D3093: Exponent must contain only digit characters".to_string(),
1314 ));
1315 }
1316
1317 if exponent_pos.is_some() && min_exponent_digits == 0 {
1319 return Err(FunctionError::ArgumentError(
1320 "D3093: Exponent cannot be empty".to_string(),
1321 ));
1322 }
1323
1324 if min_exponent_digits > 0 && (has_percent || has_per_mille) {
1326 return Err(FunctionError::ArgumentError(
1327 "D3092: Percent/per-mille not allowed with exponential notation".to_string(),
1328 ));
1329 }
1330
1331 let mut seen_zero_in_integer = false;
1334 for c in integer_part.chars() {
1335 if is_digit_in_family(c, zero_digit) {
1336 seen_zero_in_integer = true;
1337 } else if c == digit_char && seen_zero_in_integer {
1338 return Err(FunctionError::ArgumentError(
1339 "D3090: Optional digit (#) cannot appear after mandatory digit (0) in integer part".to_string()
1340 ));
1341 }
1342 }
1343
1344 let mut seen_hash_in_fractional = false;
1347 for c in fractional_part.chars() {
1348 if c == digit_char {
1349 seen_hash_in_fractional = true;
1350 } else if is_digit_in_family(c, zero_digit) && seen_hash_in_fractional {
1351 return Err(FunctionError::ArgumentError(
1352 "D3091: Mandatory digit (0) cannot appear after optional digit (#) in fractional part".to_string()
1353 ));
1354 }
1355 }
1356
1357 let valid_chars: Vec<char> =
1360 vec![decimal_sep, grouping_sep, zero_digit, digit_char, 'e', 'E'];
1361 for c in mantissa_part.chars() {
1362 if !is_digit_in_family(c, zero_digit) && !valid_chars.contains(&c) {
1363 return Err(FunctionError::ArgumentError(format!(
1364 "D3086: Invalid character in picture: '{}'",
1365 c
1366 )));
1367 }
1368 }
1369
1370 let scaling_factor = min_integer_digits;
1372
1373 Ok(PictureParts {
1374 prefix,
1375 suffix,
1376 min_integer_digits,
1377 min_fractional_digits,
1378 max_fractional_digits,
1379 grouping_positions,
1380 fractional_grouping_positions,
1381 regular_grouping,
1382 has_decimal: decimal_pos.is_some(),
1383 has_integer_part: !integer_part.is_empty(),
1384 has_percent,
1385 has_per_mille,
1386 min_exponent_digits,
1387 scaling_factor,
1388 })
1389 }
1390
1391 fn apply_number_picture(
1393 value: f64,
1394 parts: &PictureParts,
1395 decimal_sep: char,
1396 grouping_sep: char,
1397 zero_digit: char,
1398 ) -> Result<String, FunctionError> {
1399 let (mantissa, exponent) = if parts.min_exponent_digits > 0 {
1401 let max_mantissa = 10_f64.powi(parts.scaling_factor as i32);
1403 let min_mantissa = 10_f64.powi(parts.scaling_factor as i32 - 1);
1404
1405 let mut m = value;
1406 let mut e = 0_i32;
1407
1408 while m < min_mantissa && m != 0.0 {
1410 m *= 10.0;
1411 e -= 1;
1412 }
1413 while m >= max_mantissa {
1414 m /= 10.0;
1415 e += 1;
1416 }
1417
1418 (m, Some(e))
1419 } else {
1420 (value, None)
1421 };
1422
1423 let factor = 10_f64.powi(parts.max_fractional_digits as i32);
1425 let rounded = (mantissa * factor).round() / factor;
1426
1427 let mut num_str = format!("{:.prec$}", rounded, prec = parts.max_fractional_digits);
1429
1430 if decimal_sep != '.' {
1432 num_str = num_str.replace('.', &decimal_sep.to_string());
1433 }
1434
1435 let decimal_pos = num_str.find(decimal_sep).unwrap_or(num_str.len());
1437 let mut integer_str = num_str[..decimal_pos].to_string();
1438 let mut fractional_str = if decimal_pos < num_str.len() {
1439 num_str[decimal_pos + 1..].to_string()
1440 } else {
1441 String::new()
1442 };
1443
1444 while integer_str.len() > 1 && integer_str.starts_with(zero_digit) {
1446 integer_str.remove(0);
1447 }
1448 if integer_str == zero_digit.to_string() && !parts.has_integer_part {
1450 integer_str.clear();
1451 }
1452 if integer_str.is_empty() && parts.has_integer_part {
1454 integer_str.push(zero_digit);
1455 }
1456
1457 while !fractional_str.is_empty() && fractional_str.ends_with(zero_digit) {
1459 fractional_str.pop();
1460 }
1461
1462 while integer_str.len() < parts.min_integer_digits {
1464 integer_str.insert(0, zero_digit);
1465 }
1466
1467 while fractional_str.len() < parts.min_fractional_digits {
1469 fractional_str.push(zero_digit);
1470 }
1471
1472 while fractional_str.len() > parts.min_fractional_digits {
1474 if fractional_str.ends_with(zero_digit) {
1475 fractional_str.pop();
1476 } else {
1477 break;
1478 }
1479 }
1480
1481 if parts.regular_grouping > 0 {
1483 let mut grouped = String::new();
1485 let chars: Vec<char> = integer_str.chars().collect();
1486 for (i, &c) in chars.iter().enumerate() {
1487 grouped.push(c);
1488 let pos_from_right = chars.len() - i - 1;
1489 if pos_from_right > 0 && pos_from_right % parts.regular_grouping == 0 {
1490 grouped.push(grouping_sep);
1491 }
1492 }
1493 integer_str = grouped;
1494 } else if !parts.grouping_positions.is_empty() {
1495 let mut grouped = String::new();
1497 let chars: Vec<char> = integer_str.chars().collect();
1498 for (i, &c) in chars.iter().enumerate() {
1499 grouped.push(c);
1500 let pos_from_right = chars.len() - i - 1;
1501 if parts.grouping_positions.contains(&pos_from_right) {
1502 grouped.push(grouping_sep);
1503 }
1504 }
1505 integer_str = grouped;
1506 }
1507
1508 if !parts.fractional_grouping_positions.is_empty() {
1510 let mut grouped = String::new();
1511 let chars: Vec<char> = fractional_str.chars().collect();
1512 for (i, &c) in chars.iter().enumerate() {
1513 grouped.push(c);
1514 let pos_from_left = i + 1;
1516 if parts.fractional_grouping_positions.contains(&pos_from_left) {
1517 grouped.push(grouping_sep);
1518 }
1519 }
1520 fractional_str = grouped;
1521 }
1522
1523 let mut result = if parts.has_decimal || !fractional_str.is_empty() {
1525 format!("{}{}{}", integer_str, decimal_sep, fractional_str)
1526 } else {
1527 integer_str
1528 };
1529
1530 if zero_digit != '0' {
1532 let zero_code = zero_digit as u32;
1533 result = result
1534 .chars()
1535 .map(|c| {
1536 if c.is_ascii_digit() {
1537 let digit_value = c as u32 - '0' as u32;
1538 char::from_u32(zero_code + digit_value).unwrap_or(c)
1539 } else {
1540 c
1541 }
1542 })
1543 .collect();
1544 }
1545
1546 if let Some(exp) = exponent {
1548 let exp_str = format!("{:0width$}", exp.abs(), width = parts.min_exponent_digits);
1550
1551 let exp_formatted = if zero_digit != '0' {
1553 let zero_code = zero_digit as u32;
1554 exp_str
1555 .chars()
1556 .map(|c| {
1557 if c.is_ascii_digit() {
1558 let digit_value = c as u32 - '0' as u32;
1559 char::from_u32(zero_code + digit_value).unwrap_or(c)
1560 } else {
1561 c
1562 }
1563 })
1564 .collect()
1565 } else {
1566 exp_str
1567 };
1568
1569 result.push('e');
1571 if exp < 0 {
1572 result.push('-');
1573 }
1574 result.push_str(&exp_formatted);
1575 }
1576
1577 Ok(result)
1578 }
1579
1580 #[derive(Debug)]
1582 struct PictureParts {
1583 prefix: String,
1584 suffix: String,
1585 min_integer_digits: usize,
1586 min_fractional_digits: usize,
1587 max_fractional_digits: usize,
1588 grouping_positions: Vec<usize>,
1589 fractional_grouping_positions: Vec<usize>,
1590 regular_grouping: usize,
1591 has_decimal: bool,
1592 has_integer_part: bool,
1593 has_percent: bool,
1594 has_per_mille: bool,
1595 min_exponent_digits: usize,
1596 scaling_factor: usize,
1597 }
1598
1599 pub fn format_base(value: f64, radix: Option<i64>) -> Result<JValue, FunctionError> {
1602 let int_value = value.round() as i64;
1604
1605 let radix = radix.unwrap_or(10);
1607
1608 if !(2..=36).contains(&radix) {
1610 return Err(FunctionError::ArgumentError(format!(
1611 "D3100: Radix must be between 2 and 36, got {}",
1612 radix
1613 )));
1614 }
1615
1616 let is_negative = int_value < 0;
1618 let abs_value = int_value.unsigned_abs();
1619
1620 let digits = "0123456789abcdefghijklmnopqrstuvwxyz";
1622 let mut result = String::new();
1623 let mut val = abs_value;
1624
1625 if val == 0 {
1626 result.push('0');
1627 } else {
1628 while val > 0 {
1629 let digit = (val % radix as u64) as usize;
1630 result.insert(0, digits.chars().nth(digit).unwrap());
1631 val /= radix as u64;
1632 }
1633 }
1634
1635 if is_negative {
1637 result.insert(0, '-');
1638 }
1639
1640 Ok(JValue::string(result))
1641 }
1642}
1643
1644pub mod array {
1646 use super::*;
1647
1648 pub fn count(arr: &[JValue]) -> Result<JValue, FunctionError> {
1650 Ok(JValue::Number(arr.len() as f64))
1651 }
1652
1653 pub fn append(arr1: &[JValue], val: &JValue) -> Result<JValue, FunctionError> {
1655 let mut result = arr1.to_vec();
1656 match val {
1657 JValue::Array(arr2) => result.extend(arr2.iter().cloned()),
1658 other => result.push(other.clone()),
1659 }
1660 Ok(JValue::array(result))
1661 }
1662
1663 pub fn reverse(arr: &[JValue]) -> Result<JValue, FunctionError> {
1665 let mut result = arr.to_vec();
1666 result.reverse();
1667 Ok(JValue::array(result))
1668 }
1669
1670 pub fn sort(arr: &[JValue]) -> Result<JValue, FunctionError> {
1672 let mut result = arr.to_vec();
1673
1674 let all_numbers = result.iter().all(|v| matches!(v, JValue::Number(_)));
1676 let all_strings = result.iter().all(|v| matches!(v, JValue::String(_)));
1677
1678 if all_numbers {
1679 result.sort_by(|a, b| {
1680 let a_num = a.as_f64().unwrap();
1681 let b_num = b.as_f64().unwrap();
1682 a_num
1683 .partial_cmp(&b_num)
1684 .unwrap_or(std::cmp::Ordering::Equal)
1685 });
1686 } else if all_strings {
1687 result.sort_by(|a, b| {
1688 let a_str = a.as_str().unwrap();
1689 let b_str = b.as_str().unwrap();
1690 a_str.cmp(b_str)
1691 });
1692 } else {
1693 return Err(FunctionError::TypeError(
1694 "sort() requires all elements to be of the same comparable type".to_string(),
1695 ));
1696 }
1697
1698 Ok(JValue::array(result))
1699 }
1700
1701 pub fn distinct(arr: &[JValue]) -> Result<JValue, FunctionError> {
1703 let mut result = Vec::new();
1704 let mut seen = Vec::new();
1705
1706 for value in arr {
1707 let mut is_new = true;
1708 for seen_value in &seen {
1709 if values_equal(value, seen_value) {
1710 is_new = false;
1711 break;
1712 }
1713 }
1714 if is_new {
1715 seen.push(value.clone());
1716 result.push(value.clone());
1717 }
1718 }
1719
1720 Ok(JValue::array(result))
1721 }
1722
1723 pub fn exists(value: &JValue) -> Result<JValue, FunctionError> {
1725 let is_missing = value.is_null() || value.is_undefined();
1726 Ok(JValue::Bool(!is_missing))
1727 }
1728
1729 pub fn values_equal(a: &JValue, b: &JValue) -> bool {
1731 match (a, b) {
1732 (JValue::Null, JValue::Null) => true,
1733 (JValue::Bool(a), JValue::Bool(b)) => a == b,
1734 (JValue::Number(a), JValue::Number(b)) => a == b,
1735 (JValue::String(a), JValue::String(b)) => a == b,
1736 (JValue::Array(a), JValue::Array(b)) => {
1737 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
1738 }
1739 (JValue::Object(a), JValue::Object(b)) => {
1740 a.len() == b.len()
1741 && a.iter()
1742 .all(|(k, v)| b.get(k).is_some_and(|v2| values_equal(v, v2)))
1743 }
1744 _ => false,
1745 }
1746 }
1747
1748 pub fn shuffle(arr: &[JValue]) -> Result<JValue, FunctionError> {
1751 if arr.len() <= 1 {
1752 return Ok(JValue::array(arr.to_vec()));
1753 }
1754
1755 use rand::seq::SliceRandom;
1756 use rand::thread_rng;
1757
1758 let mut result = arr.to_vec();
1759 let mut rng = thread_rng();
1760 result.shuffle(&mut rng);
1761
1762 Ok(JValue::array(result))
1763 }
1764}
1765
1766pub mod object {
1768 use super::*;
1769
1770 pub fn keys(obj: &IndexMap<String, JValue>) -> Result<JValue, FunctionError> {
1772 let keys: Vec<JValue> = obj.keys().map(|k| JValue::string(k.as_str())).collect();
1773 Ok(JValue::array(keys))
1774 }
1775
1776 pub fn lookup(obj: &IndexMap<String, JValue>, key: &str) -> Result<JValue, FunctionError> {
1778 Ok(obj.get(key).cloned().unwrap_or(JValue::Null))
1779 }
1780
1781 pub fn spread(obj: &IndexMap<String, JValue>) -> Result<JValue, FunctionError> {
1783 let pairs: Vec<JValue> = obj
1785 .iter()
1786 .map(|(k, v)| {
1787 let mut pair = IndexMap::new();
1788 pair.insert(k.clone(), v.clone());
1789 JValue::object(pair)
1790 })
1791 .collect();
1792 Ok(JValue::array(pairs))
1793 }
1794
1795 pub fn merge(objects: &[JValue]) -> Result<JValue, FunctionError> {
1797 let mut result = IndexMap::new();
1798
1799 for obj in objects {
1800 match obj {
1801 JValue::Object(map) => {
1802 for (k, v) in map.iter() {
1803 result.insert(k.clone(), v.clone());
1804 }
1805 }
1806 _ => {
1807 return Err(FunctionError::TypeError(
1808 "merge() requires all arguments to be objects".to_string(),
1809 ))
1810 }
1811 }
1812 }
1813
1814 Ok(JValue::object(result))
1815 }
1816}
1817
1818pub mod encoding {
1820 use super::*;
1821 use base64::{engine::general_purpose, Engine as _};
1822
1823 pub fn base64encode(s: &str) -> Result<JValue, FunctionError> {
1825 let encoded = general_purpose::STANDARD.encode(s.as_bytes());
1826 Ok(JValue::string(encoded))
1827 }
1828
1829 pub fn base64decode(s: &str) -> Result<JValue, FunctionError> {
1831 match general_purpose::STANDARD.decode(s.as_bytes()) {
1832 Ok(bytes) => match String::from_utf8(bytes) {
1833 Ok(decoded) => Ok(JValue::string(decoded)),
1834 Err(_) => Err(FunctionError::RuntimeError(
1835 "Invalid UTF-8 in decoded base64".to_string(),
1836 )),
1837 },
1838 Err(_) => Err(FunctionError::RuntimeError(
1839 "Invalid base64 string".to_string(),
1840 )),
1841 }
1842 }
1843
1844 pub fn encode_url_component(s: &str) -> Result<JValue, FunctionError> {
1846 let encoded = percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC)
1847 .to_string();
1848 Ok(JValue::string(encoded))
1849 }
1850
1851 pub fn decode_url_component(s: &str) -> Result<JValue, FunctionError> {
1853 match percent_encoding::percent_decode_str(s).decode_utf8() {
1854 Ok(decoded) => Ok(JValue::string(decoded.to_string())),
1855 Err(_) => Err(FunctionError::RuntimeError(
1856 "Invalid percent-encoded string".to_string(),
1857 )),
1858 }
1859 }
1860
1861 pub fn encode_url(s: &str) -> Result<JValue, FunctionError> {
1864 let encoded =
1866 percent_encoding::utf8_percent_encode(s, percent_encoding::CONTROLS).to_string();
1867 Ok(JValue::string(encoded))
1868 }
1869
1870 pub fn decode_url(s: &str) -> Result<JValue, FunctionError> {
1872 match percent_encoding::percent_decode_str(s).decode_utf8() {
1873 Ok(decoded) => Ok(JValue::string(decoded.to_string())),
1874 Err(_) => Err(FunctionError::RuntimeError(
1875 "Invalid percent-encoded URL".to_string(),
1876 )),
1877 }
1878 }
1879}
1880
1881#[cfg(test)]
1882mod tests {
1883 use super::*;
1884
1885 #[test]
1888 fn test_string_conversion() {
1889 assert_eq!(
1891 string::string(&JValue::string("hello"), None).unwrap(),
1892 JValue::string("hello")
1893 );
1894
1895 assert_eq!(
1897 string::string(&JValue::Number(42.0), None).unwrap(),
1898 JValue::string("42")
1899 );
1900
1901 assert_eq!(
1903 string::string(&JValue::Number(3.14), None).unwrap(),
1904 JValue::string("3.14")
1905 );
1906
1907 assert_eq!(
1909 string::string(&JValue::Bool(true), None).unwrap(),
1910 JValue::string("true")
1911 );
1912
1913 assert_eq!(
1915 string::string(&JValue::Null, None).unwrap(),
1916 JValue::string("null")
1917 );
1918
1919 assert_eq!(
1921 string::string(
1922 &JValue::array(vec![
1923 JValue::from(1i64),
1924 JValue::from(2i64),
1925 JValue::from(3i64)
1926 ]),
1927 None
1928 )
1929 .unwrap(),
1930 JValue::string("[1,2,3]")
1931 );
1932 }
1933
1934 #[test]
1935 fn test_length() {
1936 assert_eq!(string::length("hello").unwrap(), JValue::Number(5.0));
1937 assert_eq!(string::length("").unwrap(), JValue::Number(0.0));
1938 assert_eq!(
1940 string::length("Hello \u{4e16}\u{754c}").unwrap(),
1941 JValue::Number(8.0)
1942 );
1943 assert_eq!(
1944 string::length("\u{1f389}\u{1f38a}").unwrap(),
1945 JValue::Number(2.0)
1946 );
1947 }
1948
1949 #[test]
1950 fn test_uppercase_lowercase() {
1951 assert_eq!(string::uppercase("hello").unwrap(), JValue::string("HELLO"));
1952 assert_eq!(string::lowercase("HELLO").unwrap(), JValue::string("hello"));
1953 assert_eq!(
1954 string::uppercase("Hello World").unwrap(),
1955 JValue::string("HELLO WORLD")
1956 );
1957 }
1958
1959 #[test]
1960 fn test_substring() {
1961 assert_eq!(
1963 string::substring("hello world", 0, Some(5)).unwrap(),
1964 JValue::string("hello")
1965 );
1966
1967 assert_eq!(
1969 string::substring("hello world", 6, None).unwrap(),
1970 JValue::string("world")
1971 );
1972
1973 assert_eq!(
1975 string::substring("hello world", -5, Some(5)).unwrap(),
1976 JValue::string("world")
1977 );
1978
1979 assert_eq!(
1981 string::substring("Hello \u{4e16}\u{754c}", 6, Some(2)).unwrap(),
1982 JValue::string("\u{4e16}\u{754c}")
1983 );
1984
1985 assert_eq!(
1987 string::substring("hello", 0, Some(-1)).unwrap(),
1988 JValue::string("")
1989 );
1990 }
1991
1992 #[test]
1993 fn test_substring_before_after() {
1994 assert_eq!(
1996 string::substring_before("hello world", " ").unwrap(),
1997 JValue::string("hello")
1998 );
1999 assert_eq!(
2000 string::substring_before("hello world", "x").unwrap(),
2001 JValue::string("hello world")
2002 );
2003 assert_eq!(
2004 string::substring_before("hello world", "").unwrap(),
2005 JValue::string("")
2006 );
2007
2008 assert_eq!(
2010 string::substring_after("hello world", " ").unwrap(),
2011 JValue::string("world")
2012 );
2013 assert_eq!(
2015 string::substring_after("hello world", "x").unwrap(),
2016 JValue::string("hello world")
2017 );
2018 assert_eq!(
2019 string::substring_after("hello world", "").unwrap(),
2020 JValue::string("hello world")
2021 );
2022 }
2023
2024 #[test]
2025 fn test_trim() {
2026 assert_eq!(string::trim(" hello ").unwrap(), JValue::string("hello"));
2027 assert_eq!(string::trim("hello").unwrap(), JValue::string("hello"));
2028 assert_eq!(
2029 string::trim("\t\nhello\r\n").unwrap(),
2030 JValue::string("hello")
2031 );
2032 }
2033
2034 #[test]
2035 fn test_contains() {
2036 assert_eq!(
2037 string::contains("hello world", &JValue::string("world")).unwrap(),
2038 JValue::Bool(true)
2039 );
2040 assert_eq!(
2041 string::contains("hello world", &JValue::string("xyz")).unwrap(),
2042 JValue::Bool(false)
2043 );
2044 assert_eq!(
2045 string::contains("hello world", &JValue::string("")).unwrap(),
2046 JValue::Bool(true)
2047 );
2048 }
2049
2050 #[test]
2051 fn test_split() {
2052 assert_eq!(
2054 string::split("a,b,c", &JValue::string(","), None).unwrap(),
2055 JValue::array(vec![
2056 JValue::string("a"),
2057 JValue::string("b"),
2058 JValue::string("c")
2059 ])
2060 );
2061
2062 assert_eq!(
2064 string::split("a,b,c,d", &JValue::string(","), Some(2)).unwrap(),
2065 JValue::array(vec![JValue::string("a"), JValue::string("b")])
2066 );
2067
2068 assert_eq!(
2070 string::split("abc", &JValue::string(""), None).unwrap(),
2071 JValue::array(vec![
2072 JValue::string("a"),
2073 JValue::string("b"),
2074 JValue::string("c")
2075 ])
2076 );
2077 }
2078
2079 #[test]
2080 fn test_join() {
2081 let arr = vec![
2083 JValue::string("a"),
2084 JValue::string("b"),
2085 JValue::string("c"),
2086 ];
2087 assert_eq!(
2088 string::join(&arr, Some(",")).unwrap(),
2089 JValue::string("a,b,c")
2090 );
2091
2092 assert_eq!(string::join(&arr, None).unwrap(), JValue::string("abc"));
2094
2095 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2097 assert_eq!(
2098 string::join(&arr, Some("-")).unwrap(),
2099 JValue::string("1-2-3")
2100 );
2101 }
2102
2103 #[test]
2104 fn test_replace() {
2105 assert_eq!(
2107 string::replace("hello hello", &JValue::string("hello"), "hi", None).unwrap(),
2108 JValue::string("hi hi")
2109 );
2110
2111 assert_eq!(
2113 string::replace("hello hello hello", &JValue::string("hello"), "hi", Some(2)).unwrap(),
2114 JValue::string("hi hi hello")
2115 );
2116
2117 assert!(string::replace("hello", &JValue::string(""), "x", None).is_err());
2119 }
2120
2121 #[test]
2124 fn test_number_conversion() {
2125 assert_eq!(
2127 numeric::number(&JValue::Number(42.0)).unwrap(),
2128 JValue::Number(42.0)
2129 );
2130
2131 assert_eq!(
2133 numeric::number(&JValue::string("42")).unwrap(),
2134 JValue::Number(42.0)
2135 );
2136 assert_eq!(
2137 numeric::number(&JValue::string("3.14")).unwrap(),
2138 JValue::Number(3.14)
2139 );
2140 assert_eq!(
2141 numeric::number(&JValue::string(" 123 ")).unwrap(),
2142 JValue::Number(123.0)
2143 );
2144
2145 assert_eq!(
2147 numeric::number(&JValue::Bool(true)).unwrap(),
2148 JValue::Number(1.0)
2149 );
2150 assert_eq!(
2151 numeric::number(&JValue::Bool(false)).unwrap(),
2152 JValue::Number(0.0)
2153 );
2154
2155 assert!(numeric::number(&JValue::Null).is_err());
2157 assert!(numeric::number(&JValue::string("not a number")).is_err());
2158 }
2159
2160 #[test]
2161 fn test_sum() {
2162 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2164 assert_eq!(numeric::sum(&arr).unwrap(), JValue::Number(6.0));
2165
2166 assert_eq!(numeric::sum(&[]).unwrap(), JValue::Number(0.0));
2168
2169 let arr = vec![JValue::from(1i64), JValue::string("2")];
2171 assert!(numeric::sum(&arr).is_err());
2172 }
2173
2174 #[test]
2175 fn test_max_min() {
2176 let arr = vec![
2177 JValue::from(3i64),
2178 JValue::from(1i64),
2179 JValue::from(4i64),
2180 JValue::from(2i64),
2181 ];
2182
2183 assert_eq!(numeric::max(&arr).unwrap(), JValue::Number(4.0));
2184 assert_eq!(numeric::min(&arr).unwrap(), JValue::Number(1.0));
2185
2186 assert_eq!(numeric::max(&[]).unwrap(), JValue::Null);
2188 assert_eq!(numeric::min(&[]).unwrap(), JValue::Null);
2189 }
2190
2191 #[test]
2192 fn test_average() {
2193 let arr = vec![
2194 JValue::from(1i64),
2195 JValue::from(2i64),
2196 JValue::from(3i64),
2197 JValue::from(4i64),
2198 ];
2199 assert_eq!(numeric::average(&arr).unwrap(), JValue::Number(2.5));
2200
2201 assert_eq!(numeric::average(&[]).unwrap(), JValue::Null);
2203 }
2204
2205 #[test]
2206 fn test_math_functions() {
2207 assert_eq!(numeric::abs(-5.5).unwrap(), JValue::Number(5.5));
2209 assert_eq!(numeric::abs(5.5).unwrap(), JValue::Number(5.5));
2210
2211 assert_eq!(numeric::floor(3.7).unwrap(), JValue::Number(3.0));
2213 assert_eq!(numeric::floor(-3.7).unwrap(), JValue::Number(-4.0));
2214
2215 assert_eq!(numeric::ceil(3.2).unwrap(), JValue::Number(4.0));
2217 assert_eq!(numeric::ceil(-3.2).unwrap(), JValue::Number(-3.0));
2218
2219 assert_eq!(
2221 numeric::round(3.14159, Some(2)).unwrap(),
2222 JValue::Number(3.14)
2223 );
2224 assert_eq!(numeric::round(3.14159, None).unwrap(), JValue::Number(3.0));
2225 assert_eq!(numeric::round(3.14, Some(-1)).unwrap(), JValue::Number(0.0));
2227
2228 assert_eq!(numeric::sqrt(16.0).unwrap(), JValue::Number(4.0));
2230 assert!(numeric::sqrt(-1.0).is_err());
2231
2232 assert_eq!(numeric::power(2.0, 3.0).unwrap(), JValue::Number(8.0));
2234 assert_eq!(numeric::power(9.0, 0.5).unwrap(), JValue::Number(3.0));
2235 }
2236
2237 #[test]
2240 fn test_count() {
2241 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2242 assert_eq!(array::count(&arr).unwrap(), JValue::Number(3.0));
2243 assert_eq!(array::count(&[]).unwrap(), JValue::Number(0.0));
2244 }
2245
2246 #[test]
2247 fn test_append() {
2248 let arr1 = vec![JValue::from(1i64), JValue::from(2i64)];
2249
2250 let result = array::append(&arr1, &JValue::from(3i64)).unwrap();
2252 assert_eq!(
2253 result,
2254 JValue::array(vec![
2255 JValue::from(1i64),
2256 JValue::from(2i64),
2257 JValue::from(3i64)
2258 ])
2259 );
2260
2261 let arr2 = JValue::array(vec![JValue::from(3i64), JValue::from(4i64)]);
2263 let result = array::append(&arr1, &arr2).unwrap();
2264 assert_eq!(
2265 result,
2266 JValue::array(vec![
2267 JValue::from(1i64),
2268 JValue::from(2i64),
2269 JValue::from(3i64),
2270 JValue::from(4i64)
2271 ])
2272 );
2273 }
2274
2275 #[test]
2276 fn test_reverse() {
2277 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2278 assert_eq!(
2279 array::reverse(&arr).unwrap(),
2280 JValue::array(vec![
2281 JValue::from(3i64),
2282 JValue::from(2i64),
2283 JValue::from(1i64)
2284 ])
2285 );
2286 }
2287
2288 #[test]
2289 fn test_sort() {
2290 let arr = vec![
2292 JValue::from(3i64),
2293 JValue::from(1i64),
2294 JValue::from(4i64),
2295 JValue::from(2i64),
2296 ];
2297 assert_eq!(
2298 array::sort(&arr).unwrap(),
2299 JValue::array(vec![
2300 JValue::from(1i64),
2301 JValue::from(2i64),
2302 JValue::from(3i64),
2303 JValue::from(4i64)
2304 ])
2305 );
2306
2307 let arr = vec![
2309 JValue::string("charlie"),
2310 JValue::string("alice"),
2311 JValue::string("bob"),
2312 ];
2313 assert_eq!(
2314 array::sort(&arr).unwrap(),
2315 JValue::array(vec![
2316 JValue::string("alice"),
2317 JValue::string("bob"),
2318 JValue::string("charlie")
2319 ])
2320 );
2321
2322 let arr = vec![JValue::from(1i64), JValue::string("a")];
2324 assert!(array::sort(&arr).is_err());
2325 }
2326
2327 #[test]
2328 fn test_distinct() {
2329 let arr = vec![
2330 JValue::from(1i64),
2331 JValue::from(2i64),
2332 JValue::from(1i64),
2333 JValue::from(3i64),
2334 JValue::from(2i64),
2335 ];
2336 assert_eq!(
2337 array::distinct(&arr).unwrap(),
2338 JValue::array(vec![
2339 JValue::from(1i64),
2340 JValue::from(2i64),
2341 JValue::from(3i64)
2342 ])
2343 );
2344
2345 let arr = vec![
2347 JValue::string("a"),
2348 JValue::string("b"),
2349 JValue::string("a"),
2350 ];
2351 assert_eq!(
2352 array::distinct(&arr).unwrap(),
2353 JValue::array(vec![JValue::string("a"), JValue::string("b")])
2354 );
2355 }
2356
2357 #[test]
2358 fn test_exists() {
2359 assert_eq!(
2360 array::exists(&JValue::Number(42.0)).unwrap(),
2361 JValue::Bool(true)
2362 );
2363 assert_eq!(
2364 array::exists(&JValue::string("hello")).unwrap(),
2365 JValue::Bool(true)
2366 );
2367 assert_eq!(array::exists(&JValue::Null).unwrap(), JValue::Bool(false));
2368 }
2369
2370 #[test]
2373 fn test_keys() {
2374 let mut obj = IndexMap::new();
2375 obj.insert("name".to_string(), JValue::string("Alice"));
2376 obj.insert("age".to_string(), JValue::Number(30.0));
2377
2378 let result = object::keys(&obj).unwrap();
2379 if let JValue::Array(keys) = result {
2380 assert_eq!(keys.len(), 2);
2381 assert!(keys.contains(&JValue::string("name")));
2382 assert!(keys.contains(&JValue::string("age")));
2383 } else {
2384 panic!("Expected array of keys");
2385 }
2386 }
2387
2388 #[test]
2389 fn test_lookup() {
2390 let mut obj = IndexMap::new();
2391 obj.insert("name".to_string(), JValue::string("Alice"));
2392 obj.insert("age".to_string(), JValue::Number(30.0));
2393
2394 assert_eq!(
2395 object::lookup(&obj, "name").unwrap(),
2396 JValue::string("Alice")
2397 );
2398 assert_eq!(object::lookup(&obj, "age").unwrap(), JValue::Number(30.0));
2399 assert_eq!(object::lookup(&obj, "missing").unwrap(), JValue::Null);
2400 }
2401
2402 #[test]
2403 fn test_spread() {
2404 let mut obj = IndexMap::new();
2405 obj.insert("a".to_string(), JValue::from(1i64));
2406 obj.insert("b".to_string(), JValue::from(2i64));
2407
2408 let result = object::spread(&obj).unwrap();
2409 if let JValue::Array(pairs) = result {
2410 assert_eq!(pairs.len(), 2);
2411 for pair in pairs.iter() {
2413 if let JValue::Object(p) = pair {
2414 assert_eq!(
2415 p.len(),
2416 1,
2417 "Each spread element should be a single-key object"
2418 );
2419 } else {
2420 panic!("Expected Object in spread result");
2421 }
2422 }
2423 let all_keys: Vec<String> = pairs
2425 .iter()
2426 .filter_map(|p| {
2427 if let JValue::Object(m) = p {
2428 m.keys().next().cloned()
2429 } else {
2430 None
2431 }
2432 })
2433 .collect();
2434 assert!(all_keys.contains(&"a".to_string()));
2435 assert!(all_keys.contains(&"b".to_string()));
2436 } else {
2437 panic!("Expected array of key-value pairs");
2438 }
2439 }
2440
2441 #[test]
2442 fn test_merge() {
2443 let mut obj1 = IndexMap::new();
2444 obj1.insert("a".to_string(), JValue::from(1i64));
2445 obj1.insert("b".to_string(), JValue::from(2i64));
2446
2447 let mut obj2 = IndexMap::new();
2448 obj2.insert("b".to_string(), JValue::from(3i64));
2449 obj2.insert("c".to_string(), JValue::from(4i64));
2450
2451 let arr = vec![JValue::object(obj1), JValue::object(obj2)];
2452 let result = object::merge(&arr).unwrap();
2453
2454 if let JValue::Object(merged) = result {
2455 assert_eq!(merged.get("a"), Some(&JValue::from(1i64)));
2456 assert_eq!(merged.get("b"), Some(&JValue::from(3i64))); assert_eq!(merged.get("c"), Some(&JValue::from(4i64)));
2458 } else {
2459 panic!("Expected merged object");
2460 }
2461 }
2462}