Skip to content

Commit ea0d4e4

Browse files
author
Cole (Mike) Winberry
committed
fix(validation)!: #266 validation.ValidationCommand no longer returns an error when the validation has been successfully run
fix(validator)!: validator.Validate() no longer returns the parsed basic output as an error and instead returns the jsonschema.ValidationError fix(cmd): validate cmd updated to handle ValidationCommand & validator.Validate changes
1 parent 1921e9f commit ea0d4e4

File tree

6 files changed

+70
-45
lines changed

6 files changed

+70
-45
lines changed

src/cmd/validate/validate.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package validate
22

33
import (
4+
"fmt"
45
"log"
56

67
"github.com/defenseunicorns/go-oscal/src/pkg/validation"
@@ -16,7 +17,11 @@ var ValidateCmd = &cobra.Command{
1617
Long: "Validate an OSCAL document against the OSCAL schema version specified in the document.",
1718
RunE: func(cmd *cobra.Command, args []string) error {
1819
// Run the validation
19-
validationResponse, validationErr := validation.ValidationCommand(inputfile)
20+
validationResponse, err := validation.ValidationCommand(inputfile)
21+
// Return any non-validation errors if they exist
22+
if err != nil {
23+
return err
24+
}
2025

2126
// Write validation result if it was specified and exists before returning ValidateCommand error
2227
if validationResultFile != "" {
@@ -25,15 +30,15 @@ var ValidateCmd = &cobra.Command{
2530
log.Printf("Failed to write validation result to %s: %s\n", validationResultFile, err)
2631
}
2732
}
28-
// Return the error from the validation if there was one
29-
if validationErr != nil {
30-
return validationErr
33+
34+
// Log any go-oscal related warnings (ie version warnings)
35+
for _, warning := range validationResponse.Warnings {
36+
log.Print(warning)
3137
}
3238

33-
if len(validationResponse.Warnings) > 0 {
34-
for _, warning := range validationResponse.Warnings {
35-
log.Print(warning)
36-
}
39+
// Return any validation errors
40+
if validationResponse.JsonSchemaError != nil {
41+
return fmt.Errorf("invalid OSCAL document: %s", validationResponse.JsonSchemaError)
3742
}
3843

3944
// No errors, log success

src/pkg/validation/validationError.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ import (
1010
// Extension of the jsonschema.BasicError struct to include the failed value
1111
// if the failed value is a map or slice, it will be omitted
1212
type ValidatorError struct {
13-
KeywordLocation string `json:"keywordLocation" yaml:"keywordLocation"`
14-
AbsoluteKeywordLocation string `json:"absoluteKeywordLocation" yaml:"absoluteKeywordLocation"`
15-
InstanceLocation string `json:"instanceLocation" yaml:"instanceLocation"`
16-
Error string `json:"error" yaml:"error"`
17-
FailedValue interface{} `json:"failedValue,omitempty" yaml:"failedValue,omitempty"`
13+
// KeywordLocation is the location of the keyword in the schema for failing value
14+
KeywordLocation string `json:"keywordLocation" yaml:"keywordLocation"`
15+
// AbsoluteKeywordLocation is the absolute location of the keyword in the schema for failing value
16+
AbsoluteKeywordLocation string `json:"absoluteKeywordLocation" yaml:"absoluteKeywordLocation"`
17+
// InstanceLocation is the location of the instance in the document
18+
InstanceLocation string `json:"instanceLocation" yaml:"instanceLocation"`
19+
// Error is the error message
20+
Error string `json:"error" yaml:"error"`
21+
// FailedValue is the value of the key that failed validation
22+
FailedValue interface{} `json:"failedValue,omitempty" yaml:"failedValue,omitempty"`
1823
}
1924

2025
// Creates a []ValidatorError from a jsonschema.Basic

src/pkg/validation/validationResult.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,25 @@ import (
99
)
1010

1111
type ValidationResult struct {
12-
Valid bool `json:"valid" yaml:"valid"`
13-
TimeStamp time.Time `json:"timeStamp" yaml:"timeStamp"`
14-
Errors []ValidatorError `json:"errors,omitempty" yaml:"errors,omitempty"`
15-
Metadata ValidationResultMetadata `json:"metadata" yaml:"metadata"`
12+
// Valid is true if the validation result is valid
13+
Valid bool `json:"valid" yaml:"valid"`
14+
// TimeStamp is the time the validation result was created
15+
TimeStamp time.Time `json:"timeStamp" yaml:"timeStamp"`
16+
// Errors is a slice of ValidatorErrors
17+
Errors []ValidatorError `json:"errors,omitempty" yaml:"errors,omitempty"`
18+
// Metadata is the metadata of the validation result
19+
Metadata ValidationResultMetadata `json:"metadata" yaml:"metadata"`
1620
}
1721

1822
type ValidationResultMetadata struct {
19-
DocumentPath string `json:"documentPath,omitempty" yaml:"documentPath,omitempty"`
20-
DocumentType string `json:"documentType,omitempty" yaml:"documentType,omitempty"`
23+
// DocumentPath is the path to the document
24+
DocumentPath string `json:"documentPath,omitempty" yaml:"documentPath,omitempty"`
25+
// DocumentType is the type of the document
26+
DocumentType string `json:"documentType,omitempty" yaml:"documentType,omitempty"`
27+
// DocumentVersion is the version of the document
2128
DocumentVersion string `json:"documentVersion,omitempty" yaml:"documentVersion,omitempty"`
22-
SchemaVersion string `json:"schemaVersion,omitempty" yaml:"schemaVersion,omitempty"`
29+
// SchemaVersion is the version of the schema
30+
SchemaVersion string `json:"schemaVersion,omitempty" yaml:"schemaVersion,omitempty"`
2331
}
2432

2533
// NewValidationResult creates a new ValidationResult from a Validator and a slice of ValidatorErrors
@@ -50,6 +58,7 @@ func WriteValidationResult(validationResult ValidationResult, outputFile string)
5058
return files.WriteOutput(validationResultBytes, outputFile)
5159
}
5260

61+
// WriteValidationResults writes a slice of ValidationResults to a file
5362
func WriteValidationResults(validationResults []ValidationResult, outputFile string) (err error) {
5463
resultMap := map[string][]ValidationResult{
5564
"results": validationResults,

src/pkg/validation/validation_command.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ import (
77
"strings"
88

99
"github.com/defenseunicorns/go-oscal/src/pkg/versioning"
10+
"github.com/santhosh-tekuri/jsonschema/v5"
1011
)
1112

1213
type ValidationResponse struct {
1314
Validator Validator
14-
Result ValidationResult
15-
Warnings []string
15+
// Parsed validation result
16+
Result ValidationResult
17+
// Non-failing go-oscal warnings (ie: deprecated fields, newer schema versions, etc)
18+
Warnings []string
19+
// Unparsed Failing validation errors from the jsonschema library
20+
JsonSchemaError *jsonschema.ValidationError
1621
}
1722

1823
// ValidationCommand validates an OSCAL document
@@ -36,27 +41,30 @@ func ValidationCommand(inputFile string) (validationResponse ValidationResponse,
3641
}
3742
validationResponse.Validator = validator
3843

39-
// Get and set version warnings
40-
version := validator.GetSchemaVersion()
41-
err = versioning.VersionWarning(version)
42-
if err != nil {
43-
validationResponse.Warnings = append(validationResponse.Warnings, err.Error())
44-
}
45-
4644
// Set the document path
4745
validator.SetDocumentPath(inputFile)
4846

4947
// Run the validation
50-
validationError := validator.Validate()
51-
52-
// Write validation result if it was specified and exists before returning ValidateCommand error
53-
validationResult, _ := validator.GetValidationResult()
54-
validationResponse.Result = validationResult
48+
err = validator.Validate()
49+
if err != nil {
50+
validationError, ok := err.(*jsonschema.ValidationError)
51+
// If the error is not a validation error, return the error
52+
if !ok {
53+
return validationResponse, err
54+
}
55+
// Set the jsonschema error in the validation response
56+
validationResponse.JsonSchemaError = validationError
57+
}
5558

56-
// Handle the validation error
57-
if validationError != nil {
58-
return validationResponse, fmt.Errorf("failed to validate %s version %s: %s", validator.GetModelType(), validator.GetSchemaVersion(), err)
59+
// Get and set version warnings if upgrade available
60+
version := validator.GetSchemaVersion()
61+
err = versioning.VersionWarning(version)
62+
if err != nil {
63+
validationResponse.Warnings = append(validationResponse.Warnings, err.Error())
5964
}
6065

66+
// Set the response result, ignore error since we know validate has been run
67+
validationResponse.Result, _ = validator.GetValidationResult()
68+
6169
return validationResponse, nil
6270
}

src/pkg/validation/validation_command_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ func TestValidationCommand(t *testing.T) {
5353
}
5454
})
5555

56-
t.Run("returns an error and validation result if the input file fails validation", func(t *testing.T) {
56+
t.Run("returns no error and validation result if the input file fails validation", func(t *testing.T) {
5757
validationResponse, err := validation.ValidationCommand(gooscaltest.InvalidCatalogPath)
58-
if err == nil {
59-
t.Error("expected an error, got nil")
58+
if err != nil {
59+
t.Errorf("expected no error, got %s", err)
6060
}
6161

6262
if validationResponse.Result.Valid != false {

src/pkg/validation/validator.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package validation
22

33
import (
4-
"encoding/json"
54
"errors"
65
"strings"
76

@@ -131,18 +130,17 @@ func (v *Validator) Validate() error {
131130

132131
err = sch.Validate(v.jsonMap)
133132
if err != nil {
134-
// If the error is not a validation error, return the error
133+
// If the error is not a `ValidationError`, return the error
135134
validationErr, ok := err.(*jsonschema.ValidationError)
136135
if !ok {
137136
return err
138137
}
139138

140139
// Extract the specific errors from the schema error
141-
// Return the errors as a string
142140
basicErrors := ExtractErrors(v.jsonMap, validationErr.BasicOutput())
141+
// Set the validation result
143142
v.validationResult = NewValidationResult(v, basicErrors)
144-
formattedErrors, _ := json.MarshalIndent(basicErrors, "", " ")
145-
return errors.New(string(formattedErrors))
143+
return err
146144
}
147145

148146
v.validationResult = NewValidationResult(v, []ValidatorError{})

0 commit comments

Comments
 (0)