This guide demonstrates how to walk a CUE schema using the Go API, programmatically inspecting its structure and types. The Go code shown here is a limited code generator and, as presented, it generates Go structs from simple CUE definitions. It could be adapted to other schema-walking tasks - not just code generation.
Initialize Go and CUE modules
Create a Go module, or use an existing one if that’s more suitable for your situation:
$ go mod init go.example
...
Create a CUE module if you don’t already have one:
$ cue mod init cue.example
The identifiers for the CUE and Go modules don’t need to match, but it doesn’t matter if they’re the same.
Declare a CUE schema
Declare the CUE schema that you wish to walk.
We’ll use the following example.cue
file,
but you should use some CUE that’s specific to your situation.
package example
#Person: {
name!: string
age?: int & >=0
}
#Address: {
line1!: string
line2?: string
line3?: string
country?: string
}
aPerson: #Person & {
name: "John Adams"
}
anAddress: #Address & {
line1: "1600 Pennsylvania Ave NW"
line2: "Washington, DC 20500"
country: "United States of America"
}
someData: aValue: 42
_aHiddenField: aValue: 139
Our example.cue
file contains two
definitions that we want to process:
#Person
and #Address
.
It also includes concrete data fields and a hidden field,
which we don’t consider as schema. The data and hidden fields are included in
order to demonstrate that they are not processed by the code presented below.
Ensure there are no errors in our CUE:
$ cue vet
Use a Go program to walk the schema
Create the file main.go
and add the following code:
package main
import (
"fmt"
"log"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
)
func main() {
ctx := cuecontext.New()
// Load CUE from the package in the current directory
insts := load.Instances([]string{"."}, nil)
v := ctx.BuildInstance(insts[0])
if err := v.Err(); err != nil {
log.Fatal(err)
}
// "Render" the top-level struct definitions as Go types
fmt.Printf("package p\n\n")
it, err := v.Fields(cue.Definitions(true))
if err != nil {
log.Fatal(err)
}
for it.Next() {
v := it.Value()
if !it.Selector().IsDefinition() || v.IncompleteKind() != cue.StructKind {
continue
}
structToType(it.Selector(), it.Value())
}
}
// structToType prints the top-level fields of a struct value
func structToType(name cue.Selector, val cue.Value) {
fmt.Printf("type %v struct {\n", strings.TrimPrefix(name.String(), "#"))
// Iterate through the fields of the struct
it, _ := val.Fields(cue.Optional(true))
for it.Next() {
switch k := it.Value().IncompleteKind(); k {
case cue.StringKind, cue.IntKind, cue.FloatKind, cue.BoolKind:
fmt.Printf("\t%v %v\n", it.Selector().Unquoted(), it.Value().IncompleteKind())
}
}
fmt.Printf("}\n")
}
Add a dependency on cuelang.org/go
and ensure the Go module is tidy:
$ go get cuelang.org/go@v0.12.0-0.dev.0.20241122172925-7d64696b0129
...
$ go mod tidy
...
You can use @latest
in place of a specific version.
Run the Go program:
$ go run .
package p
type Person struct {
name string
age int
}
type Address struct {
line1 string
line2 string
line3 string
country string
}
As you can see from its output, this Go program is a very limited form of code generator that takes each CUE definition and produces a matching Go struct type.
Related content
- The
cue
Go API - The
cue/cuecontext
Go API - The
cue/load
Go API - Reference: cue help mod init
- Reference: cue help vet
- Language Tour: Definitions