Evaluating Values



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"
`

func main() {
	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)
}

func printErr(prefix string, err error) {
	if err != nil {
		msg := errors.Details(err, nil)
		fmt.Printf("%s:\n%s\n", prefix, msg)
	}
}

func loose(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),
	)
}

func every(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),
	)
}

func strict(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)

Validate Docs

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
}
`

func main() {
	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)
}

Unify Docs

Value.Subsume()

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
}
`

func main() {
	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))
}

func printErr(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"

Subsume Docs

Why >10 won’t be subsumed by int?

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
}
`

func main() {
	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)
}

go run eval.go

v: {
	i:  int
	s:  "hello"
	#d: "defn"
	_h: "hidden"
	o?: string
}
v: {
	i:  int
	s:  "hello"
	#d: "defn"
	o?: string
}

Eval Docs

There may be a bug in this function, see issue 1326

Value.Evaluate()

This function has been proposed but does not exist yet. It would take ...Option like Validate and Syntax and return a new Value after processing.

func (v Value) Evaluate(opts ...Option) Value {...}

If you find the new function helpful, please let the devs know by contributing to issue 1327.

We'll never share your email with anyone else.
2022 Hofstadter, Inc