Package cuego
Package cuego allows using CUE constraints in Go programs.
CUE constraints can be used to validate Go types as well as fill out
missing struct fields that are implied from the constraints and the values
already defined by the struct value.
CUE constraints can be added through field tags or by associating
CUE code with a Go type. The field tags method follows the usual
Go pattern:
type Sum struct {
A int `cue:"C-B" json:",omitempty"`
B int `cue:"C-A" json:",omitempty"`
C int `cue:"A+B" json:",omitempty"`
}
func main() {
fmt.Println(cuego.Validate(&Sum{A: 1, B: 5, C: 6}))
}
AddConstraints allows annotating Go types with any CUE constraints.
Validating Go Values
To check whether a struct's values satisfy its constraints, call Validate:
if err := cuego.Validate(p); err != nil {
return err
}
Validation assumes that all values are filled in correctly and will not
infer values. To automatically infer values, use Complete.
Completing Go Values
Package cuego can also be used to infer undefined values from a set of
CUE constraints, for instance to fill out fields in a struct. A value
is considered undefined if it is a nil pointer type or if it is a zero
value and there is a JSON field tag with the omitempty flag.
A Complete will implicitly validate a struct.
Variables
DefaultContext is the shared context used with top-level functions.
var DefaultContext = &Context{}
func Complete(x interface{}) error
Complete sets previously undefined values in x that can be uniquely
determined form the constraints defined on the type of x such that validation
passes, or returns an error, without modifying anything, if this is not
possible.
Complete does a JSON round trip. This means that data not preserved in such a
round trip, such as the location name of a time.Time, is lost after a
successful update.
▾ Example (StructTag)
Code:
type Sum struct {
A int `cue:"C-B" json:",omitempty"`
B int `cue:"C-A" json:",omitempty"`
C int `cue:"A+B" json:",omitempty"`
}
a := Sum{A: 1, B: 5}
err := cuego.Complete(&a)
fmt.Printf("completed: %#v (err: %v)\n", a, err)
a = Sum{A: 2, C: 8}
err = cuego.Complete(&a)
fmt.Printf("completed: %#v (err: %v)\n", a, err)
a = Sum{A: 2, B: 3, C: 8}
err = cuego.Complete(&a)
fmt.Println(errMsg(err))
Output:
completed: cuego_test.Sum{A:1, B:5, C:6} (err: <nil>)
completed: cuego_test.Sum{A:2, B:6, C:8} (err: <nil>)
2 errors in empty disjunction:
conflicting values null and {A:2,B:3,C:8} (mismatched types null and struct)
A: conflicting values 5 and 2
func Constrain(x interface{}, constraints string) error
Constrain associates the given CUE constraints with the type of x or
reports an error if the constraints are invalid or not compatible with x.
Constrain works across package boundaries and is typically called in the
package defining the type. Use a Context to apply constraints locally.
▾ Example
Code:
type Config struct {
Filename string
OptFile string `json:",omitempty"`
MaxCount int
MinCount int
}
err := cuego.Constrain(&Config{}, `{
let jsonFile = =~".json$"
// Filename must be defined and have a .json extension
Filename: jsonFile
// OptFile must be undefined or be a file name with a .json extension
OptFile?: jsonFile
MinCount: >0 & <=MaxCount
MaxCount: <=10_000
}`)
fmt.Println("error:", errMsg(err))
fmt.Println("validate:", errMsg(cuego.Validate(&Config{
Filename: "foo.json",
MaxCount: 1200,
MinCount: 39,
})))
fmt.Println("validate:", errMsg(cuego.Validate(&Config{
Filename: "foo.json",
MaxCount: 12,
MinCount: 39,
})))
fmt.Println("validate:", errMsg(cuego.Validate(&Config{
Filename: "foo.jso",
MaxCount: 120,
MinCount: 39,
})))
Output:
error: nil
validate: nil
validate: 2 errors in empty disjunction:
conflicting values null and {Filename:"foo.json",MaxCount:12,MinCount:39} (mismatched types null and struct)
MinCount: invalid value 39 (out of bound <=12)
validate: 2 errors in empty disjunction:
conflicting values null and {Filename:"foo.jso",MaxCount:120,MinCount:39} (mismatched types null and struct)
Filename: invalid value "foo.jso" (out of bound =~".json$")
func MustConstrain(x interface{}, constraints string)
MustConstrain is like Constrain, but panics if there is an error.
func Validate(x interface{}) error
Validate is a wrapper for Validate called on the global context.
A Context holds type constraints that are only applied within a given
context.
Global constraints that are defined at the time a constraint is
created are applied as well.
type Context struct {
}
func (c *Context) Complete(x interface{}) error
Complete sets previously undefined values in x that can be uniquely
determined form the constraints defined on the type of x such that validation
passes, or returns an error, without modifying anything, if this is not
possible.
A value is considered undefined if it is pointer type and is nil or if it
is a field with a zero value and a json tag with the omitempty tag.
Complete does a JSON round trip. This means that data not preserved in such a
round trip, such as the location name of a time.Time, is lost after a
successful update.
func (c *Context) Constrain(x interface{}, constraints string) error
Constrain associates the given CUE constraints with the type of x or reports
an error if the constraints are invalid or not compatible with x.
func (c *Context) Validate(x interface{}) error
Validate checks whether x validates against the registered constraints for
the type of x.
Constraints for x can be defined as field tags or through the Register
function.