1+ use serde:: { Deserialize , Serialize } ;
12use std:: collections:: HashMap ;
23use std:: fmt:: { Display , Formatter } ;
34use std:: str:: FromStr ;
4- use serde:: { Serialize , Deserialize } ;
55
66#[ derive( Debug , PartialEq , Eq , Hash , Clone , Serialize , Deserialize ) ]
77pub struct ValidationError {
@@ -20,6 +20,18 @@ impl std::fmt::Display for ValidationError {
2020 }
2121}
2222
23+ /// Result of executing a single test
24+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
25+ #[ serde( rename_all = "camelCase" ) ]
26+ pub struct TestResult {
27+ /// The test ID that was executed
28+ pub test_id : String ,
29+ /// Whether the test passed
30+ pub success : bool ,
31+ /// The errors if the test failed (empty if successful)
32+ pub errors : Vec < ValidationError > ,
33+ }
34+
2335/// Result of a CSAF validation
2436#[ derive( Debug , Clone , Serialize , Deserialize ) ]
2537#[ serde( rename_all = "camelCase" ) ]
@@ -32,6 +44,8 @@ pub struct ValidationResult {
3244 pub errors : Vec < ValidationError > ,
3345 /// The validation preset that was used
3446 pub preset : ValidationPreset ,
47+ /// Individual test results with execution details
48+ pub test_results : Vec < TestResult > ,
3549}
3650
3751#[ derive( Debug , PartialEq , Eq , Hash , Clone , Serialize , Deserialize ) ]
@@ -66,11 +80,19 @@ impl Display for ValidationPreset {
6680}
6781
6882pub trait Validate {
69- /// Validates this object according to a validation preset
70- fn validate_preset ( & ' static self , preset : ValidationPreset ) ;
71-
72- /// Validates this object according to a specific test ID.
73- fn validate_by_test ( & self , version : & str ) ;
83+ /// Validates this object according to
84+ fn validate_by_test < VersionedDocument > ( & self , test_id : & str ) -> TestResult ;
85+
86+ /// Validates this object according to specific test IDs and returns detailed results
87+ fn validate_tests (
88+ & self ,
89+ version : & str ,
90+ preset : ValidationPreset ,
91+ test_ids : & [ & str ] ,
92+ ) -> ValidationResult ;
93+
94+ /// Validates this object according to a validation preset and returns detailed results
95+ fn validate_preset ( & self , version : & str , preset : ValidationPreset ) -> ValidationResult ;
7496}
7597
7698pub type Test < VersionedDocument > = fn ( & VersionedDocument ) -> Result < ( ) , ValidationError > ;
@@ -90,104 +112,85 @@ pub trait Validatable<VersionedDocument> {
90112 fn doc ( & self ) -> & VersionedDocument ;
91113}
92114
93- /// Executes all tests of the specified [preset] against the [target]
94- /// (which is of type [VersionedDocument], e.g. a CSAF 2.0 document).
95- pub fn validate_by_preset < VersionedDocument > (
96- target : & impl Validatable < VersionedDocument > ,
97- preset : ValidationPreset ,
98- ) {
99- println ! ( "Validating document with {:?} preset... \n " , preset) ;
100-
101- // Loop through tests
102- if let Some ( tests) = target. presets ( ) . get ( & preset) {
103- for test_id in tests {
104- println ! ( "Executing Test {}... " , test_id) ;
105- validate_by_test ( target, test_id) ;
106-
107- println ! ( )
108- }
109- } else {
110- println ! ( "No tests found for preset" )
111- }
112- }
113-
115+ /// Execute a single test and return the test result.
116+ ///
117+ /// This function will check, whether the test_id exists in the Validatable's
118+ /// tests. If it does, it will execute the test function and return the result.
119+ /// If not, it will return a TestResult indicating that the test was not found.
114120pub fn validate_by_test < VersionedDocument > (
115121 target : & impl Validatable < VersionedDocument > ,
116122 test_id : & str ,
117- ) {
118- if let Some ( test_fn) = target. tests ( ) . get ( test_id) {
119- let _ = match test_fn ( target. doc ( ) ) {
120- Ok ( ( ) ) => println ! ( "> Test Success" ) ,
121- Err ( e) => println ! ( "> Error: {}" , e) ,
122- } ;
123+ ) -> TestResult {
124+ // Fetch tests from the validatable
125+ let tests = target. tests ( ) ;
126+
127+ // Try to find and execute the test specified by the test_id
128+ if let Some ( test_fn) = tests. get ( test_id) {
129+ match test_fn ( target. doc ( ) ) {
130+ Ok ( ( ) ) => TestResult {
131+ test_id : test_id. to_string ( ) ,
132+ success : true ,
133+ errors : vec ! [ ] ,
134+ } ,
135+ Err ( error) => TestResult {
136+ test_id : test_id. to_string ( ) ,
137+ success : false ,
138+ errors : vec ! [ error] ,
139+ } ,
140+ }
123141 } else {
124- println ! ( "Test with ID {} is missing implementation" , test_id) ;
142+ let error = ValidationError {
143+ message : format ! ( "Test '{}' not found" , test_id) ,
144+ instance_path : "" . to_string ( ) ,
145+ } ;
146+ TestResult {
147+ test_id : test_id. to_string ( ) ,
148+ success : false ,
149+ errors : vec ! [ error] ,
150+ }
125151 }
126152}
127153
128- /// Collect validation errors for a given preset without printing
129- pub fn collect_validation_errors < VersionedDocument > (
154+ /// Validate document with specific tests and return detailed results.
155+ pub fn validate_by_tests < VersionedDocument > (
130156 target : & impl Validatable < VersionedDocument > ,
131- preset : & ValidationPreset ,
132- ) -> Vec < ValidationError > {
157+ version : & str ,
158+ preset : ValidationPreset ,
159+ test_ids : & [ & str ] ,
160+ ) -> ValidationResult {
133161 let mut errors = Vec :: new ( ) ;
162+ let mut test_results = Vec :: new ( ) ;
134163
135- if let Some ( test_ids) = target. presets ( ) . get ( preset) {
136- let tests = target. tests ( ) ;
164+ // Loop through tests and gather all results and errors
165+ for test_id in test_ids {
166+ let test_result = validate_by_test ( target, test_id) ;
137167
138- for test_id in test_ids {
139- if let Some ( test_fn) = tests. get ( test_id) {
140- if let Err ( error) = test_fn ( target. doc ( ) ) {
141- errors. push ( error) ;
142- }
143- }
144- }
168+ errors. extend ( test_result. errors . iter ( ) . cloned ( ) ) ;
169+ test_results. push ( test_result) ;
145170 }
146171
147- errors
148- }
149-
150- /// Create a validation result from a validatable document
151- pub fn create_validation_result < VersionedDocument > (
152- target : & impl Validatable < VersionedDocument > ,
153- version : & str ,
154- preset : ValidationPreset ,
155- ) -> ValidationResult {
156- let errors = collect_validation_errors ( target, & preset) ;
157-
158172 ValidationResult {
159173 success : errors. is_empty ( ) ,
160174 version : version. to_string ( ) ,
161175 errors,
162176 preset,
177+ test_results,
163178 }
164179}
165180
166- /// Print a validation result to stdout (for CLI use)
167- pub fn print_validation_result ( result : & ValidationResult ) {
168- println ! ( "Validating document with {:?} preset... \n " , result. preset) ;
169-
170- if result. success {
171- println ! ( "✅ Validation passed! No errors found." ) ;
172- println ! ( " CSAF Version: {}" , result. version) ;
173- println ! ( ) ;
174- } else {
175- println ! ( "❌ Validation failed with {} error(s):" , result. errors. len( ) ) ;
176- println ! ( " CSAF Version: {}" , result. version) ;
177- println ! ( ) ;
178- for error in & result. errors {
179- println ! ( " • {} (at {})" , error. message, error. instance_path) ;
180- }
181- println ! ( ) ;
182- }
183- }
184-
185- /// Validate and print results - convenience function for CLI use
186- pub fn validate_and_print < VersionedDocument > (
181+ /// Validate document with a preset and return detailed results.
182+ pub fn validate_by_preset < VersionedDocument > (
187183 target : & impl Validatable < VersionedDocument > ,
188184 version : & str ,
189185 preset : ValidationPreset ,
190- ) {
191- let result = create_validation_result ( target, version, preset) ;
192- print_validation_result ( & result) ;
186+ ) -> ValidationResult {
187+ // Retrieve the test IDs for the given preset
188+ let test_ids: Vec < & str > = target
189+ . presets ( )
190+ . get ( & preset)
191+ . map ( |ids| ids. iter ( ) . copied ( ) . collect ( ) )
192+ . unwrap_or_default ( ) ;
193+
194+ // Forward them to validate_by_tests
195+ validate_by_tests ( target, version, preset, & test_ids)
193196}
0 commit comments