对 Value 求值



求值是 CUE 的核心能力,并且是其他一切功能(如统一和验证)的底层能力。 本节将展示如何在 Go 中进行求值,而且可以用选项控制其行为。

Value.Validate()

Validate 函数会在一个 Value 上运行求值过程,返回一个 error 类型。 前面已经演示过如何不带参数地使用该函数了。 Validate 也可以接受传入 cue.Option 类型的参数来控制过程。 没错,就是我们之前传给 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 的文档

因为不是每一个 Option 在这里都可以使用, 在这里查看关于这个话题的讨论 issue 1329, 讨论结果更清楚之后我们会更新文档。

Value.Unify()

Unify 函数其实就是 CUE 中的 & 操作符。用同样的方式可以把两个值结合起来。 函数返回的是聚合的结果。

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 文档

Value.Subsume()

Subsume 是一种在的概念下比较两个 Value 的关系的强力工具。 Subsume 会告诉你一个 Value 是不是另一个的实例。换句话说,他检查一个 Value 是否完全向后兼容另一个。

下面的例子里,我们对每一对 Value 检查两个方向的容纳情况。 每一对的两个方向,期待的情况是一个方向通过而另一个失败。

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 文档

为什么 >10 不能被 int 容纳?

原因是 >10 是一个在 number 类型上的限制,这一点和 int 相似。 记得吗,number 可以表示整数和小数。 这意味着这两个限制在格上是平等的。 可以设置为 i: int & >10,这个限制就可以被 int 容纳了。

另外请注意,如果所有 Value 都在同一个文件里,意味着已经聚合过了

Value.Eval()

Eval 函数会解析所有引用,保证具体性,发现所有错误,并最终产生一个新的值。 求值结果包含在返回的新值中。

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 文档

这个函数可能有一个Bug,具体请查看 issue 1326

Value.Evaluate()

这个函数的提案已经提出了,但还没有实现。 它可以像 Validate 和 Syntax 接受 ...Option 参数,处理后返回一个新的 Value。

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

如果你认为这个函数很有用,可以在 issue 1327 下面发言,让开发人员得知。

我们绝不会将你的邮箱分享给任何人。
2022 Hofstadter, Inc