Skip to content

Commit

Permalink
feat(cmd Validate): Validate func for validate cmd
Browse files Browse the repository at this point in the history
This commit adds a `Validate` function that validate's user input. It gets call after `Complete` fills in the arguments, but before `Run` uses those arguments.

This commit also changes the verbage in the `validate` help section from 'data' to 'body'

It also removes the `url` flag from the `validate` command. Passing the URL down to the `lib.Validate` function was removed in a previous commit. This ensure less user confusion about why `--url` isn't actually doing anything.
  • Loading branch information
ramfox committed Jun 28, 2018
1 parent fb05985 commit b9e675b
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 34 deletions.
57 changes: 30 additions & 27 deletions cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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]
}
Expand All @@ -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
Expand All @@ -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),
}
Expand All @@ -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!")
Expand Down
63 changes: 56 additions & 7 deletions cmd/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
// "io/ioutil"
"testing"

"github.com/qri-io/qri/lib"
)

func TestValidateComplete(t *testing.T) {
Expand All @@ -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", ""},
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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)
Expand Down

0 comments on commit b9e675b

Please sign in to comment.