diff --git a/cmd/validate.go b/cmd/validate.go index 5becf459e..0e47fabc0 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -23,17 +23,17 @@ func NewValidateCommand(f Factory, ioStreams IOStreams) *cobra.Command { }, Long: ` Validate checks data for errors using a schema and then printing a list of -issues. By default validate checks dataset data against it’s own schema. +issues. By default validate checks a dataset's body against it’s own schema. Validate is a flexible command that works with data and schemas either -inside or outside of qri by providing one or both of --data and --schema +inside or outside of qri by providing one or both of --body and --schema arguments. -Providing --schema and --data is an “external validation" that uses nothing -stored in qri. When only one of schema or data args are provided, the other +Providing --schema and --body is an “external validation" that uses nothing +stored in qri. When only one of schema or body args are provided, the other comes from a dataset reference. For example, to check how a file “data.csv” validates against a dataset "foo”, we would run: - $ qri validate --data data.csv me/foo + $ qri validate --body data.csv me/foo In this case, qri will will print any validation as if data.csv was foo’s data. @@ -46,17 +46,21 @@ In this case, qri will print validation errors as if stucture.json was the schema for dataset "me/foo" Using validate this way is a great way to see how changes to data or schema -will affect a dataset before saving changes to a dataset.`, +will affect a dataset before saving changes to a dataset. + +Note: --body and --schema flags will override the dataset if both flags are provided.`, Example: ` show errors in an existing dataset: $ qri validate b5/comics`, Run: func(cmd *cobra.Command, args []string) { ExitIfErr(o.ErrOut, o.Complete(f, args)) + ExitIfErr(o.ErrOut, o.Validate()) ExitIfErr(o.ErrOut, o.Run()) }, } - cmd.Flags().StringVarP(&o.URL, "url", "u", "", "url to file to initialize from") - cmd.Flags().StringVarP(&o.Filepath, "data", "f", "", "data file to initialize from") + // TODO: restore + // cmd.Flags().StringVarP(&o.URL, "url", "u", "", "url to file to initialize from") + cmd.Flags().StringVarP(&o.Filepath, "body", "b", "", "data file to initialize from") cmd.Flags().StringVarP(&o.SchemaFilepath, "schema", "", "", "json schema file to use for validation") return cmd @@ -75,21 +79,12 @@ type ValidateOptions struct { DatasetRequests *lib.DatasetRequests } -// Complete adds any missing configuration that can only be added just before calling Run +// Complete adds any configuration that can only be added just before calling Run func (o *ValidateOptions) Complete(f Factory, args []string) (err error) { if f.RPC() != nil { return usingRPCError("validate") } - if len(args) == 0 && (o.Filepath == "" && o.SchemaFilepath == "") { - printErr(o.ErrOut, fmt.Errorf("you need to provide a dataset name or a supply the --data and --schema flags with file paths")) - return ErrBadArgs - } - - if o.Filepath != "" && o.SchemaFilepath != "" { - o.Ref = "" - } - if len(args) != 0 { o.Ref = args[0] } @@ -98,7 +93,18 @@ func (o *ValidateOptions) Complete(f Factory, args []string) (err error) { return } -// Run executes the validate command +// Validate checks that any user inputs are valid +func (o *ValidateOptions) Validate() error { + if o.URL != "" && o.Ref == "" && o.SchemaFilepath == "" { + return (lib.NewError(ErrBadArgs, "if you are validating data from a url, please include a dataset name or supply the --schema flag with a file path that Qri can validate against")) + } + if o.Ref == "" && o.Filepath == "" && o.SchemaFilepath == "" { + return lib.NewError(ErrBadArgs, "please provide a dataset name, or a supply the --body and --schema flags with file paths") + } + return nil +} + +// Run executes the run command func (o *ValidateOptions) Run() (err error) { var ( dataFile, schemaFile *os.File @@ -108,22 +114,20 @@ func (o *ValidateOptions) Run() (err error) { if o.Ref != "" { ref, err = repo.ParseDatasetRef(o.Ref) if err != nil { - printErr(o.ErrOut, fmt.Errorf("%s must be in correct DatasetRef format, [peername]/[datatset_name]", o.Ref)) - return err + return lib.NewError(err, fmt.Sprintf("%s must be in correct DatasetRef format, [peername]/[datatset_name]", o.Ref)) } } if dataFile, err = loadFileIfPath(o.Filepath); err != nil { - printErr(o.ErrOut, fmt.Errorf("could not %s", err)) - return err + return lib.NewError(err, fmt.Sprintf("error opening body file: could not %s", err)) } if schemaFile, err = loadFileIfPath(o.SchemaFilepath); err != nil { - printErr(o.ErrOut, fmt.Errorf("could not %s", err)) - return err + return lib.NewError(err, fmt.Sprintf("error opening schema file: could not %s", err)) } p := &lib.ValidateDatasetParams{ Ref: ref, + // TODO: restore // URL: addDsURL, DataFilename: filepath.Base(o.Filepath), } @@ -139,8 +143,7 @@ func (o *ValidateOptions) Run() (err error) { res := []jsonschema.ValError{} if err = o.DatasetRequests.Validate(p, &res); err != nil { - printErr(o.ErrOut, fmt.Errorf("could not validate this data. try double checking the peername, dataset name, or file names given")) - return err + return lib.NewError(err, "") } if len(res) == 0 { printSuccess(o.Out, "✔ All good!") diff --git a/cmd/validate_test.go b/cmd/validate_test.go index 73d304b1f..54166493a 100644 --- a/cmd/validate_test.go +++ b/cmd/validate_test.go @@ -4,6 +4,8 @@ import ( "bytes" // "io/ioutil" "testing" + + "github.com/qri-io/qri/lib" ) func TestValidateComplete(t *testing.T) { @@ -24,7 +26,6 @@ func TestValidateComplete(t *testing.T) { expect string err string }{ - {[]string{}, "", "", "", "you need to provide a dataset name or a supply the --data and --schema flags with file paths\n"}, {[]string{}, "filepath", "schemafilepath", "", ""}, {[]string{"test"}, "", "", "test", ""}, {[]string{"foo", "bar"}, "", "", "foo", ""}, @@ -49,11 +50,50 @@ func TestValidateComplete(t *testing.T) { ioReset(in, out, errs) continue } + + if opt.DatasetRequests == nil { + t.Errorf("case %d, opt.DatasetRequests not set.", i) + ioReset(in, out, errs) + continue + } ioReset(in, out, errs) } } +// jesus this name +func TestValidateValidate(t *testing.T) { + cases := []struct { + ref, filePath, schemaFilePath, url, err, errMsg string + }{ + {"", "", "", "", "bad arguments provided", "please provide a dataset name, or a supply the --body and --schema flags with file paths"}, + {"", "", "", "url", "bad arguments provided", "if you are validating data from a url, please include a dataset name or supply the --schema flag with a file path that Qri can validate against"}, + {"me/ref", "", "", "", "", ""}, + {"", "file/path", "schema/path", "", "", ""}, + {"me/ref", "file/path", "schema/path", "", "", ""}, + } + for i, c := range cases { + opt := &ValidateOptions{ + Ref: c.ref, + Filepath: c.filePath, + SchemaFilepath: c.schemaFilePath, + URL: c.url, + } + + err := opt.Validate() + if (err == nil && c.err != "") || (err != nil && c.err != err.Error()) { + t.Errorf("case %d, mismatched error. Expected: %s, Got: %s", i, c.err, err) + continue + } + if libErr, ok := err.(lib.Error); ok { + if libErr.Message() != c.errMsg { + t.Errorf("case %d, mismatched user-friendly message. Expected: %s, Got: %s", i, c.errMsg, libErr.Message()) + continue + } + } + } +} + func TestValidateRun(t *testing.T) { streams, in, out, errs := NewTestIOStreams() setNoColor(true) @@ -71,13 +111,14 @@ func TestValidateRun(t *testing.T) { url string expected string err string + errMsg string }{ - {"peer/movies", "", "", "", movieOutput, ""}, - {"peer/bad_dataset", "", "", "", "", "cannot find dataset: peer/bad_dataset@QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt"}, - {"", "bad/filepath", "testdata/days_of_week_schema.json", "", "", "open /Users/ramfox/go/src/github.com/qri-io/qri/cmd/bad/filepath: no such file or directory"}, - {"", "testdata/days_of_week.csv", "bad/schema_filepath", "", "", "open /Users/ramfox/go/src/github.com/qri-io/qri/cmd/bad/schema_filepath: no such file or directory"}, - {"", "testdata/days_of_week.csv", "testdata/days_of_week_schema.json", "", "✔ All good!\n", ""}, - // TOD0: pull from url + {"peer/movies", "", "", "", movieOutput, "", ""}, + {"peer/bad_dataset", "", "", "", "", "cannot find dataset: peer/bad_dataset@QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt", ""}, + {"", "bad/filepath", "testdata/days_of_week_schema.json", "", "", "open /Users/ramfox/go/src/github.com/qri-io/qri/cmd/bad/filepath: no such file or directory", "error opening body file: could not open /Users/ramfox/go/src/github.com/qri-io/qri/cmd/bad/filepath: no such file or directory"}, + {"", "testdata/days_of_week.csv", "bad/schema_filepath", "", "", "open /Users/ramfox/go/src/github.com/qri-io/qri/cmd/bad/schema_filepath: no such file or directory", "error opening schema file: could not open /Users/ramfox/go/src/github.com/qri-io/qri/cmd/bad/schema_filepath: no such file or directory"}, + {"", "testdata/days_of_week.csv", "testdata/days_of_week_schema.json", "", "✔ All good!\n", "", ""}, + // TODO: pull from url } for i, c := range cases { @@ -103,6 +144,14 @@ func TestValidateRun(t *testing.T) { continue } + if libErr, ok := err.(lib.Error); ok { + if libErr.Message() != c.errMsg { + t.Errorf("case %d, mismatched user-friendly error. Expected: '%s', Got: '%v'", i, c.errMsg, libErr.Message()) + ioReset(in, out, errs) + continue + } + } + if c.expected != out.String() { t.Errorf("case %d, output mismatch. Expected: '%s', Got: '%s'", i, c.expected, out.String()) ioReset(in, out, errs)