From a86a8fca09d48b8e2e90fb42159caf8c248a4509 Mon Sep 17 00:00:00 2001 From: per1234 <accounts@perglass.com> Date: Fri, 30 Oct 2020 15:42:17 -0700 Subject: [PATCH 1/5] Generalize JSON schema handling code Previously, the JSON schema handling code was all under the libraryproperties package, and written specifically for use in validating library.properties against the JSON schema. However, there will be be a need to work with schemas in other contexts, most immediately for writitng the schema tests, but also likely when additional schemas are added for the other Arduino project configuration data files. So it will be helpful to have the general purpose schema handling code in a dedicated package, leaving only the library.properties-specific aspects of the code in the libraryproperties package. --- check/checkdata/schema/schema.go | 77 +++++++++++++++++++ check/checkfunctions/library.go | 8 +- .../libraryproperties/libraryproperties.go | 54 +------------ util/util.go | 20 +++++ 4 files changed, 105 insertions(+), 54 deletions(-) create mode 100644 check/checkdata/schema/schema.go create mode 100644 util/util.go diff --git a/check/checkdata/schema/schema.go b/check/checkdata/schema/schema.go new file mode 100644 index 00000000..1ee64212 --- /dev/null +++ b/check/checkdata/schema/schema.go @@ -0,0 +1,77 @@ +// Package schema contains code for working with JSON schema. +package schema + +import ( + "os" + "regexp" + + "github.com/arduino/arduino-check/util" + "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) *gojsonschema.Schema { + workingPath, _ := os.Getwd() + schemasPath := paths.New(workingPath) + + schemaLoader := gojsonschema.NewSchemaLoader() + + // Load the referenced schemas. + for _, referencedSchemaFilename := range referencedSchemaFilenames { + referencedSchemaPath := schemasPath.Join(referencedSchemaFilename) + referencedSchemaURI := util.PathURI(referencedSchemaPath) + err := schemaLoader.AddSchemas(gojsonschema.NewReferenceLoader(referencedSchemaURI)) + if err != nil { + panic(err.Error()) + } + } + + // Compile the schema. + schemaPath := schemasPath.Join(schemaFilename) + schemaURI := util.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 +} 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/project/library/libraryproperties/libraryproperties.go b/project/library/libraryproperties/libraryproperties.go index a64b2bcd..708bc451 100644 --- a/project/library/libraryproperties/libraryproperties.go +++ b/project/library/libraryproperties/libraryproperties.go @@ -2,10 +2,7 @@ package libraryproperties import ( - "net/url" - "os" - "path/filepath" - + "github.com/arduino/arduino-check/check/checkdata/schema" "github.com/arduino/go-paths-helper" "github.com/arduino/go-properties-orderedmap" "github.com/xeipuuv/gojsonschema" @@ -22,51 +19,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) - return false + return schema.Validate(libraryProperties, schemaObject) } diff --git a/util/util.go b/util/util.go new file mode 100644 index 00000000..cbc4ddaf --- /dev/null +++ b/util/util.go @@ -0,0 +1,20 @@ +// Package util contains general purpose utility code. +package util + +import ( + "net/url" + "path/filepath" + + "github.com/arduino/go-paths-helper" +) + +// 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() +} From cb43cc2a209eb5977b067b87bc09cea8a1d7eece Mon Sep 17 00:00:00 2001 From: per1234 <accounts@perglass.com> Date: Wed, 4 Nov 2020 02:01:32 -0800 Subject: [PATCH 2/5] Add schemas path parameter to schema.Compile() This allows the schemas path to be specified by the caller. --- check/checkdata/schema/schema.go | 15 ++++++++++++--- .../libraryproperties/libraryproperties.go | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/check/checkdata/schema/schema.go b/check/checkdata/schema/schema.go index 1ee64212..5153904a 100644 --- a/check/checkdata/schema/schema.go +++ b/check/checkdata/schema/schema.go @@ -10,11 +10,20 @@ import ( "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) *gojsonschema.Schema { +var schemasPath *paths.Path + +func init() { workingPath, _ := os.Getwd() - schemasPath := paths.New(workingPath) + schemasPath = paths.New(workingPath) +} +// SchemasPath returns the path to the folder containing the JSON schemas. +func SchemasPath() *paths.Path { + return schemasPath +} + +// 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. diff --git a/project/library/libraryproperties/libraryproperties.go b/project/library/libraryproperties/libraryproperties.go index 708bc451..6ae36230 100644 --- a/project/library/libraryproperties/libraryproperties.go +++ b/project/library/libraryproperties/libraryproperties.go @@ -20,7 +20,7 @@ func Properties(libraryPath *paths.Path) (*properties.Map, error) { // Validate validates library.properties data against the JSON schema. func Validate(libraryProperties *properties.Map) *gojsonschema.Result { referencedSchemaFilenames := []string{} - schemaObject := schema.Compile("arduino-library-properties-schema.json", referencedSchemaFilenames) + schemaObject := schema.Compile("arduino-library-properties-schema.json", referencedSchemaFilenames, schema.SchemasPath()) return schema.Validate(libraryProperties, schemaObject) } From 14fa8486527ceee7770e56be9f0475fd96b5d78c Mon Sep 17 00:00:00 2001 From: per1234 <accounts@perglass.com> Date: Wed, 4 Nov 2020 05:37:40 -0800 Subject: [PATCH 3/5] Determine schemas path on demand Rather than determining the schemas path once at initialization and storing it in a package variable, determine it on demand when the getter function is called. --- check/checkdata/schema/schema.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/check/checkdata/schema/schema.go b/check/checkdata/schema/schema.go index 5153904a..35f0c6e0 100644 --- a/check/checkdata/schema/schema.go +++ b/check/checkdata/schema/schema.go @@ -10,16 +10,10 @@ import ( "github.com/xeipuuv/gojsonschema" ) -var schemasPath *paths.Path - -func init() { - workingPath, _ := os.Getwd() - schemasPath = paths.New(workingPath) -} - // SchemasPath returns the path to the folder containing the JSON schemas. func SchemasPath() *paths.Path { - return schemasPath + workingPath, _ := os.Getwd() + return paths.New(workingPath) } // Compile compiles the schema files specified by the filename arguments and returns the compiled schema. From e47fcdaab93a30240929c1c2d24638068a7e1703 Mon Sep 17 00:00:00 2001 From: per1234 <accounts@perglass.com> Date: Thu, 5 Nov 2020 02:12:28 -0800 Subject: [PATCH 4/5] Move `pathURI()` to the `schema` package I don't foresee any need for this function outside the `schema` package, so it doesn't seem necessary to export it. --- check/checkdata/schema/schema.go | 18 +++++++++++++++--- util/util.go | 20 -------------------- 2 files changed, 15 insertions(+), 23 deletions(-) delete mode 100644 util/util.go diff --git a/check/checkdata/schema/schema.go b/check/checkdata/schema/schema.go index 35f0c6e0..03b7ba47 100644 --- a/check/checkdata/schema/schema.go +++ b/check/checkdata/schema/schema.go @@ -2,10 +2,11 @@ package schema import ( + "net/url" "os" + "path/filepath" "regexp" - "github.com/arduino/arduino-check/util" "github.com/arduino/go-paths-helper" "github.com/xeipuuv/gojsonschema" ) @@ -23,7 +24,7 @@ func Compile(schemaFilename string, referencedSchemaFilenames []string, schemasP // Load the referenced schemas. for _, referencedSchemaFilename := range referencedSchemaFilenames { referencedSchemaPath := schemasPath.Join(referencedSchemaFilename) - referencedSchemaURI := util.PathURI(referencedSchemaPath) + referencedSchemaURI := pathURI(referencedSchemaPath) err := schemaLoader.AddSchemas(gojsonschema.NewReferenceLoader(referencedSchemaURI)) if err != nil { panic(err.Error()) @@ -32,7 +33,7 @@ func Compile(schemaFilename string, referencedSchemaFilenames []string, schemasP // Compile the schema. schemaPath := schemasPath.Join(schemaFilename) - schemaURI := util.PathURI(schemaPath) + schemaURI := pathURI(schemaPath) compiledSchema, err := schemaLoader.Compile(gojsonschema.NewReferenceLoader(schemaURI)) if err != nil { panic(err.Error()) @@ -78,3 +79,14 @@ func ValidationErrorMatch(typeQuery string, fieldQuery string, descriptionQueryR 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/util/util.go b/util/util.go deleted file mode 100644 index cbc4ddaf..00000000 --- a/util/util.go +++ /dev/null @@ -1,20 +0,0 @@ -// Package util contains general purpose utility code. -package util - -import ( - "net/url" - "path/filepath" - - "github.com/arduino/go-paths-helper" -) - -// 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() -} From fb49bcf74d93de6249ca3a0cd678851d9f3431f5 Mon Sep 17 00:00:00 2001 From: per1234 <accounts@perglass.com> Date: Thu, 5 Nov 2020 03:55:53 -0800 Subject: [PATCH 5/5] Move SchemasPath() to the configuration package --- check/checkdata/schema/schema.go | 7 ------- configuration/configuration.go | 8 ++++++++ project/library/libraryproperties/libraryproperties.go | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/check/checkdata/schema/schema.go b/check/checkdata/schema/schema.go index 03b7ba47..eb3d9c2d 100644 --- a/check/checkdata/schema/schema.go +++ b/check/checkdata/schema/schema.go @@ -3,7 +3,6 @@ package schema import ( "net/url" - "os" "path/filepath" "regexp" @@ -11,12 +10,6 @@ import ( "github.com/xeipuuv/gojsonschema" ) -// SchemasPath returns the path to the folder containing the JSON schemas. -func SchemasPath() *paths.Path { - workingPath, _ := os.Getwd() - return paths.New(workingPath) -} - // 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() 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 6ae36230..024b6f6d 100644 --- a/project/library/libraryproperties/libraryproperties.go +++ b/project/library/libraryproperties/libraryproperties.go @@ -3,6 +3,7 @@ package libraryproperties import ( "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" @@ -20,7 +21,7 @@ func Properties(libraryPath *paths.Path) (*properties.Map, error) { // Validate validates library.properties data against the JSON schema. func Validate(libraryProperties *properties.Map) *gojsonschema.Result { referencedSchemaFilenames := []string{} - schemaObject := schema.Compile("arduino-library-properties-schema.json", referencedSchemaFilenames, schema.SchemasPath()) + schemaObject := schema.Compile("arduino-library-properties-schema.json", referencedSchemaFilenames, configuration.SchemasPath()) return schema.Validate(libraryProperties, schemaObject) }