Skip to content

Commit 28a9191

Browse files
committed
cmd/cue: allow vet to check CUE against JSON and YAML
This need some changes in the loader to allow specifying non-CUE files on the command line. Change-Id: I6faa1225e4f4972c6ee8ee7f6183b07fa5b1ecf4 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2921 Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent 2b56efa commit 28a9191

File tree

13 files changed

+202
-15
lines changed

13 files changed

+202
-15
lines changed

cmd/cue/cmd/common_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ func runCommand(t *testing.T, cmd *cobra.Command, name string, args ...string) {
7474
return
7575
}
7676

77-
cmd.SetArgs(append(args, "./"+path))
77+
extra := args
78+
if len(args) == 0 {
79+
extra = append(args, "./"+path)
80+
}
81+
cmd.SetArgs(extra)
7882
rOut, wOut := io.Pipe()
7983
cmd.SetOutput(wOut)
8084
var bOut []byte

cmd/cue/cmd/testdata/hello/vet.out

Whitespace-only changes.

cmd/cue/cmd/testdata/partial/vet.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
some instances are incomplete; use the -c flag to show errors or suppress this message
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
sum: incomplete value ((1 | 2)):
2+
./testdata/partial/partial.cue:4:6
3+
b.idx: invalid non-ground value string (must be concrete int|string):
4+
./testdata/partial/partial.cue:7:9
5+
b.str: incomplete value (string):
6+
./testdata/partial/partial.cue:8:7

cmd/cue/cmd/testdata/vet/data.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# translated messages
2+
translations:
3+
hello:
4+
lang: gsw
5+
text: Grüetzi
6+
---
7+
translations:
8+
hello:
9+
lang: no
10+
text: Hallo
11+
---
12+
translations:
13+
hello:
14+
lang: nl
15+
text: Hallo
16+
skip: true

cmd/cue/cmd/testdata/vet/vet.cue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
translations <_> lang: string
3+
4+
File :: {
5+
translations: {...}
6+
}

cmd/cue/cmd/testdata/vet/vet_expr.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
field "skip" not allowed in closed struct:
2+
./testdata/vet/vet.cue:4:9

cmd/cue/cmd/testdata/vet/vet_file.out

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
translations.hello.lang: conflicting values false and string (mismatched types bool and string):
2+
./testdata/vet/data.yaml:9:11
3+
./testdata/vet/vet.cue:2:24

cmd/cue/cmd/vet.go

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,90 @@
1515
package cmd
1616

1717
import (
18+
"bytes"
19+
"fmt"
20+
"io/ioutil"
21+
"path/filepath"
22+
1823
"cuelang.org/go/cue"
1924
"cuelang.org/go/cue/ast"
25+
"cuelang.org/go/cue/encoding"
2026
"cuelang.org/go/cue/parser"
27+
"cuelang.org/go/internal"
2128
"github.com/spf13/cobra"
2229
"golang.org/x/text/message"
2330
)
2431

32+
const vetDoc = `vet validates CUE and other data files
33+
34+
By default it will only validate if there are no errors.
35+
The -c validates that all regular fields are concrete.
36+
37+
38+
Checking non-CUE files
39+
40+
Vet can also check non-CUE files. The following file formats are
41+
currently supported:
42+
43+
Format Extensions
44+
JSON .json .jsonl .ndjson
45+
YAML .yaml .yml
46+
47+
To activate this mode, the non-cue files must be explicitly mentioned on the
48+
command line. There must also be at least one CUE file to hold the constraints.
49+
50+
In this mode, each file will be verified against a CUE constraint. If the files
51+
contain multiple objects (such as using --- in YAML), they will all be verified
52+
individually.
53+
54+
By default, each file is checked against the root of the loaded CUE files.
55+
The -e can be used to only verify files against the result of an expression
56+
evaluated within the CUE files. This can be useful if the CUE files contain
57+
a set of definitions to pick from.
58+
59+
Examples:
60+
61+
# Check files against a CUE file:
62+
cue vet foo.yaml foo.cue
63+
64+
# Check files against a particular expression
65+
cue vet translations/*.yaml foo.cue -e Translation
66+
67+
If more than one expression is given, all must match all values.
68+
`
69+
2570
func newVetCmd() *cobra.Command {
2671
cmd := &cobra.Command{
2772
Use: "vet",
28-
Short: "validate CUE configurations",
73+
Short: "validate data",
74+
Long: vetDoc,
2975
RunE: doVet,
3076
}
3177

3278
cmd.Flags().BoolP(string(flagConcrete), "c", false,
3379
"require the evaluation to be concrete")
3480

81+
cmd.Flags().StringArrayP(string(flagExpression), "e", nil,
82+
"use this expression to validate non-CUE files")
83+
3584
return cmd
3685
}
3786

3887
func doVet(cmd *cobra.Command, args []string) error {
39-
instances := buildFromArgs(cmd, args)
88+
builds := loadFromArgs(cmd, args)
89+
if builds == nil {
90+
return nil
91+
}
92+
instances := buildInstances(cmd, builds)
4093

41-
var exprs []ast.Expr
42-
for _, e := range flagExpression.StringArray(cmd) {
43-
expr, err := parser.ParseExpr("<expression flag>", e)
44-
if err != nil {
45-
return err
94+
// Go into a special vet mode if the user explicitly specified non-cue
95+
// files on the command line.
96+
for _, a := range args {
97+
enc := encoding.MapExtension(filepath.Ext(a))
98+
if enc != nil && enc.Name() != "cue" {
99+
vetFiles(cmd, instances[0], builds[0].DataFiles)
100+
return nil
46101
}
47-
exprs = append(exprs, expr)
48102
}
49103

50104
shown := false
@@ -80,3 +134,52 @@ func doVet(cmd *cobra.Command, args []string) error {
80134
}
81135
return nil
82136
}
137+
138+
func vetFiles(cmd *cobra.Command, inst *cue.Instance, files []string) {
139+
expressions := flagExpression.StringArray(cmd)
140+
141+
var check cue.Value
142+
143+
if len(expressions) == 0 {
144+
check = inst.Value()
145+
}
146+
147+
for _, e := range expressions {
148+
expr, err := parser.ParseExpr("<expression flag>", e)
149+
exitIfErr(cmd, inst, err, true)
150+
151+
v := inst.Eval(expr)
152+
exitIfErr(cmd, inst, v.Err(), true)
153+
check = check.Unify(v)
154+
}
155+
156+
for _, f := range files {
157+
b, err := ioutil.ReadFile(f)
158+
exitIfErr(cmd, inst, err, true)
159+
160+
ext := filepath.Ext(filepath.Ext(f))
161+
enc := encoding.MapExtension(ext)
162+
if enc == nil {
163+
exitIfErr(cmd, inst, fmt.Errorf("unrecognized extension %q", ext), true)
164+
}
165+
166+
var exprs []ast.Expr
167+
switch enc.Name() {
168+
case "json":
169+
exprs, err = handleJSON(f, bytes.NewReader(b))
170+
case "yaml":
171+
exprs, err = handleYAML(f, bytes.NewReader(b))
172+
default:
173+
exitIfErr(cmd, inst, fmt.Errorf("vet does not support %q", enc.Name()), true)
174+
}
175+
exitIfErr(cmd, inst, err, true)
176+
177+
r := internal.GetRuntime(inst).(*cue.Runtime)
178+
for _, expr := range exprs {
179+
body, err := r.CompileExpr(expr)
180+
exitIfErr(cmd, inst, err, false)
181+
v := body.Value().Unify(check)
182+
exitIfErr(cmd, inst, v.Err(), false)
183+
}
184+
}
185+
}

cmd/cue/cmd/vet_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2019 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+
15+
package cmd
16+
17+
import "testing"
18+
19+
func TestVet(t *testing.T) {
20+
runCommand(t, newVetCmd(), "vet")
21+
22+
cmd := newVetCmd()
23+
mustParseFlags(t, cmd, "-c")
24+
runCommand(t, cmd, "vet_conc")
25+
26+
cmd = newVetCmd()
27+
runCommand(t, cmd, "vet_file", "./testdata/vet/vet.cue", "./testdata/vet/data.yaml")
28+
29+
cmd = newVetCmd()
30+
mustParseFlags(t, cmd, "-e", "File")
31+
runCommand(t, cmd, "vet_expr", "./testdata/vet/vet.cue", "./testdata/vet/data.yaml")
32+
33+
}

0 commit comments

Comments
 (0)