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