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

1

Create a Go module, or use an existing one if that’s more suitable for your situation:

TERMINAL
$ go mod init go.example
...
2

Create a CUE module if you don’t already have one:

TERMINAL
$ 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

3

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.

example.cue
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.

4

Ensure there are no errors in our CUE:

TERMINAL
$ cue vet

Use a Go program to walk the schema

5

Create the file main.go and add the following code:

main.go
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")
}
6

Add a dependency on cuelang.org/go and ensure the Go module is tidy:

TERMINAL
$ go get cuelang.org/go@v0.11.1
...
$ go mod tidy
...

You can use @latest in place of a specific version.

7

Run the Go program:

TERMINAL
$ 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.