diff --git a/check/checkdata/schema/schema.go b/check/checkdata/schema/schema.go new file mode 100644 index 00000000..eb3d9c2d --- /dev/null +++ b/check/checkdata/schema/schema.go @@ -0,0 +1,85 @@ +// Package schema contains code for working with JSON schema. +package schema + +import ( + "net/url" + "path/filepath" + "regexp" + + "github.com/arduino/go-paths-helper" + "github.com/xeipuuv/gojsonschema" +) + +// Compile compiles the schema files specified by the filename arguments and returns the compiled schema. +func Compile(schemaFilename string, referencedSchemaFilenames []string, schemasPath *paths.Path) *gojsonschema.Schema { + schemaLoader := gojsonschema.NewSchemaLoader() + + // Load the referenced schemas. + for _, referencedSchemaFilename := range referencedSchemaFilenames { + referencedSchemaPath := schemasPath.Join(referencedSchemaFilename) + referencedSchemaURI := pathURI(referencedSchemaPath) + err := schemaLoader.AddSchemas(gojsonschema.NewReferenceLoader(referencedSchemaURI)) + if err != nil { + panic(err.Error()) + } + } + + // Compile the schema. + schemaPath := schemasPath.Join(schemaFilename) + schemaURI := pathURI(schemaPath) + compiledSchema, err := schemaLoader.Compile(gojsonschema.NewReferenceLoader(schemaURI)) + if err != nil { + panic(err.Error()) + } + return compiledSchema +} + +// Validate validates an instance against a JSON schema and returns the gojsonschema.Result object. +func Validate(instanceObject interface{}, schemaObject *gojsonschema.Schema) *gojsonschema.Result { + result, err := schemaObject.Validate(gojsonschema.NewGoLoader(instanceObject)) + if err != nil { + panic(err.Error()) + } + + return result +} + +// RequiredPropertyMissing returns whether the given required property is missing from the document. +func RequiredPropertyMissing(propertyName string, validationResult *gojsonschema.Result) bool { + return ValidationErrorMatch("required", "(root)", propertyName+" is required", validationResult) +} + +// PropertyPatternMismatch returns whether the given property did not match the regular expression defined in the JSON schema. +func PropertyPatternMismatch(propertyName string, validationResult *gojsonschema.Result) bool { + return ValidationErrorMatch("pattern", propertyName, "", validationResult) +} + +// ValidationErrorMatch returns whether the given query matches against the JSON schema validation error. +// See: https://github.com/xeipuuv/gojsonschema#working-with-errors +func ValidationErrorMatch(typeQuery string, fieldQuery string, descriptionQueryRegexp string, validationResult *gojsonschema.Result) bool { + if validationResult.Valid() { + // No error, so nothing to match + return false + } + for _, validationError := range validationResult.Errors() { + if typeQuery == "" || typeQuery == validationError.Type() { + if fieldQuery == "" || fieldQuery == validationError.Field() { + descriptionQuery := regexp.MustCompile(descriptionQueryRegexp) + return descriptionQuery.MatchString(validationError.Description()) + } + } + } + + return false +} + +// pathURI returns the URI representation of the path argument. +func pathURI(path *paths.Path) string { + uriFriendlyPath := filepath.ToSlash(path.String()) + pathURI := url.URL{ + Scheme: "file", + Path: uriFriendlyPath, + } + + return pathURI.String() +} diff --git a/check/checkfunctions/library.go b/check/checkfunctions/library.go index f3419b38..da59fa2c 100644 --- a/check/checkfunctions/library.go +++ b/check/checkfunctions/library.go @@ -4,8 +4,8 @@ package checkfunctions import ( "github.com/arduino/arduino-check/check/checkdata" + "github.com/arduino/arduino-check/check/checkdata/schema" "github.com/arduino/arduino-check/check/checkresult" - "github.com/arduino/arduino-check/project/library/libraryproperties" ) // LibraryPropertiesFormat checks for invalid library.properties format. @@ -22,7 +22,7 @@ func LibraryPropertiesNameFieldMissing() (result checkresult.Type, output string return checkresult.NotRun, "" } - if libraryproperties.FieldMissing("name", checkdata.LibraryPropertiesSchemaValidationResult()) { + if schema.RequiredPropertyMissing("name", checkdata.LibraryPropertiesSchemaValidationResult()) { return checkresult.Fail, "" } return checkresult.Pass, "" @@ -34,7 +34,7 @@ func LibraryPropertiesNameFieldDisallowedCharacters() (result checkresult.Type, return checkresult.NotRun, "" } - if libraryproperties.FieldPatternMismatch("name", checkdata.LibraryPropertiesSchemaValidationResult()) { + if schema.PropertyPatternMismatch("name", checkdata.LibraryPropertiesSchemaValidationResult()) { return checkresult.Fail, "" } @@ -47,7 +47,7 @@ func LibraryPropertiesVersionFieldMissing() (result checkresult.Type, output str return checkresult.NotRun, "" } - if libraryproperties.FieldMissing("version", checkdata.LibraryPropertiesSchemaValidationResult()) { + if schema.RequiredPropertyMissing("version", checkdata.LibraryPropertiesSchemaValidationResult()) { return checkresult.Fail, "" } return checkresult.Pass, "" diff --git a/configuration/configuration.go b/configuration/configuration.go index ba66592f..b61bbe93 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -2,6 +2,8 @@ package configuration import ( + "os" + "github.com/arduino/arduino-check/configuration/checkmode" "github.com/arduino/arduino-check/project/projecttype" "github.com/arduino/arduino-check/result/outputformat" @@ -79,3 +81,9 @@ var targetPath *paths.Path func TargetPath() *paths.Path { return targetPath } + +// SchemasPath returns the path to the folder containing the JSON schemas. +func SchemasPath() *paths.Path { + workingPath, _ := os.Getwd() + return paths.New(workingPath) +} diff --git a/project/library/libraryproperties/libraryproperties.go b/project/library/libraryproperties/libraryproperties.go index a64b2bcd..024b6f6d 100644 --- a/project/library/libraryproperties/libraryproperties.go +++ b/project/library/libraryproperties/libraryproperties.go @@ -2,10 +2,8 @@ package libraryproperties import ( - "net/url" - "os" - "path/filepath" - + "github.com/arduino/arduino-check/check/checkdata/schema" + "github.com/arduino/arduino-check/configuration" "github.com/arduino/go-paths-helper" "github.com/arduino/go-properties-orderedmap" "github.com/xeipuuv/gojsonschema" @@ -22,51 +20,8 @@ func Properties(libraryPath *paths.Path) (*properties.Map, error) { // Validate validates library.properties data against the JSON schema. func Validate(libraryProperties *properties.Map) *gojsonschema.Result { - workingPath, _ := os.Getwd() - schemaPath := paths.New(workingPath).Join("arduino-library-properties-schema.json") - uriFriendlySchemaPath := filepath.ToSlash(schemaPath.String()) - schemaPathURI := url.URL{ - Scheme: "file", - Path: uriFriendlySchemaPath, - } - schemaLoader := gojsonschema.NewReferenceLoader(schemaPathURI.String()) - - documentLoader := gojsonschema.NewGoLoader(libraryProperties) - - result, err := gojsonschema.Validate(schemaLoader, documentLoader) - if err != nil { - panic(err.Error()) - } - - return result -} - -// FieldMissing returns whether the given required field is missing from library.properties. -func FieldMissing(fieldName string, validationResult *gojsonschema.Result) bool { - return ValidationErrorMatch("required", "(root)", fieldName+" is required", validationResult) -} - -// FieldPatternMismatch returns whether the given field did not match the regular expression defined in the JSON schema. -func FieldPatternMismatch(fieldName string, validationResult *gojsonschema.Result) bool { - return ValidationErrorMatch("pattern", fieldName, "", validationResult) -} - -// ValidationErrorMatch returns whether the given query matches against the JSON schema validation error. -// See: https://github.com/xeipuuv/gojsonschema#working-with-errors -func ValidationErrorMatch(typeQuery string, fieldQuery string, descriptionQuery string, validationResult *gojsonschema.Result) bool { - if validationResult.Valid() { - // No error, so nothing to match - return false - } - for _, validationError := range validationResult.Errors() { - if typeQuery == "" || typeQuery == validationError.Type() { - if fieldQuery == "" || fieldQuery == validationError.Field() { - if descriptionQuery == "" || descriptionQuery == validationError.Description() { - return true - } - } - } - } + referencedSchemaFilenames := []string{} + schemaObject := schema.Compile("arduino-library-properties-schema.json", referencedSchemaFilenames, configuration.SchemasPath()) - return false + return schema.Validate(libraryProperties, schemaObject) }