Skip to content
Snippets Groups Projects
Select Git revision
  • 90621735090b3b8c803591c24469532d512eb39c
  • master default
  • rtde
  • tmp-gpg-key-workaround-2
  • tmp-gpg-key-workaround
  • 68-git-lfs-error-in-ddeploy-job
  • split-build-and-test
  • 66-jazzy-support
  • 62-deploy-jobs-do-not-pull-files-from-lfs-manual-lfs-pull
  • 62-deploy-jobs-do-not-pull-files-from-lfs-custom-docker-image
  • py3-without-industrial-ci-test
  • 58-add-yolo-pip-package-support
  • 55-collision-between-test-jobs-due-to-dds-autodiscovery-ros2
  • 52-ddeploy-job-failing-when-enforcing-labels-alt-quick-dind-test
  • 48-python3_syntax
  • 46-default-docker-image-name-too-long
  • 45-double-pipeline-triggered-if-merge-request-has-melodic-branch-name
  • 40-repo-is-ros-testing
  • test-badges
  • test-lfs-concept
  • add-packages
21 results

Dockerfile

Blame
  • main.go 5.90 KiB
    // yajsv is a command line tool for validating JSON documents against
    // a provided JSON Schema - https://json-schema.org/
    package main
    
    import (
    	"bufio"
    	"flag"
    	"fmt"
    	"io/ioutil"
    	"log"
    	"os"
    	"path/filepath"
    	"runtime"
    	"strings"
    	"sync"
    
    	"github.com/ghodss/yaml"
    	"github.com/mitchellh/go-homedir"
    	"github.com/xeipuuv/gojsonschema"
    )
    
    var (
    	version = "undefined"
    	schemaFlag  = flag.String("s", "", "primary JSON schema to validate against, required")
    	quietFlag   = flag.Bool("q", false, "quiet, only print validation failures and errors")
    	versionFlag = flag.Bool("v", false, "print version and exit")
    
    	listFlags stringFlags
    	refFlags  stringFlags
    )
    
    func init() {
    	flag.Var(&listFlags, "l", "validate JSON documents from newline separated paths and/or globs in a text file (relative to the basename of the file itself)")
    	flag.Var(&refFlags, "r", "referenced schema(s), can be globs and/or used multiple times")
    	flag.Usage = printUsage
    }
    
    func main() {
    	log.SetFlags(0)
    	os.Exit(realMain(os.Args[1:]))
    }
    
    func realMain(args []string) int {
    	flag.CommandLine.Parse(args)
    	if *versionFlag {
    		fmt.Println(version)
    		return 0
    	}
    	if *schemaFlag == "" {
    		return usageError("missing required -s schema argument")
    	}
    
    	// Resolve document paths to validate
    	docs := make([]string, 0)
    	for _, arg := range flag.Args() {
    		docs = append(docs, glob(arg)...)
    	}
    	for _, list := range listFlags {
    		dir := filepath.Dir(list)
    		f, err := os.Open(list)
    		if err != nil {
    			log.Fatalf("%s: %s\n", list, err)
    		}
    		defer f.Close()
    
    		scanner := bufio.NewScanner(f)
    		for scanner.Scan() {
    			// Calclate the glob relative to the directory of the file list
    			pattern := strings.TrimSpace(scanner.Text())
    			if !filepath.IsAbs(pattern) {
    				pattern = filepath.Join(dir, pattern)
    			}
    			docs = append(docs, glob(pattern)...)
    		}
    		if err := scanner.Err(); err != nil {
    			log.Fatalf("%s: invalid file list: %s\n", list, err)
    		}
    	}
    	if len(docs) == 0 {
    		return usageError("no documents to validate")
    	}
    
    	// Compile target schema
    	sl := gojsonschema.NewSchemaLoader()
    	schemaUri := *schemaFlag
    	for _, ref := range refFlags {
    		for _, p := range glob(ref) {
    			if p == schemaUri {
    				continue
    			}
    
    			loader, err := jsonLoader(p)
    			if err != nil {
    				log.Fatalf("%s: unable to load schema ref: %s\n", *schemaFlag, err)
    			}
    			addSchemaErr := sl.AddSchemas(loader)
    			if addSchemaErr != nil {
    				log.Fatalf("%s: invalid schema: %s\n", p, err)
    			}
    		}
    	}
    
    	schemaLoader, err := jsonLoader(schemaUri)
    	if err != nil {
    		log.Fatalf("%s: unable to load schema: %s\n", *schemaFlag, err)
    	}
    	schema, err := sl.Compile(schemaLoader)
    	if err != nil {
    		log.Fatalf("%s: invalid schema: %s\n", *schemaFlag, err)
    	}
    
    	// Validate the schema against each doc in parallel, limiting simultaneous
    	// open files to avoid ulimit issues.
    	var wg sync.WaitGroup
    	sem := make(chan int, runtime.GOMAXPROCS(0)+10)
    	failures := make([]string, 0)
    	errors := make([]string, 0)
    	for _, p := range docs {
    		wg.Add(1)
    		go func(path string) {
    			defer wg.Done()
    			sem <- 0
    			defer func() { <-sem }()
    
    
    			loader, err := jsonLoader(path)
    			if err != nil {
    				msg := fmt.Sprintf("%s: unable to load doc: %s\n", *schemaFlag, err)
    				fmt.Println(msg)
    				errors = append(errors, msg)
    			} else {
    				result, err := schema.Validate(loader)
    				switch {
    				case err != nil:
    					msg := fmt.Sprintf("%s: error: %s", path, err)
    					fmt.Println(msg)
    					errors = append(errors, msg)
    
    				case !result.Valid():
    					lines := make([]string, len(result.Errors()))
    					for i, desc := range result.Errors() {
    						lines[i] = fmt.Sprintf("%s: fail: %s", path, desc)
    					}
    					msg := strings.Join(lines, "\n")
    					fmt.Println(msg)
    					failures = append(failures, msg)
    
    				case !*quietFlag:
    					fmt.Printf("%s: pass\n", path)
    				}
    			}
    		}(p)
    	}
    	wg.Wait()
    
    	// Summarize results (e.g. errors)
    	if !*quietFlag {
    		if len(failures) > 0 {
    			fmt.Printf("%d of %d failed validation\n", len(failures), len(docs))
    			fmt.Println(strings.Join(failures, "\n"))
    		}
    		if len(errors) > 0 {
    			fmt.Printf("%d of %d malformed documents\n", len(errors), len(docs))
    			fmt.Println(strings.Join(errors, "\n"))
    		}
    	}
    	exit := 0
    	if len(failures) > 0 {
    		exit |= 1
    	}
    	if len(errors) > 0 {
    		exit |= 2
    	}
    	return exit
    }
    
    func jsonLoader(path string) (gojsonschema.JSONLoader, error) {
    	buf, err := ioutil.ReadFile(path)
    	if err != nil {
    		return nil, err
    	}
    	switch filepath.Ext(path) {
    	case ".yml", ".yaml":
    		buf, err = yaml.YAMLToJSON(buf)
    	}
    	if err != nil {
    		return nil, err
    	}
    	return gojsonschema.NewBytesLoader(buf), nil
    }
    
    func printUsage() {
    	fmt.Fprintf(os.Stderr, `Usage: %s -s schema.json|schema.yml [options] document.json|document.yml ...
    
      yajsv validates JSON and YAML document(s) against a schema. One of three statuses are
      reported per document:
    
        pass: Document is valid relative to the schema
        fail: Document is invalid relative to the schema
        error: Document is malformed, e.g. not valid JSON or YAML
    
      The 'fail' status may be reported multiple times per-document, once for each
      schema validation failure.
    
      Sets the exit code to 1 on any failures, 2 on any errors, 3 on both, 4 on
      invalid usage. Otherwise, 0 is returned if everything passes validation.
    
    Options:
    
    `, os.Args[0])
    	flag.PrintDefaults()
    	fmt.Fprintln(os.Stderr)
    }
    
    func usageError(msg string) int {
    	fmt.Fprintln(os.Stderr, msg)
    	printUsage()
    	return 4
    }
    
    // glob is a wrapper that also resolves `~` since we may be skipping
    // the shell expansion when single-quoting globs at the command line
    func glob(pattern string) []string {
    	pattern, err := homedir.Expand(pattern)
    	if err != nil {
    		log.Fatal(err)
    	}
    	paths, err := filepath.Glob(pattern)
    	if err != nil {
    		log.Fatal(err)
    	}
    	if len(paths) == 0 {
    		log.Fatalf("%s: no such file or directory", pattern)
    	}
    	return paths
    }
    
    type stringFlags []string
    
    func (sf *stringFlags) String() string {
    	return "multi-string"
    }
    
    func (sf *stringFlags) Set(value string) error {
    	*sf = append(*sf, value)
    	return nil
    }