1#![deny(unused_must_use)]
6
7pub mod commands;
8mod rules;
9pub mod utils;
10
11pub use crate::commands::helper::{validate_and_return_json as run_checks, ValidateInput};
12use crate::commands::parse_tree::ParseTree;
13use crate::commands::rulegen::Rulegen;
14use crate::commands::test::Test;
15use crate::commands::validate::{OutputFormatType, ShowSummaryType, Validate};
16use crate::commands::Executable;
17pub use crate::rules::errors::Error;
18
19#[cfg(target_arch = "wasm32")]
20use crate::utils::reader::{ReadBuffer, Reader};
21#[cfg(target_arch = "wasm32")]
22use crate::utils::writer::WriteBuffer::Vec as WBVec;
23#[cfg(target_arch = "wasm32")]
24use std::io::Cursor;
25
26use wasm_bindgen::prelude::*;
27
28pub trait CommandBuilder<T: Executable> {
29 fn try_build(self) -> crate::rules::Result<T>;
30}
31
32#[derive(Default, Debug)]
33pub struct ParseTreeBuilder {
36 rules: Option<String>,
37 output: Option<String>,
38 print_json: bool,
39 print_yaml: bool,
40}
41
42impl CommandBuilder<ParseTree> for ParseTreeBuilder {
43 fn try_build(self) -> crate::rules::Result<ParseTree> {
46 let ParseTreeBuilder {
47 rules,
48 output,
49 print_json,
50 print_yaml,
51 } = self;
52
53 Ok(ParseTree {
54 rules,
55 output,
56 print_json,
57 print_yaml,
58 })
59 }
60}
61
62impl ParseTreeBuilder {
63 pub fn rules(mut self, rules: Option<String>) -> Self {
65 self.rules = rules;
66
67 self
68 }
69
70 pub fn output(mut self, output: Option<String>) -> Self {
72 self.output = output;
73
74 self
75 }
76
77 pub fn print_json(mut self, arg: bool) -> Self {
79 self.print_json = arg;
80
81 self
82 }
83
84 pub fn print_yaml(mut self, arg: bool) -> Self {
86 self.print_yaml = arg;
87
88 self
89 }
90}
91
92#[derive(Debug)]
93#[wasm_bindgen]
94pub struct ValidateBuilder {
97 rules: Vec<String>,
98 data: Vec<String>,
99 input_params: Vec<String>,
100 template_type: Option<String>,
101 output_format: OutputFormatType,
102 show_summary: Vec<ShowSummaryType>,
103 alphabetical: bool,
104 last_modified: bool,
105 verbose: bool,
106 print_json: bool,
107 payload: bool,
108 structured: bool,
109}
110
111impl Default for ValidateBuilder {
112 fn default() -> Self {
113 Self {
114 rules: Default::default(),
115 data: Default::default(),
116 input_params: Default::default(),
117 template_type: Default::default(),
118 output_format: Default::default(),
119 show_summary: vec![Default::default()],
120 alphabetical: Default::default(),
121 last_modified: false,
122 verbose: false,
123 print_json: false,
124 payload: false,
125 structured: false,
126 }
127 }
128}
129
130impl CommandBuilder<Validate> for ValidateBuilder {
131 #[allow(deprecated)]
138 fn try_build(self) -> crate::rules::Result<Validate> {
139 if self.structured {
140 if self.output_format == OutputFormatType::SingleLineSummary {
141 return Err(Error::IllegalArguments(String::from(
142 "single-line-summary is not able to be used when the `structured` flag is present",
143 )));
144 }
145
146 if self.print_json {
147 return Err(Error::IllegalArguments(String::from("unable to construct validate command when both structured and print_json are set to true")));
148 }
149
150 if self.verbose {
151 return Err(Error::IllegalArguments(String::from("unable to construct validate command when both structured and verbose are set to true")));
152 }
153
154 if self.show_summary.iter().any(|st| {
155 matches!(
156 st,
157 ShowSummaryType::Pass
158 | ShowSummaryType::Fail
159 | ShowSummaryType::Skip
160 | ShowSummaryType::All
161 )
162 }) {
163 return Err(Error::IllegalArguments(String::from(
164 "Cannot provide a summary-type other than `none` when the `structured` flag is present",
165 )));
166 }
167 } else if matches!(
168 self.output_format,
169 OutputFormatType::Junit | OutputFormatType::Sarif
170 ) {
171 return Err(Error::IllegalArguments(format!(
172 "the structured flag must be set when output is set to {:?}",
173 self.output_format
174 )));
175 }
176
177 if self.payload && (!self.rules.is_empty() || !self.data.is_empty()) {
178 return Err(Error::IllegalArguments(String::from("cannot construct a validate command payload conflicts with both data and rules arguments")));
179 }
180
181 if !self.payload && self.rules.is_empty() {
182 return Err(Error::IllegalArguments(String::from("cannot construct a validate command: either payload must be set to true, or rules must not be empty")));
183 }
184
185 if self.last_modified && self.alphabetical {
186 return Err(Error::IllegalArguments(String::from(
187 "cannot have both last modified, and alphabetical arguments set to true",
188 )));
189 }
190
191 let ValidateBuilder {
192 rules,
193 data,
194 input_params,
195 template_type,
196 output_format,
197 show_summary,
198 alphabetical,
199 last_modified,
200 verbose,
201 print_json,
202 payload,
203 structured,
204 } = self;
205
206 Ok(Validate {
207 rules,
208 data,
209 input_params,
210 template_type,
211 output_format,
212 show_summary,
213 alphabetical,
214 last_modified,
215 verbose,
216 print_json,
217 payload,
218 structured,
219 })
220 }
221}
222
223#[wasm_bindgen]
224impl ValidateBuilder {
225 pub fn rules(mut self, rules: Vec<String>) -> Self {
228 self.rules = rules;
229
230 self
231 }
232
233 pub fn data(mut self, data: Vec<String>) -> Self {
236 self.data = data;
237
238 self
239 }
240
241 #[wasm_bindgen(js_name = showSummary)]
245 pub fn show_summary(mut self, args: Vec<ShowSummaryType>) -> Self {
246 self.show_summary = args;
247
248 self
249 }
250
251 pub fn input_params(mut self, input_params: Vec<String>) -> Self {
253 self.input_params = input_params;
254
255 self
256 }
257
258 #[wasm_bindgen(js_name = outputFormat)]
262 pub fn output_format(mut self, output: OutputFormatType) -> Self {
263 self.output_format = output;
264
265 self
266 }
267
268 pub fn payload(mut self, arg: bool) -> Self {
272 self.payload = arg;
273
274 self
275 }
276
277 pub fn alphabetical(mut self, arg: bool) -> Self {
279 self.alphabetical = arg;
280
281 self
282 }
283
284 pub fn last_modified(mut self, arg: bool) -> Self {
286 self.last_modified = arg;
287
288 self
289 }
290
291 pub fn verbose(mut self, arg: bool) -> Self {
294 self.verbose = arg;
295
296 self
297 }
298
299 pub fn print_json(mut self, arg: bool) -> Self {
303 self.print_json = arg;
304
305 self
306 }
307
308 pub fn structured(mut self, arg: bool) -> Self {
313 self.structured = arg;
314
315 self
316 }
317
318 #[cfg(target_arch = "wasm32")]
319 #[wasm_bindgen(constructor)]
320 pub fn new() -> ValidateBuilder {
321 ValidateBuilder {
322 ..Default::default()
323 }
324 }
325
326 #[cfg(target_arch = "wasm32")]
327 #[wasm_bindgen(js_name = tryBuildAndExecute)]
328 pub fn try_build_js_and_execute(self, payload: &str) -> Result<JsValue, JsValue> {
329 let mut reader = Reader::new(ReadBuffer::Cursor(Cursor::new(Vec::from(
330 payload.as_bytes(),
331 ))));
332
333 let mut writer = utils::writer::Writer::new(WBVec(vec![]))
334 .map_err(|e| JsValue::from_str(&e.to_string()))?;
335
336 let cmd = self
337 .try_build()
338 .map_err(|e| JsValue::from_str(&e.to_string()))?;
339
340 cmd.execute(&mut writer, &mut reader)
341 .map_err(|e| JsValue::from_str(&e.to_string()))?;
342
343 Ok(JsValue::from(
344 writer.into_string().unwrap_or("".to_string()),
345 ))
346 }
347}
348#[derive(Default, Debug)]
351pub struct TestBuilder {
352 rules: Option<String>,
353 test_data: Option<String>,
354 directory: Option<String>,
355 alphabetical: bool,
356 last_modified: bool,
357 verbose: bool,
358 output_format: OutputFormatType,
359}
360
361impl CommandBuilder<Test> for TestBuilder {
362 fn try_build(self) -> crate::rules::Result<Test> {
369 if self.last_modified && self.alphabetical {
370 return Err(Error::IllegalArguments(String::from("unable to construct a test command: cannot have both last modified, and alphabetical arguments set to true")));
371 }
372
373 if self.directory.is_some() && self.rules.is_some() {
374 return Err(Error::IllegalArguments(String::from("unable to construct a test command: cannot pass both a directory argument, and a rules argument")));
375 }
376
377 if !matches!(self.output_format, OutputFormatType::SingleLineSummary) && self.verbose {
378 return Err(Error::IllegalArguments(String::from("Cannot provide an output_type of JSON, YAML, or JUnit while the verbose flag is set")));
379 }
380
381 let TestBuilder {
382 rules,
383 test_data,
384 directory,
385 alphabetical,
386 last_modified,
387 verbose,
388 output_format,
389 } = self;
390
391 Ok(Test {
392 rules,
393 test_data,
394 directory,
395 alphabetical,
396 last_modified,
397 verbose,
398 output_format,
399 })
400 }
401}
402
403impl TestBuilder {
404 pub fn rules(mut self, rules: Option<String>) -> Self {
407 self.rules = rules;
408
409 self
410 }
411
412 pub fn test_data(mut self, test_data: Option<String>) -> Self {
415 self.test_data = test_data;
416
417 self
418 }
419
420 pub fn directory(mut self, directory: Option<String>) -> Self {
424 self.directory = directory;
425
426 self
427 }
428
429 pub fn alphabetical(mut self, arg: bool) -> Self {
432 self.alphabetical = arg;
433
434 self
435 }
436
437 pub fn last_modified(mut self, arg: bool) -> Self {
440 self.last_modified = arg;
441
442 self
443 }
444
445 pub fn verbose(mut self, arg: bool) -> Self {
448 self.verbose = arg;
449
450 self
451 }
452
453 pub fn output_format(mut self, output: OutputFormatType) -> Self {
458 self.output_format = output;
459
460 self
461 }
462}
463
464#[derive(Debug, Default)]
465pub struct RulegenBuilder {
468 output: Option<String>,
469 template: String,
470}
471
472impl CommandBuilder<Rulegen> for RulegenBuilder {
473 fn try_build(self) -> crate::rules::Result<Rulegen> {
475 let RulegenBuilder { output, template } = self;
476 Ok(Rulegen { output, template })
477 }
478}
479
480impl RulegenBuilder {
481 pub fn output(mut self, output: Option<String>) -> Self {
484 self.output = output;
485
486 self
487 }
488
489 pub fn template(mut self, template: String) -> Self {
491 self.template = template;
492
493 self
494 }
495}
496
497#[cfg(test)]
498mod cfn_guard_lib_tests {
499 use crate::{
500 commands::validate::ShowSummaryType, CommandBuilder, TestBuilder, ValidateBuilder,
501 };
502
503 #[test]
504 fn validate_with_errors() {
505 let cmd = ValidateBuilder::default()
507 .data(vec![String::from("resources/validate/data-dir")])
508 .rules(vec![String::from("resources/validate/rules-dir")])
509 .structured(true)
510 .output_format(crate::commands::validate::OutputFormatType::JSON)
511 .try_build();
512 assert!(cmd.is_err());
513
514 let cmd = ValidateBuilder::default()
516 .data(vec![String::from("resources/validate/data-dir")])
517 .rules(vec![String::from("resources/validate/rules-dir")])
518 .structured(true)
519 .show_summary(vec![ShowSummaryType::None])
520 .try_build();
521 assert!(cmd.is_err());
522
523 let cmd = ValidateBuilder::default()
525 .data(vec![String::from("resources/validate/data-dir")])
526 .rules(vec![String::from("resources/validate/rules-dir")])
527 .structured(true)
528 .output_format(crate::commands::validate::OutputFormatType::JSON)
529 .verbose(true)
530 .show_summary(vec![ShowSummaryType::None])
531 .try_build();
532 assert!(cmd.is_err());
533
534 let cmd = ValidateBuilder::default()
536 .data(vec![String::from("resources/validate/data-dir")])
537 .rules(vec![String::from("resources/validate/rules-dir")])
538 .structured(true)
539 .output_format(crate::commands::validate::OutputFormatType::JSON)
540 .print_json(true)
541 .show_summary(vec![ShowSummaryType::None])
542 .try_build();
543 assert!(cmd.is_err());
544
545 vec![
547 crate::commands::validate::OutputFormatType::Junit,
548 crate::commands::validate::OutputFormatType::Sarif,
549 ]
550 .into_iter()
551 .for_each(|output_format| {
552 let cmd = ValidateBuilder::default()
553 .data(vec![String::from("resources/validate/data-dir")])
554 .rules(vec![String::from("resources/validate/rules-dir")])
555 .output_format(output_format)
556 .show_summary(vec![ShowSummaryType::None])
557 .try_build();
558
559 assert!(cmd.is_err());
560 });
561
562 let cmd = ValidateBuilder::default()
564 .output_format(crate::commands::validate::OutputFormatType::Junit)
565 .structured(true)
566 .show_summary(vec![ShowSummaryType::None])
567 .try_build();
568
569 assert!(cmd.is_err());
570
571 let cmd = ValidateBuilder::default()
573 .rules(vec![String::from("resources/validate/rules-dir")])
574 .payload(true)
575 .output_format(crate::commands::validate::OutputFormatType::Junit)
576 .structured(true)
577 .show_summary(vec![ShowSummaryType::None])
578 .try_build();
579
580 assert!(cmd.is_err());
581
582 let cmd = ValidateBuilder::default()
584 .data(vec![String::from("resources/validate/data-dir")])
585 .payload(true)
586 .output_format(crate::commands::validate::OutputFormatType::Junit)
587 .structured(true)
588 .show_summary(vec![ShowSummaryType::None])
589 .try_build();
590
591 assert!(cmd.is_err());
592
593 let cmd = ValidateBuilder::default()
595 .payload(true)
596 .alphabetical(true)
597 .last_modified(true)
598 .output_format(crate::commands::validate::OutputFormatType::Junit)
599 .structured(true)
600 .show_summary(vec![ShowSummaryType::None])
601 .try_build();
602
603 assert!(cmd.is_err());
604 }
605
606 #[test]
607 fn validate_happy_path() {
608 let data = vec![String::from("resources/validate/data-dir")];
609 let rules = vec![String::from("resources/validate/rules-dir")];
610 let cmd = ValidateBuilder::default()
611 .verbose(true)
612 .output_format(crate::commands::validate::OutputFormatType::JSON)
613 .data(data.clone())
614 .rules(rules.clone())
615 .try_build();
616
617 assert!(cmd.is_ok());
618
619 let cmd = ValidateBuilder::default()
620 .verbose(true)
621 .show_summary(vec![
622 ShowSummaryType::Pass,
623 ShowSummaryType::Fail,
624 ShowSummaryType::Skip,
625 ])
626 .output_format(crate::commands::validate::OutputFormatType::JSON)
627 .data(data.clone())
628 .rules(rules.clone())
629 .try_build();
630
631 assert!(cmd.is_ok());
632
633 let cmd = ValidateBuilder::default()
634 .verbose(true)
635 .output_format(crate::commands::validate::OutputFormatType::JSON)
636 .data(data.clone())
637 .rules(rules.clone())
638 .try_build();
639
640 assert!(cmd.is_ok());
641
642 let cmd = ValidateBuilder::default()
643 .data(data.clone())
644 .rules(rules.clone())
645 .structured(true)
646 .output_format(crate::commands::validate::OutputFormatType::JSON)
647 .show_summary(vec![ShowSummaryType::None])
648 .try_build();
649
650 assert!(cmd.is_ok());
651
652 let cmd = ValidateBuilder::default()
653 .data(data.clone())
654 .rules(rules.clone())
655 .output_format(crate::commands::validate::OutputFormatType::Junit)
656 .structured(true)
657 .show_summary(vec![ShowSummaryType::None])
658 .try_build();
659
660 assert!(cmd.is_ok());
661 }
662
663 #[test]
664 fn build_test_command_happy_path() {
665 let data = String::from("resources/validate/data-dir");
666 let rules = String::from("resources/validate/rules-dir");
667 let cmd = TestBuilder::default()
668 .test_data(Option::from(data.clone()))
669 .rules(Option::from(rules.clone()))
670 .try_build();
671
672 assert!(cmd.is_ok());
673
674 let cmd = TestBuilder::default()
675 .directory(Option::from(data.clone()))
676 .try_build();
677 assert!(cmd.is_ok());
678
679 let cmd = TestBuilder::default()
680 .directory(Option::from(data.clone()))
681 .alphabetical(true)
682 .try_build();
683
684 assert!(cmd.is_ok())
685 }
686
687 #[test]
688 fn build_test_command_with_errors() {
689 let data = String::from("resources/validate/data-dir");
690 let rules = String::from("resources/validate/rules-dir");
691
692 let cmd = TestBuilder::default()
694 .rules(Option::from(rules.clone()))
695 .directory(Option::from(data.clone()))
696 .try_build();
697
698 assert!(cmd.is_err());
699
700 let cmd = TestBuilder::default()
702 .directory(Option::from(data.clone()))
703 .last_modified(true)
704 .alphabetical(true)
705 .try_build();
706
707 assert!(cmd.is_err());
708 }
709}