CUE v0.12.0 introduced an experimental new command: gengotypes. This command generates Go types from CUE definitions and places them in .go files. It uses sensible defaults for the generated Go code that you can override by configuring various CUE attributes.

This guide shows you how to use the cue exp gengotypes command.

Why was gengotypes introduced?

When a Go program works with CUE it can be useful to declare Go types which mirror its CUE schemas.

For example, if the program loads a YAML configuration file and validates it using a CUE schema it’s often useful to hold the config as a Go struct value that controls how the program should behave.

Using gengotypes allows you to declare your schemas in CUE and automatically derive compatible Go types – without having to repeat yourself or maintain two versions of the same structure.

Generating Go types

1

Start with some CUE that contains at least one definition:

pets.cue
package pet

// These are the only pet types we can handle.
petTypes: ["dog", "cat", "goldfish"]

// A dog tracks information we hold about each dog.
#dog: {
	// A dog has at least one name.
	name!: string
	age?:  float & <30
}

Hidden definitions (fields with names that start _#) are ignored by the gengotypes command.

2

Run the cue exp gengotypes command targetting the single CUE package in the current directory:

TERMINAL
$ cue exp gengotypes .

The command is silent when it succeeds.

3

Observe the contents of the cue_types_gen.go file:

cue_types_gen.go
// Code generated by "cue exp gengotypes"; DO NOT EDIT.

package pet

// A dog tracks information we hold about each dog.
type Dog struct {
	// A dog has at least one name.
	Name string `json:"name"`

	Age float64 `json:"age,omitempty"`
}

Notice these differences between the contents of pets.cue and cue_types_gen.go:

  • By default, gengotypes ensures the first character of each struct’s name is upper-cased (#dog vs Dog).
  • The top-level regular field petTypes and its comment aren’t present in the Go file. Only fields inside a definition are translated to Go.
  • The optional field age has a numeric constraint in CUE (<30) which can’t be expressed in the simpler, less powerful Go type system – so the Go encodes a float64 type. Generated Go types are guaranteed to accept any value accepted by the CUE definitions but, as with age, they may be more general.

Controlling the generated Go

If you need to control the generated Go package, type, and field names, you can use the @go() attribute to specify them in your CUE:

4

Add another CUE file, morePets.cue, containing the following code:

morePets.cue
package pet

@go(acmecorppets) // Rename this package when emitted as Go.

// A cat represents an instance of a domestic cat.
#cat: {
	@go(cat) // Rename this definition when emitted as Go.

	// Cats consider themselves to have a title, not a name.
	title!:              string @go(Name) // Rename this field when emitted as Go
	socialMediaAccount!: string @go(-)    // Don't emit this field as Go.
}

#horse: {
	@go(-) // Don't emit this definition as Go.
	status: "Unsupported"
}
5

Run the cue exp gengotypes command again:

TERMINAL
$ cue exp gengotypes .
6

Observe the new contents of the cue_types_gen.go file:

cue_types_gen.go
// Code generated by "cue exp gengotypes"; DO NOT EDIT.

package acmecorppets

// A cat represents an instance of a domestic cat.
type cat struct {
	// Cats consider themselves to have a title, not a name.
	Name string `json:"title"`
}

// A dog tracks information we hold about each dog.
type Dog struct {
	// A dog has at least one name.
	Name string `json:"name"`

	Age float64 `json:"age,omitempty"`
}

Notice these differences between the contents of morePets.cue and the new cue_types_gen.go:

  • At the package level:
    • The @go(acmecorppets) attribute renamed the generated Go package from pet.
    • The cue command targetted the whole of the CUE package so the dog-related contents of pets.cue are included under the renamed Go package. Only a single package-level renaming attribute is needed.
  • Inside #cat:
    • The definition-level @go(cat) attribute renamed the generated Go type from the default that would otherwise have been emitted (Cat).
    • The field-level @go(Name) attribute renamed the generated Go field from Title.
    • The field-level @go(-) attribute caused the socialMediaAccount field to be omitted from the generated Go type.
  • Inside #horse, the definition-level @go(-) attribute caused the entire definition to be omitted from the generated Go.

You can also use the @go() attribute to control the type of a generated field – see cue help exp gengotypes for more information.