1use std::net::IpAddr;
2
3use smol_str::SmolStr;
4use winnow::{
5 Parser,
6 ascii::{digit1, multispace0},
7 combinator::{alt, fail},
8 token::literal,
9};
10use wp_primitives::{
11 WResult,
12 fun::{fun_trait::Fun0Builder, parser::call_fun_args0},
13};
14use wp_primitives::{
15 atom::take_string,
16 fun::{
17 fun_trait::{Fun1Builder, Fun2Builder, ParseNext},
18 parser::{call_fun_args1, call_fun_args2, take_arr},
19 },
20};
21
22use crate::ast::{
23 WplFun,
24 processor::{
25 CharsHas, CharsIn, CharsInArg, CharsNotHas, CharsNotHasArg, CharsValue, DigitHas,
26 DigitHasArg, DigitIn, DigitInArg, DigitRange, Has, HasArg, IpIn, IpInArg, PipeNot,
27 RegexMatch, ReplaceFunc, SelectLast, StartsWith, TakeField, TargetCharsHas, TargetCharsIn,
28 TargetCharsNotHas, TargetDigitHas, TargetDigitIn, TargetHas, TargetIpIn, normalize_target,
29 },
30};
31
32use super::utils::take_key;
33
34fn take_quoted_string(input: &mut &str) -> WResult<String> {
37 literal("\"").parse_next(input)?;
38
39 let mut result = String::new();
40 let mut chars = input.chars();
41
42 loop {
43 match chars.next() {
44 None => {
45 return fail.parse_next(input);
46 }
47 Some('\\') => {
48 match chars.next() {
50 Some('"') => result.push('"'),
51 Some('\\') => result.push('\\'),
52 Some('n') => result.push('\n'),
53 Some('t') => result.push('\t'),
54 _ => {
55 return fail.parse_next(input);
56 }
57 }
58 }
59 Some('"') => {
60 let consumed = input.len() - chars.as_str().len();
62 *input = &input[consumed..];
63 return Ok(result);
64 }
65 Some(ch) => result.push(ch),
66 }
67 }
68}
69
70fn take_single_quoted_string(input: &mut &str) -> WResult<String> {
73 literal("'").parse_next(input)?;
74
75 let mut result = String::new();
76 let mut chars = input.chars();
77
78 loop {
79 match chars.next() {
80 None => {
81 return fail.parse_next(input);
82 }
83 Some('\\') => {
84 match chars.as_str().chars().next() {
86 Some('\'') => {
87 result.push('\'');
88 chars.next(); }
90 _ => {
91 result.push('\\');
93 }
94 }
95 }
96 Some('\'') => {
97 let consumed = input.len() - chars.as_str().len();
99 *input = &input[consumed..];
100 return Ok(result);
101 }
102 Some(ch) => result.push(ch),
103 }
104 }
105}
106
107fn take_string_or_quoted(input: &mut &str) -> WResult<String> {
109 multispace0.parse_next(input)?;
110 alt((
111 take_quoted_string,
112 take_single_quoted_string,
113 take_string.map(|s: &str| s.to_string()),
114 ))
115 .parse_next(input)
116}
117
118pub fn wpl_fun(input: &mut &str) -> WResult<WplFun> {
119 multispace0.parse_next(input)?;
120 let fun = alt((
121 parse_pipe_not,
123 alt((
124 call_fun_args2::<DigitRangeArg>.map(|arg| {
126 WplFun::DigitRange(DigitRange {
127 begin: arg.begin,
128 end: arg.end,
129 })
130 }),
131 call_fun_args1::<RegexMatch>.map(WplFun::RegexMatch),
132 call_fun_args1::<StartsWith>.map(WplFun::StartsWith),
133 call_fun_args1::<TakeField>.map(WplFun::SelectTake),
134 call_fun_args0::<SelectLast>.map(WplFun::SelectLast),
135 call_fun_args2::<TargetCharsHas>.map(WplFun::TargetCharsHas),
136 call_fun_args1::<CharsHas>.map(WplFun::CharsHas),
137 call_fun_args2::<TargetCharsNotHas>.map(WplFun::TargetCharsNotHas),
138 call_fun_args1::<CharsNotHasArg>
139 .map(|arg| WplFun::CharsNotHas(CharsNotHas { value: arg.value })),
140 call_fun_args2::<TargetCharsIn>.map(WplFun::TargetCharsIn),
141 call_fun_args1::<CharsInArg>.map(|arg| WplFun::CharsIn(CharsIn { value: arg.value })),
142 )),
143 alt((
144 call_fun_args2::<TargetDigitHas>.map(WplFun::TargetDigitHas),
145 call_fun_args1::<DigitHasArg>
146 .map(|arg| WplFun::DigitHas(DigitHas { value: arg.value })),
147 call_fun_args2::<TargetDigitIn>.map(WplFun::TargetDigitIn),
148 call_fun_args1::<DigitInArg>.map(|arg| WplFun::DigitIn(DigitIn { value: arg.value })),
149 call_fun_args2::<TargetIpIn>.map(WplFun::TargetIpIn),
150 call_fun_args1::<IpInArg>.map(|arg| WplFun::IpIn(IpIn { value: arg.value })),
151 call_fun_args1::<TargetHas>.map(WplFun::TargetHas),
152 call_fun_args0::<HasArg>.map(|_| WplFun::Has(Has)),
153 call_fun_args0::<JsonUnescape>.map(WplFun::TransJsonUnescape),
154 call_fun_args0::<Base64Decode>.map(WplFun::TransBase64Decode),
155 call_fun_args2::<ReplaceFunc>.map(WplFun::TransCharsReplace),
156 )),
157 ))
158 .parse_next(input)?;
159 Ok(fun)
160}
161
162fn parse_pipe_not(input: &mut &str) -> WResult<WplFun> {
164 literal("not").parse_next(input)?;
166 multispace0.parse_next(input)?;
167 literal("(").parse_next(input)?;
169 multispace0.parse_next(input)?;
170 let inner = wpl_fun.parse_next(input)?;
172 multispace0.parse_next(input)?;
173 literal(")").parse_next(input)?;
175
176 Ok(WplFun::PipeNot(PipeNot {
177 inner: Box::new(inner),
178 }))
179}
180
181impl Fun2Builder for TargetDigitHas {
182 type ARG1 = SmolStr;
183 type ARG2 = i64;
184
185 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
186 multispace0.parse_next(data)?;
187 let val = take_key.parse_next(data)?;
188 Ok(val.into())
189 }
190 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
191 multispace0.parse_next(data)?;
192 let val = digit1.parse_next(data)?;
193 Ok(val.parse::<i64>().unwrap_or(0))
194 }
195
196 fn fun_name() -> &'static str {
197 "f_digit_has"
198 }
199
200 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
201 Self {
202 target: normalize_target(args.0),
203 value: args.1,
204 }
205 }
206}
207
208impl Fun1Builder for CharsHas {
209 type ARG1 = SmolStr;
210
211 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
212 multispace0.parse_next(data)?;
213 let val = take_string.parse_next(data)?;
214 Ok(val.into())
215 }
216
217 fn fun_name() -> &'static str {
218 "chars_has"
219 }
220
221 fn build(args: Self::ARG1) -> Self {
222 Self { value: args }
223 }
224}
225
226impl Fun1Builder for CharsNotHasArg {
227 type ARG1 = SmolStr;
228
229 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
230 multispace0.parse_next(data)?;
231 let val = take_string.parse_next(data)?;
232 Ok(val.into())
233 }
234
235 fn fun_name() -> &'static str {
236 "chars_not_has"
237 }
238
239 fn build(args: Self::ARG1) -> Self {
240 Self { value: args }
241 }
242}
243
244impl Fun1Builder for CharsInArg {
245 type ARG1 = Vec<CharsValue>;
246
247 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
248 take_arr::<CharsValue>(data)
249 }
250
251 fn fun_name() -> &'static str {
252 "chars_in"
253 }
254
255 fn build(args: Self::ARG1) -> Self {
256 let value = args.iter().map(|i| i.0.clone()).collect();
257 Self { value }
258 }
259}
260
261impl Fun1Builder for DigitHasArg {
262 type ARG1 = i64;
263
264 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
265 multispace0.parse_next(data)?;
266 let val = digit1.parse_next(data)?;
267 Ok(val.parse::<i64>().unwrap_or(0))
268 }
269
270 fn fun_name() -> &'static str {
271 "digit_has"
272 }
273
274 fn build(args: Self::ARG1) -> Self {
275 Self { value: args }
276 }
277}
278
279impl Fun1Builder for DigitInArg {
280 type ARG1 = Vec<i64>;
281
282 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
283 take_arr::<i64>(data)
284 }
285
286 fn fun_name() -> &'static str {
287 "digit_in"
288 }
289
290 fn build(args: Self::ARG1) -> Self {
291 Self { value: args }
292 }
293}
294
295impl Fun1Builder for IpInArg {
296 type ARG1 = Vec<IpAddr>;
297
298 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
299 take_arr::<IpAddr>(data)
300 }
301
302 fn fun_name() -> &'static str {
303 "ip_in"
304 }
305
306 fn build(args: Self::ARG1) -> Self {
307 Self { value: args }
308 }
309}
310
311impl Fun0Builder for HasArg {
312 fn fun_name() -> &'static str {
313 "has"
314 }
315
316 fn build() -> Self {
317 HasArg
318 }
319}
320impl Fun2Builder for TargetCharsHas {
321 type ARG1 = SmolStr;
322 type ARG2 = SmolStr;
323
324 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
325 multispace0.parse_next(data)?;
326 let val = take_key.parse_next(data)?;
327 Ok(val.into())
328 }
329 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
330 multispace0.parse_next(data)?;
331 let val = take_string.parse_next(data)?;
332 Ok(val.into())
333 }
334
335 fn fun_name() -> &'static str {
336 "f_chars_has"
337 }
338 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
339 Self {
340 target: normalize_target(args.0),
341 value: args.1,
342 }
343 }
344}
345
346impl Fun2Builder for TargetCharsNotHas {
347 type ARG1 = SmolStr;
348 type ARG2 = SmolStr;
349
350 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
351 multispace0.parse_next(data)?;
352 let val = take_key.parse_next(data)?;
353 Ok(val.into())
354 }
355 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
356 multispace0.parse_next(data)?;
357 let val = take_string.parse_next(data)?;
358 Ok(val.into())
359 }
360
361 fn fun_name() -> &'static str {
362 "f_chars_not_has"
363 }
364 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
365 Self {
366 target: normalize_target(args.0),
367 value: args.1,
368 }
369 }
370}
371
372impl ParseNext<CharsValue> for CharsValue {
373 fn parse_next(input: &mut &str) -> WResult<CharsValue> {
374 let val = take_string.parse_next(input)?;
375 Ok(CharsValue(val.into()))
376 }
377}
378impl Fun2Builder for TargetCharsIn {
379 type ARG1 = SmolStr;
380 type ARG2 = Vec<CharsValue>;
381 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
382 multispace0.parse_next(data)?;
383 let val = take_key.parse_next(data)?;
384 Ok(val.into())
385 }
386
387 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
388 take_arr::<CharsValue>(data)
389 }
390
391 fn fun_name() -> &'static str {
392 "f_chars_in"
393 }
394
395 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
396 let value: Vec<SmolStr> = args.1.iter().map(|i| i.0.clone()).collect();
397 Self {
398 target: normalize_target(args.0),
399 value,
400 }
401 }
402}
403
404impl Fun2Builder for TargetDigitIn {
405 type ARG1 = SmolStr;
406 type ARG2 = Vec<i64>;
407
408 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
409 take_arr::<i64>(data)
410 }
411 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
412 multispace0.parse_next(data)?;
413 let val = take_key.parse_next(data)?;
414 Ok(val.into())
415 }
416
417 fn fun_name() -> &'static str {
418 "f_digit_in"
419 }
420 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
421 Self {
422 target: normalize_target(args.0),
423 value: args.1,
424 }
425 }
426}
427impl Fun1Builder for TargetHas {
428 type ARG1 = SmolStr;
429
430 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
431 multispace0.parse_next(data)?;
432 let val = take_key.parse_next(data)?;
433 Ok(val.into())
434 }
435
436 fn fun_name() -> &'static str {
437 "f_has"
438 }
439
440 fn build(args: Self::ARG1) -> Self {
441 Self {
442 target: normalize_target(args),
443 }
444 }
445}
446
447impl Fun2Builder for TargetIpIn {
448 type ARG1 = SmolStr;
449 type ARG2 = Vec<IpAddr>;
450
451 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
452 take_arr::<IpAddr>(data)
453 }
454 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
455 multispace0.parse_next(data)?;
456 let val = take_key.parse_next(data)?;
457 Ok(val.into())
458 }
459
460 fn fun_name() -> &'static str {
461 "f_ip_in"
462 }
463 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
464 Self {
465 target: normalize_target(args.0),
466 value: args.1,
467 }
468 }
469}
470
471use crate::ast::processor::JsonUnescape;
473
474impl Fun0Builder for JsonUnescape {
475 fn fun_name() -> &'static str {
476 "json_unescape"
477 }
478
479 fn build() -> Self {
480 JsonUnescape {}
481 }
482}
483
484use crate::ast::processor::Base64Decode;
485impl Fun0Builder for Base64Decode {
486 fn fun_name() -> &'static str {
487 "base64_decode"
488 }
489
490 fn build() -> Self {
491 Base64Decode {}
492 }
493}
494
495impl Fun2Builder for ReplaceFunc {
496 type ARG1 = SmolStr;
497 type ARG2 = SmolStr;
498
499 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
500 multispace0.parse_next(data)?;
501 let val = take_string_or_quoted.parse_next(data)?;
502 Ok(val.into())
503 }
504
505 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
506 multispace0.parse_next(data)?;
507 let val = take_string_or_quoted.parse_next(data)?;
508 Ok(val.into())
509 }
510
511 fn fun_name() -> &'static str {
512 "chars_replace"
513 }
514
515 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
516 Self {
517 target: args.0,
518 value: args.1,
519 }
520 }
521}
522
523#[derive(Clone, Debug, PartialEq)]
525pub struct DigitRangeArg {
526 pub(crate) begin: i64,
527 pub(crate) end: i64,
528}
529
530impl Fun2Builder for DigitRangeArg {
531 type ARG1 = i64;
532 type ARG2 = i64;
533
534 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
535 multispace0.parse_next(data)?;
536 let val = digit1.parse_next(data)?;
537 Ok(val.parse::<i64>().unwrap_or(0))
538 }
539
540 fn args2(data: &mut &str) -> WResult<Self::ARG2> {
541 multispace0.parse_next(data)?;
542 let val = digit1.parse_next(data)?;
543 Ok(val.parse::<i64>().unwrap_or(0))
544 }
545
546 fn fun_name() -> &'static str {
547 "digit_range"
548 }
549
550 fn build(args: (Self::ARG1, Self::ARG2)) -> Self {
551 Self {
552 begin: args.0,
553 end: args.1,
554 }
555 }
556}
557
558impl Fun1Builder for RegexMatch {
559 type ARG1 = SmolStr;
560
561 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
562 multispace0.parse_next(data)?;
563 let val = take_string_or_quoted.parse_next(data)?;
564 Ok(val.into())
565 }
566
567 fn fun_name() -> &'static str {
568 "regex_match"
569 }
570
571 fn build(args: Self::ARG1) -> Self {
572 Self { pattern: args }
573 }
574}
575
576impl Fun1Builder for StartsWith {
577 type ARG1 = SmolStr;
578
579 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
580 multispace0.parse_next(data)?;
581 let val = take_string_or_quoted.parse_next(data)?;
582 Ok(val.into())
583 }
584
585 fn fun_name() -> &'static str {
586 "starts_with"
587 }
588
589 fn build(args: Self::ARG1) -> Self {
590 Self { prefix: args }
591 }
592}
593
594impl Fun1Builder for TakeField {
595 type ARG1 = SmolStr;
596
597 fn args1(data: &mut &str) -> WResult<Self::ARG1> {
598 multispace0.parse_next(data)?;
599 let val = alt((
600 take_quoted_string.map(SmolStr::from),
601 take_single_quoted_string.map(SmolStr::from),
602 take_key.map(SmolStr::from),
603 ))
604 .parse_next(data)?;
605 Ok(val)
606 }
607
608 fn fun_name() -> &'static str {
609 "take"
610 }
611
612 fn build(args: Self::ARG1) -> Self {
613 Self { target: args }
614 }
615}
616
617impl Fun0Builder for SelectLast {
618 fn fun_name() -> &'static str {
619 "last"
620 }
621
622 fn build() -> Self {
623 SelectLast {}
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use std::net::{Ipv4Addr, Ipv6Addr};
630
631 use orion_error::TestAssert;
632
633 use crate::ast::processor::{Has, JsonUnescape, ReplaceFunc, SelectLast, TakeField};
634
635 use super::*;
636
637 #[test]
638 fn test_parse_fun() {
639 let fun = wpl_fun.parse(r#"f_has(src)"#).assert();
640 assert_eq!(
641 fun,
642 WplFun::TargetHas(TargetHas {
643 target: Some("src".into())
644 })
645 );
646
647 let fun = wpl_fun.parse("has()").assert();
648 assert_eq!(fun, WplFun::Has(Has));
649
650 let fun = wpl_fun.parse(r#"f_digit_in(src, [1,2,3])"#).assert();
651 assert_eq!(
652 fun,
653 WplFun::TargetDigitIn(TargetDigitIn {
654 target: Some("src".into()),
655 value: vec![1, 2, 3]
656 })
657 );
658
659 let fun = wpl_fun.parse("digit_has(42)").assert();
660 assert_eq!(fun, WplFun::DigitHas(DigitHas { value: 42 }));
661
662 let fun = wpl_fun.parse("digit_in([4,5])").assert();
663 assert_eq!(fun, WplFun::DigitIn(DigitIn { value: vec![4, 5] }));
664
665 let fun = wpl_fun
666 .parse(r#"f_ip_in(src, [127.0.0.1, 127.0.0.2])"#)
667 .assert();
668 assert_eq!(
669 fun,
670 WplFun::TargetIpIn(TargetIpIn {
671 target: Some("src".into()),
672 value: vec![
673 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
674 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2))
675 ]
676 })
677 );
678
679 let fun = wpl_fun.parse(r#"ip_in([127.0.0.1,::1])"#).assert();
680 assert_eq!(
681 fun,
682 WplFun::IpIn(IpIn {
683 value: vec![
684 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
685 IpAddr::V6(Ipv6Addr::LOCALHOST),
686 ],
687 })
688 );
689
690 let fun = wpl_fun
692 .parse(r#"f_ip_in(src, [::1, 2001:db8::1])"#)
693 .assert();
694 assert_eq!(
695 fun,
696 WplFun::TargetIpIn(TargetIpIn {
697 target: Some("src".into()),
698 value: vec![
699 IpAddr::V6(Ipv6Addr::LOCALHOST),
700 IpAddr::V6("2001:db8::1".parse().unwrap()),
701 ]
702 })
703 );
704
705 let fun = wpl_fun.parse("json_unescape()").assert();
706 assert_eq!(fun, WplFun::TransJsonUnescape(JsonUnescape {}));
707
708 assert!(wpl_fun.parse("json_unescape(decoded)").is_err());
709
710 let fun = wpl_fun.parse("take(src)").assert();
711 assert_eq!(
712 fun,
713 WplFun::SelectTake(TakeField {
714 target: "src".into(),
715 })
716 );
717
718 let fun = wpl_fun.parse(r#"take("@key")"#).assert();
720 assert_eq!(
721 fun,
722 WplFun::SelectTake(TakeField {
723 target: "@key".into(),
724 })
725 );
726
727 let fun = wpl_fun.parse("take('@field')").assert();
729 assert_eq!(
730 fun,
731 WplFun::SelectTake(TakeField {
732 target: "@field".into(),
733 })
734 );
735
736 let fun = wpl_fun.parse(r#"take("field with spaces")"#).assert();
738 assert_eq!(
739 fun,
740 WplFun::SelectTake(TakeField {
741 target: "field with spaces".into(),
742 })
743 );
744
745 let fun = wpl_fun.parse("take('field,with,commas')").assert();
747 assert_eq!(
748 fun,
749 WplFun::SelectTake(TakeField {
750 target: "field,with,commas".into(),
751 })
752 );
753
754 let fun = wpl_fun.parse(r#"take("field\"name")"#).assert();
756 assert_eq!(
757 fun,
758 WplFun::SelectTake(TakeField {
759 target: "field\"name".into(),
760 })
761 );
762
763 let fun = wpl_fun.parse("take('field\\'name')").assert();
765 assert_eq!(
766 fun,
767 WplFun::SelectTake(TakeField {
768 target: "field'name".into(),
769 })
770 );
771
772 let fun = wpl_fun.parse(r"take('raw\nstring')").assert();
774 assert_eq!(
775 fun,
776 WplFun::SelectTake(TakeField {
777 target: r"raw\nstring".into(),
778 })
779 );
780
781 let fun = wpl_fun.parse(r"take('path\to\file')").assert();
782 assert_eq!(
783 fun,
784 WplFun::SelectTake(TakeField {
785 target: r"path\to\file".into(),
786 })
787 );
788
789 let fun = wpl_fun.parse(r#"take("line\nbreak")"#).assert();
791 assert_eq!(
792 fun,
793 WplFun::SelectTake(TakeField {
794 target: "line\nbreak".into(),
795 })
796 );
797
798 let fun = wpl_fun.parse("last()").assert();
799 assert_eq!(fun, WplFun::SelectLast(SelectLast {}));
800
801 let fun = wpl_fun.parse("f_chars_has(_, foo)").assert();
802 assert_eq!(
803 fun,
804 WplFun::TargetCharsHas(TargetCharsHas {
805 target: None,
806 value: "foo".into(),
807 })
808 );
809
810 let fun = wpl_fun.parse("chars_has(bar)").assert();
811 assert_eq!(
812 fun,
813 WplFun::CharsHas(CharsHas {
814 value: "bar".into(),
815 })
816 );
817
818 let fun = wpl_fun.parse("chars_has(中文)").assert();
819 assert_eq!(
820 fun,
821 WplFun::CharsHas(CharsHas {
822 value: "中文".into(),
823 })
824 );
825
826 let fun = wpl_fun.parse("chars_not_has(baz)").assert();
827 assert_eq!(
828 fun,
829 WplFun::CharsNotHas(CharsNotHas {
830 value: "baz".into(),
831 })
832 );
833
834 let fun = wpl_fun.parse("chars_in([foo,bar])").assert();
835 assert_eq!(
836 fun,
837 WplFun::CharsIn(CharsIn {
838 value: vec!["foo".into(), "bar".into()],
839 })
840 );
841
842 let fun = wpl_fun.parse("base64_decode()").assert();
843 assert_eq!(fun, WplFun::TransBase64Decode(Base64Decode {}));
844 assert!(wpl_fun.parse("base64_decode(decoded)").is_err());
845
846 let fun = wpl_fun.parse(r#"chars_replace(hello, hi)"#).assert();
848 assert_eq!(
849 fun,
850 WplFun::TransCharsReplace(ReplaceFunc {
851 target: "hello".into(),
852 value: "hi".into(),
853 })
854 );
855
856 let fun = wpl_fun
857 .parse(r#"chars_replace(old_value, new_value)"#)
858 .assert();
859 assert_eq!(
860 fun,
861 WplFun::TransCharsReplace(ReplaceFunc {
862 target: "old_value".into(),
863 value: "new_value".into(),
864 })
865 );
866
867 let fun = wpl_fun.parse(r#"chars_replace(旧值, 新值)"#).assert();
869 assert_eq!(
870 fun,
871 WplFun::TransCharsReplace(ReplaceFunc {
872 target: "旧值".into(),
873 value: "新值".into(),
874 })
875 );
876
877 let fun = wpl_fun
879 .parse(r#"chars_replace(test-old, test-new)"#)
880 .assert();
881 assert_eq!(
882 fun,
883 WplFun::TransCharsReplace(ReplaceFunc {
884 target: "test-old".into(),
885 value: "test-new".into(),
886 })
887 );
888
889 let fun = wpl_fun
891 .parse(r#"chars_replace(error_code, status_code)"#)
892 .assert();
893 assert_eq!(
894 fun,
895 WplFun::TransCharsReplace(ReplaceFunc {
896 target: "error_code".into(),
897 value: "status_code".into(),
898 })
899 );
900
901 let fun = wpl_fun
903 .parse(r#"chars_replace("test,old", "test,new")"#)
904 .assert();
905 assert_eq!(
906 fun,
907 WplFun::TransCharsReplace(ReplaceFunc {
908 target: "test,old".into(),
909 value: "test,new".into(),
910 })
911 );
912
913 let fun = wpl_fun
915 .parse(r#"chars_replace("hello world", "goodbye world")"#)
916 .assert();
917 assert_eq!(
918 fun,
919 WplFun::TransCharsReplace(ReplaceFunc {
920 target: "hello world".into(),
921 value: "goodbye world".into(),
922 })
923 );
924
925 let fun = wpl_fun
927 .parse(r#"chars_replace("test,old", new_value)"#)
928 .assert();
929 assert_eq!(
930 fun,
931 WplFun::TransCharsReplace(ReplaceFunc {
932 target: "test,old".into(),
933 value: "new_value".into(),
934 })
935 );
936
937 let fun = wpl_fun.parse(r#"chars_replace(test, "")"#).assert();
939 assert_eq!(
940 fun,
941 WplFun::TransCharsReplace(ReplaceFunc {
942 target: "test".into(),
943 value: "".into(),
944 })
945 );
946
947 let fun = wpl_fun
949 .parse(r#"chars_replace("test,old", "\"test,new\"")"#)
950 .assert();
951 assert_eq!(
952 fun,
953 WplFun::TransCharsReplace(ReplaceFunc {
954 target: "test,old".into(),
955 value: "\"test,new\"".into(),
956 })
957 );
958
959 let fun = wpl_fun
961 .parse(r#"chars_replace("path\\to\\file", "new\\path")"#)
962 .assert();
963 assert_eq!(
964 fun,
965 WplFun::TransCharsReplace(ReplaceFunc {
966 target: "path\\to\\file".into(),
967 value: "new\\path".into(),
968 })
969 );
970
971 let fun = wpl_fun
973 .parse(r#"chars_replace("line1\nline2", "tab\there")"#)
974 .assert();
975 assert_eq!(
976 fun,
977 WplFun::TransCharsReplace(ReplaceFunc {
978 target: "line1\nline2".into(),
979 value: "tab\there".into(),
980 })
981 );
982 }
983
984 #[test]
985 fn test_parse_digit_range() {
986 use winnow::Parser;
987 use wp_primitives::fun::parser::call_fun_args2;
988
989 let mut input = "digit_range(1, 10)";
991 let result = call_fun_args2::<DigitRangeArg>.parse_next(&mut input);
992 assert!(
993 result.is_ok(),
994 "Simple case should parse successfully: {:?}",
995 result
996 );
997 let arg = result.unwrap();
998 assert_eq!(arg.begin, 1);
999 assert_eq!(arg.end, 10);
1000
1001 let mut input2 = "digit_range(100, 200)";
1003 let result2 = call_fun_args2::<DigitRangeArg>.parse_next(&mut input2);
1004 assert!(
1005 result2.is_ok(),
1006 "Different values should parse: {:?}",
1007 result2
1008 );
1009 let arg2 = result2.unwrap();
1010 assert_eq!(arg2.begin, 100);
1011 assert_eq!(arg2.end, 200);
1012 }
1013
1014 #[test]
1015 fn test_parse_regex_match() {
1016 let mut wpl_fun = wpl_fun;
1017
1018 let fun = wpl_fun.parse(r"regex_match('^\d+$')").assert();
1020 assert_eq!(
1021 fun,
1022 WplFun::RegexMatch(RegexMatch {
1023 pattern: r"^\d+$".into(),
1024 })
1025 );
1026
1027 let fun = wpl_fun.parse(r"regex_match('^\w+@\w+\.\w+$')").assert();
1029 assert_eq!(
1030 fun,
1031 WplFun::RegexMatch(RegexMatch {
1032 pattern: r"^\w+@\w+\.\w+$".into(),
1033 })
1034 );
1035
1036 let fun = wpl_fun.parse(r"regex_match('^(GET|POST|PUT)$')").assert();
1038 assert_eq!(
1039 fun,
1040 WplFun::RegexMatch(RegexMatch {
1041 pattern: r"^(GET|POST|PUT)$".into(),
1042 })
1043 );
1044 }
1045
1046 #[test]
1047 fn test_parse_start_with() {
1048 let mut wpl_fun = wpl_fun;
1049
1050 let fun = wpl_fun.parse(r"starts_with('http')").assert();
1052 assert_eq!(
1053 fun,
1054 WplFun::StartsWith(StartsWith {
1055 prefix: "http".into(),
1056 })
1057 );
1058
1059 let fun = wpl_fun.parse(r"starts_with('https://')").assert();
1061 assert_eq!(
1062 fun,
1063 WplFun::StartsWith(StartsWith {
1064 prefix: "https://".into(),
1065 })
1066 );
1067
1068 let fun = wpl_fun.parse(r"starts_with('/')").assert();
1070 assert_eq!(fun, WplFun::StartsWith(StartsWith { prefix: "/".into() }));
1071 }
1072
1073 #[test]
1074 fn test_parse_pipe_not() {
1075 let fun = wpl_fun.parse(r"not(f_chars_has(dev_type, NDS))").assert();
1077
1078 if let WplFun::PipeNot(pipe_not) = fun {
1079 if let WplFun::TargetCharsHas(inner) = *pipe_not.inner {
1080 assert_eq!(inner.target, Some("dev_type".into()));
1081 assert_eq!(inner.value.as_str(), "NDS");
1082 } else {
1083 panic!("Inner function should be TargetCharsHas");
1084 }
1085 } else {
1086 panic!("Should parse as PipeNot");
1087 }
1088
1089 let fun = wpl_fun.parse(r"not(has())").assert();
1091 if let WplFun::PipeNot(pipe_not) = fun {
1092 assert!(matches!(*pipe_not.inner, WplFun::Has(_)));
1093 } else {
1094 panic!("Should parse as PipeNot with Has");
1095 }
1096
1097 let fun = wpl_fun.parse(r"not(f_has(field_name))").assert();
1099 if let WplFun::PipeNot(pipe_not) = fun {
1100 if let WplFun::TargetHas(inner) = *pipe_not.inner {
1101 assert_eq!(inner.target, Some("field_name".into()));
1102 } else {
1103 panic!("Inner function should be TargetHas");
1104 }
1105 } else {
1106 panic!("Should parse as PipeNot");
1107 }
1108
1109 let fun = wpl_fun.parse(r"not(not(has()))").assert();
1111 if let WplFun::PipeNot(outer_not) = fun {
1112 if let WplFun::PipeNot(inner_not) = *outer_not.inner {
1113 assert!(matches!(*inner_not.inner, WplFun::Has(_)));
1114 } else {
1115 panic!("Inner should be PipeNot");
1116 }
1117 } else {
1118 panic!("Should parse as nested PipeNot");
1119 }
1120 }
1121}