gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/yamltest/main.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Binary yamltest does strict yaml parsing and validation.
    16  package main
    17  
    18  import (
    19  	"encoding/json"
    20  	"errors"
    21  	"flag"
    22  	"fmt"
    23  	"os"
    24  
    25  	"github.com/xeipuuv/gojsonschema"
    26  	yaml "gopkg.in/yaml.v2"
    27  )
    28  
    29  var (
    30  	schema = flag.String("schema", "", "path to JSON schema file.")
    31  	strict = flag.Bool("strict", true, "Whether to enable strict mode for YAML decoding")
    32  )
    33  
    34  func fixup(v any) (any, error) {
    35  	switch x := v.(type) {
    36  	case map[any]any:
    37  		// Coerse into a string-based map, required for yaml.
    38  		strMap := make(map[string]any)
    39  		for k, v := range x {
    40  			strK, ok := k.(string)
    41  			if !ok {
    42  				// This cannot be converted to JSON at all.
    43  				return nil, fmt.Errorf("invalid key %T in (%#v)", k, x)
    44  			}
    45  			fv, err := fixup(v)
    46  			if err != nil {
    47  				return nil, fmt.Errorf(".%s%w", strK, err)
    48  			}
    49  			strMap[strK] = fv
    50  		}
    51  		return strMap, nil
    52  	case []any:
    53  		for i := range x {
    54  			fv, err := fixup(x[i])
    55  			if err != nil {
    56  				return nil, fmt.Errorf("[%d]%w", i, err)
    57  			}
    58  			x[i] = fv
    59  		}
    60  		return x, nil
    61  	default:
    62  		return v, nil
    63  	}
    64  }
    65  
    66  func loadFile(filename string) (gojsonschema.JSONLoader, error) {
    67  	f, err := os.Open(filename)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	defer f.Close()
    72  	dec := yaml.NewDecoder(f)
    73  	dec.SetStrict(*strict)
    74  	var object any
    75  	if err := dec.Decode(&object); err != nil {
    76  		return nil, err
    77  	}
    78  	fixedObject, err := fixup(object) // For serialization.
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	bytes, err := json.Marshal(fixedObject)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	return gojsonschema.NewStringLoader(string(bytes)), nil
    87  }
    88  
    89  func main() {
    90  	flag.Parse()
    91  	if *schema == "" || len(flag.Args()) == 0 {
    92  		flag.Usage()
    93  		os.Exit(2)
    94  	}
    95  
    96  	// Construct our schema loader.
    97  	schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", *schema))
    98  
    99  	// Parse all documents.
   100  	allErrors := make(map[string][]error)
   101  	for _, filename := range flag.Args() {
   102  		// Record the filename with an empty slice for below, where
   103  		// we will emit all files (even those without any errors).
   104  		allErrors[filename] = nil
   105  		documentLoader, err := loadFile(filename)
   106  		if err != nil {
   107  			allErrors[filename] = append(allErrors[filename], err)
   108  			continue
   109  		}
   110  		result, err := gojsonschema.Validate(schemaLoader, documentLoader)
   111  		if err != nil {
   112  			allErrors[filename] = append(allErrors[filename], err)
   113  			continue
   114  		}
   115  		for _, desc := range result.Errors() {
   116  			allErrors[filename] = append(allErrors[filename], errors.New(desc.String()))
   117  		}
   118  	}
   119  
   120  	// Print errors in yaml format.
   121  	totalErrors := 0
   122  	for filename, errs := range allErrors {
   123  		totalErrors += len(errs)
   124  		if len(errs) == 0 {
   125  			fmt.Fprintf(os.Stderr, "%s: ✓\n", filename)
   126  			continue
   127  		}
   128  		fmt.Fprintf(os.Stderr, "%s:\n", filename)
   129  		for _, err := range errs {
   130  			fmt.Fprintf(os.Stderr, "- %s\n", err)
   131  		}
   132  	}
   133  	if totalErrors != 0 {
   134  		os.Exit(1)
   135  	}
   136  }