Evaluation is core CUE and powers everything from unification to validation.
This page will show you how to run the evaluator from Go and
configure the options to control its behavior.
Value.Validate()
Validate runs the evaluator on a value and returns an error.
We’ve seen the function used without arguments.
Validate will also use the many cue.Options so you can control the process.
These are the same options used in Value.Syntax(...Option).
validate.go
package main
import (
"fmt""cuelang.org/go/cue""cuelang.org/go/cue/cuecontext""cuelang.org/go/cue/errors")
const val = `
i: int
s: string
t: [string]: string
_h: int
_h: "hidden"
#d: int
#d: "bar"
`funcmain() {
c := cuecontext.New()
v := c.CompileString(val)
// try out different validation schemes
printErr("loose error", loose(v))
printErr("every error", every(v))
printErr("strict error", strict(v))
fmt.Printf("\nvalue:\n%#v\n", v)
}
funcprintErr(prefix string, err error) {
if err !=nil {
msg := errors.Details(err, nil)
fmt.Printf("%s:\n%s\n", prefix, msg)
}
}
funcloose(v cue.Value) error {
return v.Validate(
// not final or concrete
cue.Concrete(false),
// check minimally
cue.Definitions(false),
cue.Hidden(false),
cue.Optional(false),
)
}
funcevery(v cue.Value) error {
return v.Validate(
// not final or concrete
cue.Concrete(false),
// check everything
cue.Definitions(true),
cue.Hidden(true),
cue.Optional(true),
)
}
funcstrict(v cue.Value) error {
return v.Validate(
// ensure final and concrete
cue.Final(),
cue.Concrete(true),
// check everything
cue.Definitions(true),
cue.Hidden(true),
cue.Optional(true),
)
}
go run validate.go
loose error:
#d: conflicting values int and "bar" (mismatched types int and string):
7:5
8:5
_h: conflicting values int and "hidden" (mismatched types int and string):
5:5
6:5
every error:
#d: conflicting values int and "bar" (mismatched types int and string):
7:5
8:5
_h: conflicting values int and "hidden" (mismatched types int and string):
5:5
6:5
strict error:
#d: conflicting values int and "bar" (mismatched types int and string):
7:5
8:5
_h: conflicting values int and "hidden" (mismatched types int and string):
5:5
6:5
value:
i: int
s: string
t: {
[string]: string
}
_h: _|_ // conflicting values int and "hidden" (mismatched types int and string)
#d: _|_ // conflicting values int and "bar" (mismatched types int and string)
See issue 1329 for a discussion on Options,
as they do not seem to all be used. These docs will be updated once there is more clarification.
Value.Unify()
Unify is the & from your CUE code and combines two values in the same way.
It returns the result of the unification.
unify.go
package main
import (
"fmt""cuelang.org/go/cue""cuelang.org/go/cue/cuecontext""cuelang.org/go/cue/errors")
const schema = `
v: {
i: int
s: string
}
`const val = `
v: {
i: "hello"
s: 1
}
`funcmain() {
c := cuecontext.New()
s := c.CompileString(schema, cue.Filename("schema.cue"))
v := c.CompileString(val, cue.Filename("val.cue"))
// unify the schema and value
u := s.Unify(v)
// check for errors during unification
if u.Err() !=nil {
msg := errors.Details(u.Err(), nil)
fmt.Printf("Unify Error:\n%s\n", msg)
}
// To get all errors, we need to validate
err := u.Validate()
if err !=nil {
msg := errors.Details(err, nil)
fmt.Printf("Validate Error:\n%s\n", msg)
}
// print u
fmt.Printf("%#v\n", u)
}
go run unify.go
Unify Error:
v.i: conflicting values int and "hello" (mismatched types int and string):
schema.cue:3:5
val.cue:3:5
Validate Error:
v.i: conflicting values int and "hello" (mismatched types int and string):
schema.cue:3:5
val.cue:3:5
v.s: conflicting values string and 1 (mismatched types string and int):
schema.cue:4:5
val.cue:4:5
v: {
i: _|_ // conflicting values int and "hello" (mismatched types int and string)
s: _|_ // conflicting values string and 1 (mismatched types string and int)
}
Subsumption is a powerful technique which compares the relation of two values within the lattice.
Subsume will tell you if one value is an instance of another,
or to say it another way, if one value is backwards compatible with another.
In the following example, we check subsume in both directions.
For each pair, we expect one to pass and one to fail.
subsume.go
package main
import (
"fmt""cuelang.org/go/cue""cuelang.org/go/cue/cuecontext""cuelang.org/go/cue/errors")
const schemaWithNumber = `
{
i: number
s: string
}
`const schemaWithInt = `
{
i: int
s: string
}
`const constraint = `
{
i: >10 // this will only be subsumed by number, not int
s: =~"^foo"
}
`const val = `
{
i: 100
s: "foobar"
}
`const constraintType = `
{
i: uint
s: string
}
`funcmain() {
ctx := cuecontext.New()
sn := ctx.CompileString(schemaWithNumber, cue.Filename("schema_number.cue"))
si := ctx.CompileString(schemaWithInt, cue.Filename("schema_int.cue"))
c := ctx.CompileString(constraint, cue.Filename("constraint.cue"))
v := ctx.CompileString(val, cue.Filename("val.cue"))
b := ctx.CompileString(constraintType, cue.Filename("bad.cue"))
// check subsumptions
printErr("sn > c", sn.Subsume(c))
printErr("c > sn", c.Subsume(sn))
printErr("sn > v", sn.Subsume(v))
printErr("v > sn", v.Subsume(sn))
// this seems not intuitive, we'll talk it later
printErr("si > c", si.Subsume(c))
printErr("c > si", c.Subsume(si))
printErr("si > v", si.Subsume(v))
printErr("v > si", v.Subsume(si))
printErr("s > b", si.Subsume(b))
printErr("b > v", b.Subsume(v))
}
funcprintErr(prefix string, err error) {
if err !=nil {
msg := errors.Details(err, nil)
fmt.Printf("%s:\n%s\n", prefix, msg)
}
}
go run subsume.go
c > sn:
field i not present in {i:number,s:string}:
schema_number.cue:2:1
missing field "i"
v > sn:
field i not present in {i:number,s:string}:
schema_number.cue:2:1
missing field "i"
si > c:
field i not present in {i:>10,s:=~"^foo"}:
constraint.cue:2:1
missing field "i"
c > si:
field i not present in {i:int,s:string}:
schema_int.cue:2:1
missing field "i"
v > si:
field i not present in {i:int,s:string}:
schema_int.cue:2:1
missing field "i"
The reason is that >10 is a constraint on numbers, just like int.
Recall number is both decimal and integers.
This means both of our constraints are peers in the lattice.
We could have set i: int & >10 and this would be subsumed by int.
also note, that if the values were all in the same file,
the unification would have been implied.
Value.Eval()
Eval will resolve references, ensure concreteness, uncover all errors and
generally finalizes a value, returning the result in a new value.
eval.go
package main
import (
"fmt""cuelang.org/go/cue""cuelang.org/go/cue/cuecontext""cuelang.org/go/cue/errors")
const val = `
v: {
i: int
s: "hello"
#d: "defn"
_h: "hidden"
o?: string
}
`funcmain() {
c := cuecontext.New()
v := c.CompileString(val, cue.Filename("val.cue"))
// check for errors during compiling
if v.Err() !=nil {
msg := errors.Details(v.Err(), nil)
fmt.Printf("Compile Error:\n%s\n", msg)
}
// print v
fmt.Printf("%#v\n", v)
// eval evaluates and returns a new value
e := v.Eval()
if e.Err() !=nil {
msg := errors.Details(e.Err(), nil)
fmt.Printf("Eval Error:\n%s\n", msg)
}
// print e
fmt.Printf("%#v\n", e)
}