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 }