Generating Types



To generate types, we need to do two things

  • Update our generator definition
  • Add the template for Go types

Generator Changes

Add the following changes in their appropriate places into the existing generator definition.

gen/server.cue

#HofGenerator: gen.#HofGenerator & {

	// Exposed to the user
	Datamodel: schema.#Datamodel

	// Added to the template input
	In: {
		DM: Datamodel
	}

	// include the type files for rendering
	All: [
		for _, F in TypeFiles {F},
	]

	// Define the files generated from our models
	TypeFiles: [...gen.#File] & [
			for _, M in Datamodel.Models {
			In: {
				TYPE: {
					// embed the model fields
					M

					// Extend to include the fields in CUE order with a list
					// This is needed because we want a deterministic order
					// For example, when defining database columns or caluclating a diff
					// We don't want to sort, rather we want to maintain the order the user specifies
					// While CUE has consistent ordering, the internal Go maps do not
					// the mapping from CUE -> Go -> template rendering can misorder
					OrderedFields: [ for _, F in M.Fields {F}]
				}
			}
			TemplatePath: "type.go"
			// We name each file the same as the Model name and 
			Filepath: "\(Outdir)/types/\(In.TYPE.Name).go"
		},
	]
}

Type Template

The following creates

  • a Go struct for our Model
  • a Go map for storing instances of the type
  • several Go functions as helpers for the data store

Create a new template called type.go

templates/type.go

package types

import (
	"fmt"
)

// Represents a {{ .TYPE.Name }}
type {{ .TYPE.Name }} struct {
	{{ range .TYPE.OrderedFields }}
	{{ .Name }} {{ .Type }}
	{{- end }}
}

// A map type to store {{ .TYPE.Name }}
type {{ .TYPE.Name }}Map map[string]*{{ .TYPE.Name }}

// A var to work with
var {{ .TYPE.Name }}Store {{ .TYPE.Name }}Map

// Note, we are omitting locking and allowing concurrency issues

// initialize our storage
func init() {
	{{ .TYPE.Name }}Store = make({{ .TYPE.Name }}Map)
}

//
//// library funcs
//


func {{ .TYPE.Name }}Create(in *{{ .TYPE.Name }}) error {
	idx := in.{{ .TYPE.Index }}

	// check if already exists
	if _, ok := {{ .TYPE.Name }}Store[idx]; ok {
		return fmt.Errorf("Entry with %v already exists", idx)
	}

	// store the new value
	{{ .TYPE.Name }}Store[idx] = in

	return nil
}

func {{ .TYPE.Name }}Read(idx string) (*{{ .TYPE.Name }}, error) {

	// return if exists
	if val, ok := {{ .TYPE.Name }}Store[idx]; ok {
		return val, nil
	}

	// otherwise return error
	return nil, fmt.Errorf("Entry with %v does not exist", idx)
}

func {{ .TYPE.Name }}Update(in *{{ .TYPE.Name }}) error {
	idx := in.{{ .TYPE.Index }}

	// replace if exists, note we are not dealing with partial updates here
	if _, ok := {{ .TYPE.Name }}Store[idx]; ok {
		{{ .TYPE.Name }}Store[idx] = in
		return nil
	}

	// otherwise return error
	return fmt.Errorf("Entry with %v does not exist", idx)
}

func {{ .TYPE.Name }}Delete(idx string) error {

	// replace if exists, note we are not dealing with partial updates here
	if _, ok := {{ .TYPE.Name }}Store[idx]; ok {
		delete({{ .TYPE.Name }}Store, idx)
		return nil
	}

	// otherwise return error
	return fmt.Errorf("Entry with %v does not exist", idx)
}

Regenerate the server

You can now run hof gen ./example and you should find a new ./output/types directory.

2022 Hofstadter, Inc