Skip to content

Commit

Permalink
Merge pull request #457 from authzed/success-success-success
Browse files Browse the repository at this point in the history
Fixes for zed validate on multiple files
  • Loading branch information
tstirrat15 authored Jan 22, 2025
2 parents 753fe21 + d7347ab commit f150a84
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 64 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
dist/

# Local-only files
go.work
go.work.sum
2 changes: 1 addition & 1 deletion internal/cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func importCmdFunc(cmd *cobra.Command, args []string) error {
return err
}

decoder, err := decode.DecoderForURL(u, args)
decoder, err := decode.DecoderForURL(u)
if err != nil {
return err
}
Expand Down
80 changes: 37 additions & 43 deletions internal/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ var validateCmd = &cobra.Command{
ValidArgsFunction: commands.FileExtensionCompletions("zed", "yaml", "zaml"),
PreRunE: validatePreRunE,
RunE: validateCmdFunc,

// A schema that causes the parser/compiler to error will halt execution
// of this command with an error. In that case, we want to just display the error,
// rather than showing usage for this command.
SilenceUsage: true,
}

func validatePreRunE(cmd *cobra.Command, _ []string) error {
Expand All @@ -89,25 +94,25 @@ func validatePreRunE(cmd *cobra.Command, _ []string) error {
return nil
}

func validateCmdFunc(cmd *cobra.Command, args []string) error {
func validateCmdFunc(cmd *cobra.Command, filenames []string) error {
// Initialize variables for multiple files
var (
onlySchemaFiles = true
validateContentsFiles = make([][]byte, 0, len(args))
parsedFiles = make([]validationfile.ValidationFile, 0, len(args))
totalAssertions int
totalRelationsValidated int
totalFiles = len(args)
totalFiles = len(filenames)
successfullyValidatedFiles = 0
)

for _, arg := range args {
u, err := url.Parse(arg)
for _, filename := range filenames {
// If we're running over multiple files, print the filename for context/debugging purposes
if totalFiles > 1 {
console.Println(filename)
}

u, err := url.Parse(filename)
if err != nil {
return err
}

decoder, err := decode.DecoderForURL(u, args)
decoder, err := decode.DecoderForURL(u)
if err != nil {
return err
}
Expand All @@ -122,21 +127,16 @@ func validateCmdFunc(cmd *cobra.Command, args []string) error {
return err
}

onlySchemaFiles = isOnlySchema && onlySchemaFiles

// Store the contents and parsed file
validateContentsFiles = append(validateContentsFiles, validateContents)
parsedFiles = append(parsedFiles, parsed)
}

// Create the development context for all parsed files
ctx := cmd.Context()
tuples := make([]*core.RelationTuple, 0)
tuples := make([]*core.RelationTuple, 0)
totalAssertions := 0
totalRelationsValidated := 0

for _, parsed := range parsedFiles {
for _, rel := range parsed.Relationships.Relationships {
tuples = append(tuples, rel.ToCoreTuple())
}

// Create the development context for each run
ctx := cmd.Context()
devCtx, devErrs, err := development.NewDevContext(ctx, &devinterface.RequestContext{
Schema: parsed.Schema.Schema,
Relationships: tuples,
Expand All @@ -146,24 +146,20 @@ func validateCmdFunc(cmd *cobra.Command, args []string) error {
}
if devErrs != nil {
schemaOffset := 1 /* for the 'schema:' */
if onlySchemaFiles {
if isOnlySchema {
schemaOffset = 0
}

// Output errors for all files
for _, validateContents := range validateContentsFiles {
outputDeveloperErrorsWithLineOffset(validateContents, devErrs.InputErrors, schemaOffset)
}
// Output errors
outputDeveloperErrorsWithLineOffset(validateContents, devErrs.InputErrors, schemaOffset)
}
// Run assertions for all parsed files
// Run assertions
adevErrs, aerr := development.RunAllAssertions(devCtx, &parsed.Assertions)
if aerr != nil {
return aerr
}
if adevErrs != nil {
for _, validateContents := range validateContentsFiles {
outputDeveloperErrors(validateContents, adevErrs)
}
outputDeveloperErrors(validateContents, adevErrs)
}
successfullyValidatedFiles++

Expand All @@ -173,9 +169,7 @@ func validateCmdFunc(cmd *cobra.Command, args []string) error {
return rerr
}
if erDevErrs != nil {
for _, validateContents := range validateContentsFiles {
outputDeveloperErrors(validateContents, erDevErrs)
}
outputDeveloperErrors(validateContents, erDevErrs)
}
// Print out any warnings for all files
warnings, err := development.GetWarnings(ctx, devCtx)
Expand All @@ -185,9 +179,7 @@ func validateCmdFunc(cmd *cobra.Command, args []string) error {
if len(warnings) > 0 {
for _, warning := range warnings {
console.Printf("%s%s\n", warningPrefix(), warning.Message)
for _, validateContents := range validateContentsFiles {
outputForLine(validateContents, uint64(warning.Line), warning.SourceCode, uint64(warning.Column)) // warning.LineNumber is 1-indexed
}
outputForLine(validateContents, uint64(warning.Line), warning.SourceCode, uint64(warning.Column)) // warning.LineNumber is 1-indexed
console.Printf("\n")
}

Expand All @@ -197,15 +189,17 @@ func validateCmdFunc(cmd *cobra.Command, args []string) error {
}
totalAssertions += len(parsed.Assertions.AssertTrue) + len(parsed.Assertions.AssertFalse)
totalRelationsValidated += len(parsed.ExpectedRelations.ValidationMap)
}

console.Printf(" - %d relationships loaded, %d assertions run, %d expected relations validated\n",
len(tuples),
totalAssertions,
totalRelationsValidated,
)
console.Printf(" - %d relationships loaded, %d assertions run, %d expected relations validated\n",
len(tuples),
totalAssertions,
totalRelationsValidated,
)
}

console.Printf("total files: %d, successfully validated files: %d\n", totalFiles, successfullyValidatedFiles)
if totalFiles > 1 {
console.Printf("total files: %d, successfully validated files: %d\n", totalFiles, successfullyValidatedFiles)
}
return nil
}

Expand Down
49 changes: 29 additions & 20 deletions internal/decode/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"strings"

Expand All @@ -34,21 +35,21 @@ type Func func(out interface{}) ([]byte, bool, error)

// DecoderForURL returns the appropriate decoder for a given URL.
// Some URLs have special handling to dereference to the actual file.
func DecoderForURL(u *url.URL, args []string) (d Func, err error) {
func DecoderForURL(u *url.URL) (d Func, err error) {
switch s := u.Scheme; s {
case "file":
d = fileDecoder(u, args)
d = fileDecoder(u)
case "http", "https":
d = httpDecoder(u)
case "":
d = fileDecoder(u, args)
d = fileDecoder(u)
default:
err = fmt.Errorf("%s scheme not supported", s)
}
return
}

func fileDecoder(u *url.URL, args []string) Func {
func fileDecoder(u *url.URL) Func {
return func(out interface{}) ([]byte, bool, error) {
file, err := os.Open(u.Path)
if err != nil {
Expand All @@ -58,7 +59,7 @@ func fileDecoder(u *url.URL, args []string) Func {
if err != nil {
return nil, false, err
}
isOnlySchema, err := unmarshalAsYAMLOrSchemaWithFile(data, out, args)
isOnlySchema, err := unmarshalAsYAMLOrSchemaWithFile(data, out, u.Path)
return data, isOnlySchema, err
}
}
Expand Down Expand Up @@ -107,25 +108,33 @@ func directHTTPDecoder(u *url.URL) Func {
}

// Uses the files passed in the args and looks for the specified schemaFile to parse the YAML.
func unmarshalAsYAMLOrSchemaWithFile(data []byte, out interface{}, args []string) (bool, error) {
func unmarshalAsYAMLOrSchemaWithFile(data []byte, out interface{}, filename string) (bool, error) {
if strings.Contains(string(data), "schemaFile:") && !strings.Contains(string(data), "schema:") {
if err := yaml.Unmarshal(data, out); err != nil {
return false, err
}
schema := out.(*validationfile.ValidationFile)

for _, arg := range args {
// Check if the file specified in schemaFile is passed as an arguement.
if strings.Contains(arg, schema.SchemaFile) {
file, err := os.Open(arg)
if err != nil {
return false, err
}
data, err = io.ReadAll(file)
if err != nil {
return false, err
}
}
validationFile := out.(*validationfile.ValidationFile)

// Need to join the original filepath with the requested filepath
// to construct the path to the referenced schema file.
// NOTE: This does not allow for yaml files to transitively reference
// each other's schemaFile fields.
// TODO: enable this behavior
schemaPath := filepath.Join(path.Dir(filename), validationFile.SchemaFile)

if !filepath.IsLocal(schemaPath) {
// We want to prevent access of files that are outside of the folder
// where the command was originally invoked. This should do that.
return false, fmt.Errorf("schema filepath %s must be local to where the command was invoked", schemaPath)
}

file, err := os.Open(schemaPath)
if err != nil {
return false, err
}
data, err = io.ReadAll(file)
if err != nil {
return false, err
}
}
return unmarshalAsYAMLOrSchema(data, out)
Expand Down

0 comments on commit f150a84

Please sign in to comment.