Skip to main content

wpl/eval/runtime/
vm_unit.rs

1use crate::ast::WplPipe;
2use crate::ast::group::{WplGroup, WplGroupType};
3use crate::ast::{WplExpress, WplStatementType};
4use crate::ast::{WplField, WplSep};
5use crate::compat::New3;
6use crate::eval::builtins::{self, PipeLineResult, raw_to_utf8_string};
7use crate::eval::runtime::field::FieldEvalUnit;
8use crate::eval::runtime::field_pipe::PipeEnum;
9use crate::eval::runtime::group::WplEvalGroup;
10use std::borrow::Cow;
11use wp_model_core::raw::RawData;
12use wp_parse_api::{PipeHold, WparseError, WparseReason};
13
14use crate::parser::error::{WplCodeError, WplCodeReason};
15use crate::parser::wpl_rule::wpl_rule;
16use anyhow::Result;
17use orion_error::{ErrorWith, ToStructError, UvsFrom};
18use wp_log::debug_edata;
19use wp_model_core::model::DataRecord;
20use wp_primitives::Parser;
21use wp_primitives::WResult as ModalResult;
22
23// Internal DataResult for wp-lang usage
24// Plugin developers should use wp_parse_api::DataResult instead
25pub type DataResult = Result<(DataRecord, String), WparseError>;
26pub const OPTIMIZE_TIMES: usize = 10000;
27
28pub trait IntoRawData {
29    fn into_raw(self) -> RawData;
30}
31
32impl IntoRawData for RawData {
33    fn into_raw(self) -> RawData {
34        self
35    }
36}
37
38impl IntoRawData for String {
39    fn into_raw(self) -> RawData {
40        RawData::from_string(self)
41    }
42}
43
44impl IntoRawData for &String {
45    fn into_raw(self) -> RawData {
46        RawData::from_string(self.as_str())
47    }
48}
49
50impl IntoRawData for &str {
51    fn into_raw(self) -> RawData {
52        RawData::from_string(self)
53    }
54}
55
56#[derive(Default, Clone)]
57pub struct WplEvaluator {
58    preorder: Vec<PipeHold>,
59    group_units: Vec<WplEvalGroup>,
60}
61unsafe impl Send for WplEvaluator {}
62
63impl WplEvaluator {
64    pub fn preorder_proc(&self, data: RawData) -> Result<Vec<PipeLineResult>, WparseError> {
65        let mut pipe_obj = Vec::new();
66        let mut target = data;
67        for proc_unit in &self.preorder {
68            target = proc_unit.process(target)?;
69            pipe_obj.push(PipeLineResult {
70                name: proc_unit.name().to_string(),
71                result: raw_to_utf8_string(&target),
72            });
73        }
74        Ok(pipe_obj)
75    }
76
77    fn pipe_proc(&self, e_id: u64, data: RawData) -> Result<RawData, WparseError> {
78        let mut target = data;
79        for proc_unit in &self.preorder {
80            target = proc_unit
81                .process(target)
82                .want("pipe convert")
83                .with(e_id.to_string())
84                .with(proc_unit.name())?;
85
86            debug_edata!(
87                e_id,
88                "pipe  {}  out:{}",
89                proc_unit.name(),
90                raw_to_utf8_string(&target)
91            );
92        }
93        Ok(target)
94    }
95
96    pub fn proc<D>(&self, e_id: u64, data: D, oth_suc_len: usize) -> DataResult
97    where
98        D: IntoRawData,
99    {
100        let mut working_raw: RawData = data.into_raw();
101        if !self.preorder.is_empty() {
102            working_raw = self.pipe_proc(e_id, working_raw)?;
103        }
104
105        let input_holder: Cow<'_, str> = match &working_raw {
106            RawData::String(s) => Cow::Borrowed(s.as_str()),
107            RawData::Bytes(b) => Cow::Owned(String::from_utf8_lossy(b).into_owned()),
108            RawData::ArcBytes(b) => Cow::Owned(String::from_utf8_lossy(b).into_owned()),
109        };
110        let mut input: &str = input_holder.as_ref();
111
112        let ori_len = input.len();
113        match self.parse_groups(e_id, &mut input) {
114            Ok(log) => Ok((log, input.to_string())),
115            Err(e) => {
116                let cur_pos = input.len();
117                let pos = ori_len - cur_pos;
118                if pos >= oth_suc_len {
119                    Err(WparseReason::from_data()
120                        .to_err()
121                        .with_detail(format!("{input} @ {pos}"))
122                        .with_detail(e.to_string()))
123                } else {
124                    Err(WparseError::from(WparseReason::NotMatch))
125                }
126            }
127        }
128    }
129    pub fn from_code(code: &str) -> Result<Self, WplCodeError> {
130        let mut cur_code = code;
131        let rule = wpl_rule.parse_next(&mut cur_code).map_err(
132            |err| {
133                WplCodeReason::from_data()
134                    .to_err()
135                    .with_detail(cur_code.to_string())
136                    .with_detail(err.to_string())
137            }, //ParseCodeError::new(err.to_string())
138        )?;
139        let WplStatementType::Express(rule_define) = rule.statement;
140        Self::from(&rule_define, None)
141    }
142    pub fn from(dy_lang: &WplExpress, inject: Option<&WplExpress>) -> Result<Self, WplCodeError> {
143        let mut target_dpl = WplEvaluator {
144            ..Default::default()
145        };
146        if let Some(inject) = inject {
147            Self::assemble_ins(inject, &mut target_dpl)?;
148        }
149        Self::assemble_ins(dy_lang, &mut target_dpl)?;
150        Ok(target_dpl)
151    }
152
153    fn assemble_ins(
154        express: &WplExpress,
155        target_dpl: &mut WplEvaluator,
156    ) -> Result<(), WplCodeError> {
157        builtins::ensure_builtin_pipe_units();
158        for proc in &express.pipe_process {
159            if let Some(pipe_unit) = builtins::registry::create_pipe_unit(proc) {
160                target_dpl.preorder.push(pipe_unit);
161            } else {
162                return Err(WplCodeError::from(WplCodeReason::UnSupport(format!(
163                    "Pipe processor '{}' not registered",
164                    proc
165                ))));
166            }
167        }
168        for (i, group) in express.group.iter().enumerate() {
169            let p_group = Self::assemble_group(i + 1, group)?;
170            target_dpl.group_units.push(p_group);
171        }
172        Ok(())
173    }
174    fn assemble_group(index: usize, group: &WplGroup) -> Result<WplEvalGroup, WplCodeError> {
175        let mut p_group =
176            WplEvalGroup::new(index, group.meta.clone(), group.base_group_sep.clone());
177        for (idx, conf) in group.fields.iter().enumerate() {
178            let fpu = Self::assemble_fpu(idx + 1, conf, group.meta.clone())?;
179            p_group.field_units.push(fpu)
180        }
181        Ok(p_group)
182    }
183    fn assemble_pipe(parent_idx: usize, pipe: &WplPipe) -> Result<PipeEnum, WplCodeError> {
184        match pipe {
185            WplPipe::Fun(fun) => Ok(PipeEnum::Fun(fun.clone())),
186            WplPipe::Group(group) => {
187                let mut p_group = WplEvalGroup::new(
188                    parent_idx * 10,
189                    group.meta.clone(),
190                    group.base_group_sep.clone(),
191                );
192                for conf in &group.fields {
193                    let fpu = Self::assemble_fpu(0, conf, group.meta.clone())?;
194                    p_group.field_units.push(fpu)
195                }
196                Ok(PipeEnum::Group(p_group))
197            }
198        }
199    }
200
201    pub fn assemble_fpu(
202        idx: usize,
203        conf: &WplField,
204        grp: WplGroupType,
205    ) -> Result<FieldEvalUnit, WplCodeError> {
206        let mut fpu = Self::build_fpu(idx, &grp, conf)?;
207        if let Some(subs) = conf.sub_fields() {
208            for (k, conf) in subs.conf_items().exact_iter() {
209                let sub_fpu = Self::build_fpu(0, &grp, conf)?;
210                fpu.add_sub_fpu(k.clone(), sub_fpu);
211            }
212            for (k, _, conf) in subs.conf_items().wild_iter() {
213                let sub_fpu = Self::build_fpu(0, &grp, conf)?;
214                fpu.add_sub_fpu(k.clone(), sub_fpu);
215            }
216        }
217        Ok(fpu)
218    }
219
220    fn build_fpu(
221        idx: usize,
222        grp: &WplGroupType,
223        conf: &WplField,
224    ) -> Result<FieldEvalUnit, WplCodeError> {
225        let mut fpu = FieldEvalUnit::create(idx, conf.clone(), grp.clone())?;
226        for pipe_conf in conf.pipe.clone() {
227            let pipe = Self::assemble_pipe(idx, &pipe_conf)?;
228            fpu.add_pipe(pipe);
229        }
230        Ok(fpu)
231    }
232    //pub fn fields_proc(&self, data: &mut &str) -> WparseResult<DataRecord> {
233    pub fn parse_groups(&self, e_id: u64, data: &mut &str) -> ModalResult<DataRecord> {
234        let mut result = Vec::with_capacity(100);
235
236        let sep = WplSep::default();
237        for group_unit in self.group_units.iter() {
238            match group_unit.proc(e_id, &sep, data, &mut result) {
239                Ok(_) => {}
240                Err(e) => {
241                    return Err(e);
242                }
243            }
244        }
245        let storage_items: Vec<_> = result
246            .into_iter()
247            .map(wp_model_core::model::FieldStorage::from_owned)
248            .collect();
249        Ok(DataRecord::from(storage_items))
250    }
251}
252
253pub struct StopWatch {
254    continuous: bool,
255    run_cnt: Option<usize>,
256}
257
258impl StopWatch {
259    pub fn tag_used(&mut self) {
260        if self.continuous
261            && let Some(cnt) = &mut self.run_cnt
262        {
263            *cnt -= 1;
264        }
265    }
266    pub fn is_stop(&self) -> bool {
267        if !self.continuous {
268            true
269        } else {
270            self.run_cnt == Some(0)
271        }
272    }
273    pub fn allow_try(&self) -> bool {
274        self.continuous && self.run_cnt.is_none()
275    }
276    pub fn new(continuous: bool, run_cnt: Option<usize>) -> Self {
277        Self {
278            continuous,
279            run_cnt,
280        }
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use crate::ast::fld_fmt::for_test::{fdc2, fdc2_1, fdc3, fdc4_1};
287    use crate::ast::{WplField, WplFieldFmt};
288    use crate::compat::New1;
289    use crate::eval::builtins::raw_to_utf8_string;
290    use crate::eval::runtime::vm_unit::WplEvaluator;
291    use crate::eval::value::parse_def::Hold;
292    use crate::types::AnyResult;
293    use crate::{WparseResult, WplExpress, register_wpl_pipe};
294    use orion_error::TestAssert;
295    use smol_str::SmolStr;
296    use wp_model_core::raw::RawData;
297    use wp_parse_api::PipeProcessor;
298
299    #[test]
300    fn log_test_ty() -> AnyResult<()> {
301        let mut data = r#"<158> May 15 14:19:16 skyeye SyslogClient[1]: 2023-05-15 14:19:16|10.180.8.8|alarm| {"_origin": 1}"#;
302
303        let conf = WplExpress::new(vec![fdc3("auto", " ", true)?]);
304        let ppl = WplEvaluator::from(&conf, None)?;
305
306        let result = ppl.parse_groups(0, &mut data).assert();
307        result.items.iter().for_each(|f| println!("{}", f));
308        Ok(())
309    }
310
311    #[test]
312    fn log_test_ips() -> AnyResult<()> {
313        let conf = WplExpress::new(vec![fdc3("auto", " ", true)?]);
314        let ppl = WplEvaluator::from(&conf, None)?;
315        let mut data = r#"id=tos time="2023-05-15 09:11:53" fw=OS  pri=5 type=mgmt user=superman src=10.111.233.51 op="Modify pwd of manager" result=0 recorder=manager_so msg="null""#;
316        let result = ppl.parse_groups(0, &mut data).assert();
317        result.items.iter().for_each(|f| println!("{}", f));
318        let mut data = r#"id=tos time="2023-05-15 09:11:53" fw=OS  pri=5 type=mgmt user=superman src=10.111.233.51 op="system admininfo modify name zhaolei new_password QXF5dW53ZWleMDIwNw== privilege config login_type local comment 安全管理员 add" result=0 recorder=config msg="nuid=tos time="2023-05-15 09:11:53" fw=OS  pri=5 type=mgmt user=superman src=10.111.233.51 op="webtr webadmin show" result=-1 recorder=config msg="error -8010 : 无效输入,分析" "#;
319        let result = ppl.parse_groups(0, &mut data).assert();
320        result.items.iter().for_each(|f| println!("{}", f));
321        Ok(())
322    }
323
324    //59.x.x.x - - [06/Aug/2019:12:12:19 +0800] "GET /nginx-logo.png HTTP/1.1" 200 368 "http://119.x.x.x/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" "-"
325    #[test]
326    fn log_test_nginx() -> AnyResult<()> {
327        let conf = WplExpress::new(vec![fdc3("auto", " ", true)?]);
328        let ppl = WplEvaluator::from(&conf, None)?;
329        let mut data = r#"192.168.1.2 - - [06/Aug/2019:12:12:19 +0800] "GET /nginx-logo.png HTTP/1.1" 200 368 "http://119.122.1.4/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" "-""#;
330
331        let result = ppl.parse_groups(0, &mut data).assert();
332        assert_eq!(data, "");
333        result.items.iter().for_each(|f| println!("{}", f));
334        Ok(())
335    }
336
337    #[test]
338    fn test_huawei_default() -> AnyResult<()> {
339        let mut data = r#"<190>May 15 2023 07:09:12 KM-KJY-DC-USG12004-B02 %%01POLICY/6/POLICYPERMIT(l):CID=0x814f041e;vsys=CSG_Security, protocol=6, source-ip=10.111.117.49, source-port=34616, destination-ip=10.111.48.230, destination-port=50051, time=2023/5/15 15:09:12, source-zone=untrust, destination-zone=trust, application-name=, line-name=HO202212080377705-1.%"#;
340
341        let conf = WplExpress::new(vec![fdc3("auto", " ", true)?]);
342        let ppl = WplEvaluator::from(&conf, None)?;
343        let result = ppl.parse_groups(0, &mut data).assert();
344
345        assert_eq!(data, "");
346        result.items.iter().for_each(|f| println!("{}", f));
347        Ok(())
348    }
349
350    #[test]
351    fn test_huawei_detail() -> AnyResult<()> {
352        //*auto chars: auto; *auto,
353        let mut data = r#"<190>May 15 2023 07:09:12 KM-KJY-DC-USG12004-B02 %%01POLICY/6/POLICYPERMIT(l):CID=0x814f041e;vsys=CSG_Security, protocol=6"#;
354        let fmt = WplFieldFmt {
355            //separator: PrioSep::default(),
356            scope_beg: Some("<".to_string()),
357            scope_end: Some(">".to_string()),
358            field_cnt: None,
359            sub_fmt: None,
360        };
361        let conf = WplExpress::new(vec![
362            fdc2_1("digit", fmt)?,
363            fdc2("auto", " ")?,
364            fdc2("chars", " ")?,
365            fdc2("chars", ":")?,
366            fdc2("kv", ";")?,
367            fdc2("auto", ",")?,
368            fdc2("auto", ",")?,
369        ]);
370
371        let ppl = WplEvaluator::from(&conf, None)?;
372        let result = ppl.parse_groups(0, &mut data).assert();
373        assert_eq!(data, "");
374        result.items.iter().for_each(|f| println!("{}", f));
375        Ok(())
376    }
377
378    #[test]
379    fn test_huawei_simple() -> AnyResult<()> {
380        //*auto chars: auto; *auto,
381        let mut data = r#"<190>May 15 2023 07:09:12 KM-KJY-DC-USG12004-B02 %%01POLICY/6/POLICYPERMIT(l):CID=0x814f041e;vsys=CSG_Security, protocol=6"#;
382        let conf = WplExpress::new(vec![
383            fdc3("auto", " ", true)?,
384            fdc2("chars", ":")?,
385            fdc3("auto", ";", false)?,
386            fdc3("auto", ",", true)?,
387        ]);
388        let ppl = WplEvaluator::from(&conf, None)?;
389        let result = ppl.parse_groups(0, &mut data).assert();
390        assert_eq!(data, "");
391        result.items.iter().for_each(|f| println!("{:?}", f));
392        Ok(())
393    }
394
395    #[test]
396    fn test_huawei_simple2() -> AnyResult<()> {
397        let mut data = r#"<190>May 15 2023 07:09:12 KM-KJY-DC-USG12004-B02 %%01POLICY/6/POLICYPERMIT(l):CID=0x814f041e;vsys=CSG_Security, protocol=6"#;
398        let conf = WplExpress::new(vec![
399            WplField::try_parse("symbol(<190>)[5]").assert(),
400            fdc3("time", " ", false)?,
401            WplField::try_parse("symbol(KM)[2]").assert(),
402            fdc2("chars", ":")?,
403            fdc3("auto", ";", false)?,
404            fdc3("auto", ",", true)?,
405        ]);
406        let ppl = WplEvaluator::from(&conf, None)?;
407        let result = ppl.parse_groups(0, &mut data).assert();
408        assert_eq!(data, "");
409        result.items.iter().for_each(|f| println!("{:?}", f));
410        Ok(())
411    }
412
413    #[test]
414    fn test_gen() -> AnyResult<()> {
415        let mut data = r#"2345,2021-7-15 7:50:32,9OPP-MU-JME2-YGUW,chars_740,2022-1-18 19:30:30,jki=BkRzBo0f,138.11.13.43,tEu=GRcCwKkR,chars_493,Mrc=EskxskU3,sYp=jfKkn7th,UBa=eKhcfd9h,nXa=ZQSta6Je"#;
416        let conf = WplExpress::new(vec![
417            fdc3("digit", ",", false)?,
418            fdc3("time", ",", false)?,
419            fdc3("sn", ",", false)?,
420            fdc3("chars", ",", false)?,
421            fdc3("time", ",", false)?,
422            fdc3("auto", ",", true)?,
423        ]);
424        let ppl = WplEvaluator::from(&conf, None)?;
425        let result = ppl.parse_groups(0, &mut data).assert();
426        assert_eq!(data, "");
427        result.items.iter().for_each(|f| println!("{}", f));
428        Ok(())
429    }
430
431    #[test]
432    fn test_gen2() -> AnyResult<()> {
433        let mut data = r#"7106,2020-6-10 2:54:9,U5BH-UC-UQVY-MMKU,chars_472,2020-9-22 13:4:6,Emm=LXJDV5DC,22.161.67.67,nsL=LvVRv5uf,chars_1534,DNw=0xCQKTaQ,UFh=dMPbabRG,q29=aMsZTj83,oUi=ywMsKT2G"#;
434        let conf = WplExpress::new(vec![
435            fdc3("digit", ",", false)?,
436            fdc3("time", ",", false)?,
437            fdc3("sn", ",", false)?,
438            fdc3("chars", ",", false)?,
439            fdc3("time", ",", false)?,
440            fdc3("kv", ",", false)?,
441            fdc3("ip", ",", false)?,
442            fdc3("kv", ",", false)?,
443            fdc3("chars", ",", false)?,
444            fdc3("kv", ",", false)?,
445            fdc3("kv", ",", false)?,
446            fdc3("kv", ",", false)?,
447            fdc3("kv", ",", false)?,
448        ]);
449        let ppl = WplEvaluator::from(&conf, None)?;
450        let result = ppl.parse_groups(0, &mut data).assert();
451        assert_eq!(data, "");
452        result.items.iter().for_each(|f| println!("{}", f));
453
454        let mut data = r#"1857,2021-4-10 0:46:8,R2IP-IF-06UT-7KUU,chars_1914,2021-4-15 2:19:43,u6s=TNSAlucV,228.211.38.109,k02=doYanSlf,chars_276,SIw=nu8atSqT,84e=e6qUb2k7,aVs=pk8M8rQU,5An=9upLU8aa"#;
455        let result = ppl.parse_groups(0, &mut data).assert();
456        assert_eq!(data, "");
457        result.items.iter().for_each(|f| println!("{}", f));
458        Ok(())
459    }
460
461    #[test]
462    fn preorder_plg_pipe_unit_executes() -> AnyResult<()> {
463        #[derive(Debug)]
464        struct MockStage;
465
466        impl PipeProcessor for MockStage {
467            fn process(&self, data: RawData) -> WparseResult<RawData> {
468                let mut value = raw_to_utf8_string(&data);
469                value.push_str("-mock");
470                Ok(RawData::from_string(value))
471            }
472
473            fn name(&self) -> &'static str {
474                "mock_stage"
475            }
476        }
477
478        register_wpl_pipe!("plg_pipe/MOCK-STAGE", || Hold::new(MockStage));
479
480        let mut expr = WplExpress::new(vec![fdc3("auto", " ", true)?]);
481        expr.pipe_process = vec![SmolStr::from("plg_pipe/MOCK-STAGE")];
482
483        let evaluator = WplEvaluator::from(&expr, None)?;
484        let results = evaluator.preorder_proc(RawData::from_string("data".to_string()))?;
485        assert_eq!(results.len(), 1);
486        assert_eq!(results[0].result, "data-mock");
487        assert_eq!(results[0].name, "mock_stage");
488        Ok(())
489    }
490
491    #[test]
492    fn test_ignore() -> AnyResult<()> {
493        let mut data = r#"2345,2021-7-15 7:50:32,9OPP-MU-JME2-YGUW,chars_740"#;
494        let conf = WplExpress::new(vec![
495            fdc3("_", ",", false)?,
496            fdc3("_", ",", false)?,
497            fdc3("_", ",", false)?,
498            fdc3("_", ",", false)?,
499        ]);
500        let ppl = WplEvaluator::from(&conf, None)?;
501        let result = ppl.parse_groups(0, &mut data).assert();
502        assert_eq!(data, "");
503        result.items.iter().for_each(|f| println!("{}", f));
504        Ok(())
505    }
506
507    #[test]
508    fn test_ignore_cnt() -> AnyResult<()> {
509        let mut data = r#"2345,2021-7-15 7:50:32,9OPP-MU-JME2-YGUW,chars_740"#;
510        let conf = WplExpress::new(vec![fdc4_1("_", ",", true, 4)?]);
511        let ppl = WplEvaluator::from(&conf, None)?;
512        let result = ppl.parse_groups(0, &mut data).assert();
513        assert_eq!(data, "");
514        assert_eq!(result.items.len(), 4);
515        result.items.iter().for_each(|f| println!("{}", f));
516
517        let mut data = r#"2345,2021-7-15 7:50:32,9OPP-MU-JME2-YGUW,chars_740"#;
518        let conf = WplExpress::new(vec![fdc4_1("_", ",", true, 3)?]);
519        let ppl = WplEvaluator::from(&conf, None)?;
520        let result = ppl.parse_groups(0, &mut data).assert();
521        assert_eq!(data, "chars_740");
522        assert_eq!(result.items.len(), 3);
523        Ok(())
524    }
525
526    #[test]
527    fn test_pipe_unit_direct_lookup() -> AnyResult<()> {
528        use crate::eval::builtins::raw_to_utf8_string;
529        use crate::{create_preorder_pipe_unit, list_preorder_pipe_units};
530
531        // Define MockStage for this test
532        #[derive(Debug)]
533        struct TestMockStage;
534
535        impl PipeProcessor for TestMockStage {
536            fn process(&self, data: RawData) -> WparseResult<RawData> {
537                let mut value = raw_to_utf8_string(&data);
538                value.push_str("-mock");
539                Ok(RawData::from_string(value))
540            }
541
542            fn name(&self) -> &'static str {
543                "test_mock_stage"
544            }
545        }
546
547        // Test direct lookup after simplification
548        // Register test processors with different naming schemes
549        register_wpl_pipe!("direct-test", || Hold::new(TestMockStage));
550        register_wpl_pipe!("plg_pipe/mock-prefix", || Hold::new(TestMockStage));
551
552        // Test that direct naming works
553        let processor = create_preorder_pipe_unit("direct-test");
554        assert!(processor.is_some(), "Should find direct-test processor");
555
556        // Test that prefixed registration also works (with full name)
557        let processor = create_preorder_pipe_unit("plg_pipe/mock-prefix");
558        assert!(
559            processor.is_some(),
560            "Should find plg_pipe/with-prefix processor"
561        );
562
563        // Test actual processing with different naming
564        let test_data = RawData::from_string("hello".to_string());
565
566        if let Some(processor) = create_preorder_pipe_unit("direct-test") {
567            let result = processor.process(test_data.clone())?;
568            assert_eq!(raw_to_utf8_string(&result), "hello-mock");
569        }
570
571        if let Some(processor) = create_preorder_pipe_unit("plg_pipe/mock-prefix") {
572            let result = processor.process(test_data)?;
573            assert_eq!(raw_to_utf8_string(&result), "hello-mock");
574        }
575
576        // List all processors to see what's registered
577        let processors = list_preorder_pipe_units();
578        println!("All registered processors: {:?}", processors);
579
580        // Verify both naming approaches work (names are converted to uppercase)
581        assert!(processors.contains(&"DIRECT-TEST".into()));
582        assert!(processors.contains(&"PLG_PIPE/MOCK-PREFIX".into()));
583
584        Ok(())
585    }
586
587    #[test]
588    fn test_simplified_assemble_ins_logic() -> AnyResult<()> {
589        use crate::eval::builtins::raw_to_utf8_string;
590        use crate::{create_preorder_pipe_unit, list_preorder_pipe_units};
591
592        // Define test processor
593        #[derive(Debug)]
594        struct SimplifiedTestStage;
595
596        impl PipeProcessor for SimplifiedTestStage {
597            fn process(&self, data: RawData) -> WparseResult<RawData> {
598                let mut value = raw_to_utf8_string(&data);
599                value.push_str("-simplified");
600                Ok(RawData::from_string(value))
601            }
602
603            fn name(&self) -> &'static str {
604                "simplified_test"
605            }
606        }
607
608        // Register processors with both naming styles
609        register_wpl_pipe!("simple-test", || Hold::new(SimplifiedTestStage));
610        register_wpl_pipe!("plg_pipe/simple-prefix", || Hold::new(SimplifiedTestStage));
611
612        // Test that both can be found directly
613        let processor1 = create_preorder_pipe_unit("simple-test");
614        assert!(processor1.is_some(), "Should find simple-test");
615
616        let processor2 = create_preorder_pipe_unit("plg_pipe/simple-prefix");
617        assert!(processor2.is_some(), "Should find plg_pipe/with-prefix");
618
619        // Test that processors registered with plg_pipe/ prefix can be found without it
620        // This would fail because registration is case-sensitive and stores full name
621        let processor3 = create_preorder_pipe_unit("simple-prefix");
622        assert!(
623            processor3.is_none(),
624            "Should NOT find with-prefix (was registered as plg_pipe/with-prefix)"
625        );
626
627        // Show all registered processors
628        let processors = list_preorder_pipe_units();
629        println!("All processors: {:?}", processors);
630
631        Ok(())
632    }
633}