1// Copyright 2018 The CUE 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 15package cmd 16 17import ( 18 "github.com/spf13/cobra" 19 "golang.org/x/text/message" 20 21 "cuelang.org/go/cue" 22 "cuelang.org/go/cue/errors" 23) 24 25const vetDoc = `vet validates CUE and other data files 26 27By default it will only validate if there are no errors. 28The -c validates that all regular fields are concrete. 29 30 31Checking non-CUE files 32 33Vet can also check non-CUE files. The following file formats are 34currently supported: 35 36 Format Extensions 37 JSON .json .jsonl .ndjson 38 YAML .yaml .yml 39 TEXT .txt (validate a single string value) 40 41To activate this mode, the non-cue files must be explicitly mentioned on the 42command line. There must also be at least one CUE file to hold the constraints. 43 44In this mode, each file will be verified against a CUE constraint. If the files 45contain multiple objects (such as using --- in YAML), they will all be verified 46individually. 47 48By default, each file is checked against the root of the loaded CUE files. 49The -d can be used to only verify files against the result of an expression 50evaluated within the CUE files. This can be useful if the CUE files contain 51a set of definitions to pick from. 52 53Examples: 54 55 # Check files against a CUE file: 56 cue vet foo.yaml foo.cue 57 58 # Check files against a particular expression 59 cue vet translations/*.yaml foo.cue -d '#Translation' 60 61If more than one expression is given, all must match all values. 62` 63 64func newVetCmd(c *Command) *cobra.Command { 65 cmd := &cobra.Command{ 66 Use: "vet", 67 Short: "validate data", 68 Long: vetDoc, 69 RunE: mkRunE(c, doVet), 70 } 71 72 addOrphanFlags(cmd.Flags()) 73 addInjectionFlags(cmd.Flags(), false) 74 75 cmd.Flags().BoolP(string(flagConcrete), "c", false, 76 "require the evaluation to be concrete") 77 78 return cmd 79} 80 81// doVet validates instances. There are two modes: 82// - Only packages: vet all these packages 83// - Data files: compare each data instance against a single package. 84// 85// It is invalid to have data files with other than exactly one package. 86// 87// TODO: allow unrooted schema, such as JSON schema to compare against 88// other values. 89func doVet(cmd *Command, args []string) error { 90 b, err := parseArgs(cmd, args, &config{ 91 noMerge: true, 92 }) 93 exitOnErr(cmd, err, true) 94 95 // Go into a special vet mode if the user explicitly specified non-cue 96 // files on the command line. 97 // TODO: unify these two modes. 98 if len(b.orphaned) > 0 { 99 vetFiles(cmd, b) 100 return nil 101 } 102 103 shown := false 104 105 iter := b.instances() 106 defer iter.close() 107 for iter.scan() { 108 v := iter.value() 109 // TODO: use ImportPath or some other sanitized path. 110 111 concrete := true 112 hasFlag := false 113 if flag := cmd.Flag(string(flagConcrete)); flag != nil { 114 hasFlag = flag.Changed 115 if hasFlag { 116 concrete = flagConcrete.Bool(cmd) 117 } 118 } 119 opt := []cue.Option{ 120 cue.Attributes(true), 121 cue.Definitions(true), 122 cue.Hidden(true), 123 } 124 w := cmd.Stderr() 125 err := v.Validate(append(opt, cue.Concrete(concrete))...) 126 if err != nil && !hasFlag { 127 err = v.Validate(append(opt, cue.Concrete(false))...) 128 if !shown && err == nil { 129 shown = true 130 p := message.NewPrinter(getLang()) 131 _, _ = p.Fprintln(w, 132 "some instances are incomplete; use the -c flag to show errors or suppress this message") 133 } 134 } 135 exitOnErr(cmd, err, false) 136 } 137 exitOnErr(cmd, iter.err(), true) 138 return nil 139} 140 141func vetFiles(cmd *Command, b *buildPlan) { 142 // Use -r type root, instead of -e 143 144 if !b.encConfig.Schema.Exists() { 145 exitOnErr(cmd, errors.New("data files specified without a schema"), true) 146 } 147 148 iter := b.instances() 149 defer iter.close() 150 for iter.scan() { 151 v := iter.value() 152 153 // Always concrete when checking against concrete files. 154 err := v.Validate(cue.Concrete(true)) 155 exitOnErr(cmd, err, false) 156 } 157 exitOnErr(cmd, iter.err(), false) 158} 159