blob: d06e2a422426dd334ed2258da9d0facc46d85c16 [file] [log] [blame]
// Package jsonschema has utility functions for creating JSON Schema files from
// structs, and also for validating a JSON file against a schema.
//
// These can be used together to validate input JSON files. To add validation to
// an existing type, e.g. `foo.MyConfiguration`, defined in `/foo.go`, the first
// step is to create sub-directory called `generate`, and in there have a singe
// appliation, `/foo/generate/main.go`, which uses go:generate to emit a schema
// file.
//
// //go:generate bazelisk run --config=mayberemote //:go -- run .
// package main
//
// import (
// "go.skia.org/infra/go/jsonschema"
// "go.skia.org/infra/foo"
// )
//
// func main() {
// jsonschema.GenerateSchema("../schema.json", &foo.MyConfiguration{})
// }
//
// Note that running "go generate" on that file will drop `schema.json` file in
// the foo directory. Now add a `Validate` function to `foo.go` that uses the
// schema file, which we can make accessible by embedding it:
//
// import (
//
// _ "embed" // For embed functionality.
//
// )
//
// //go:embed schema.json
// var schema []byte
//
// func ValidateFooFile(ctx context.Context, document []byte) error {
// validationErrors, err := jsonschema.Validate(ctx, document, schema)
// ...
// }
package jsonschema
import (
"encoding/json"
"errors"
"fmt"
"io"
"github.com/invopop/jsonschema"
"github.com/xeipuuv/gojsonschema"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
)
// ErrSchemaViolation is returned from Validate if the document doesn't conform
// to the schema.
var ErrSchemaViolation = errors.New("schema violation")
// Validate returns null if the document represents a JSON body that conforms to
// the schema. If err is not nil then the slice of strings will contain a list
// of schema violations.
func Validate(document, schema []byte) ([]string, error) {
schemaLoader := gojsonschema.NewBytesLoader(schema)
documentLoader := gojsonschema.NewBytesLoader(document)
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
return nil, skerr.Wrapf(err, "failed while validating")
}
if len(result.Errors()) > 0 {
formattedResults := make([]string, len(result.Errors()))
for i, e := range result.Errors() {
formattedResults[i] = fmt.Sprintf("%d: %s", i, e.String())
}
return formattedResults, ErrSchemaViolation
}
return nil, nil
}
// GenerateSchema writes the JSON Schema for 'v' into 'filename' and will exit
// via sklog.Fatal if any errors occur. This function is designed for use
// in an app you would run via go generate.
func GenerateSchema(filename string, v interface{}) {
b, err := json.MarshalIndent(jsonschema.Reflect(v), "", " ")
if err != nil {
sklog.Fatal(err)
}
err = util.WithWriteFile(filename, func(w io.Writer) error {
_, err := w.Write(b)
return err
})
if err != nil {
sklog.Fatal(err)
}
}