v0.17.56
v0.17.56 v0.17.55 v0.17.54 v0.17.53 v0.17.52 v0.17.51 v0.17.50 v0.17.49 v0.17.48 v0.17.47 v0.17.46 v0.17.45 v0.17.44 v0.17.43 v0.17.42 v0.17.41 v0.17.40 v0.17.39 v0.17.38 v0.17.37 master

Modelgen hook

Allowing mutation of generated models before rendering
[edit]

BuildMutateHook

The following recipe shows how to use a modelgen plugin hook to mutate generated models before they are rendered into a resulting file. This feature has many uses but the example focuses only on inserting ORM-specific tags into generated struct fields. This is a common use case since it allows for better field matching of DB queries and the generated data structure.

First of all, we need to create a function that will mutate the generated model. Then we can attach the function to the plugin and use it like any other plugin.

Create generate.go file in the same folder as resolver.go (usually in graph folder) and add the following code:

//go:build ignore

package main

import (
	"fmt"
	"os"

	"github.com/99designs/gqlgen/api"
	"github.com/99designs/gqlgen/codegen/config"
	"github.com/99designs/gqlgen/plugin/modelgen"
)

// Defining mutation function
func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
	for _, model := range b.Models {
		for _, field := range model.Fields {
			field.Tag += ` orm_binding:"` + model.Name + `.` +  field.Name + `"`
		}
	}

	return b
}

func main() {
	cfg, err := config.LoadConfigFromDefaultLocations()
	if err != nil {
		fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
		os.Exit(2)
	}

	// Attaching the mutation function onto modelgen plugin
	p := modelgen.Plugin{
		MutateHook: mutateHook,
	}

	err = api.Generate(cfg, api.ReplacePlugin(&p))

	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(3)
	}
}

In resolver.go, add //go:generate go run generate.go (or replace //go:generate go run github.com/99designs/gqlgen generate if you have it there).

Now you can run go generate ./... to generate the code.

Now fields from generated models will contain a additional tag orm_binding.

This schema:

type Object {
	field1: String
	field2: Int
}

Will gen generated into:

type Object struct {
	field1 *string  `json:"field1" orm_binding:"Object.field1"`
	field2 *int     `json:"field2" orm_binding:"Object.field2"`
}

FieldMutateHook

For more fine grained control over model generation, a graphql schema aware a FieldHook can be provided. This hook has access to type and field graphql definitions enabling the hook to modify the modelgen.Field using directives defined within the schema.

The below recipe uses this feature to add validate tags to the generated model for use with go-playground/validator where the validate tags are defined in a constraint directive in the schema.

import (
	"fmt"
	"github.com/vektah/gqlparser/v2/ast"
	"os"

	"github.com/99designs/gqlgen/api"
	"github.com/99designs/gqlgen/codegen/config"
	"github.com/99designs/gqlgen/plugin/modelgen"
)

// Defining mutation function
func constraintFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *modelgen.Field) (*modelgen.Field, error) {
	// Call default hook to proceed standard directives like goField and goTag.
	// You can omit it, if you don't need.
	if f, err := modelgen.DefaultFieldMutateHook(td, fd, f); err != nil {
		return f, err
	}

	c := fd.Directives.ForName("constraint")
	if c != nil {
		formatConstraint := c.Arguments.ForName("format")

		if formatConstraint != nil{
			f.Tag += " validate:"+formatConstraint.Value.String()
		}

	}

	return f, nil
}

func main() {
	cfg, err := config.LoadConfigFromDefaultLocations()
	if err != nil {
		fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
		os.Exit(2)
	}

	// Attaching the mutation function onto modelgen plugin
	p := modelgen.Plugin{
		FieldHook: constraintFieldHook,
	}

	err = api.Generate(cfg, api.ReplacePlugin(&p))

	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(3)
	}
}

This schema:

directive @constraint(
	format: String
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION

input ObjectInput {
	contactEmail: String @constraint(format: "email")
	website: String @constraint(format: "uri")
}

Will generate the model:

type ObjectInput struct {
	contactEmail *string  `json:"contactEmail" validate:"email"`
	website      *string  `json:"website" validate:"uri"`
}

If a constraint being used during generation should not be published during introspection, the directive should be listed with skip_runtime:true in gqlgen.yml

directives:
  constraint:
    skip_runtime: true

The built-in directives @goField and @goTag is implemented using the FieldMutateHook. See: plugin/modelgen/models.go functions GoFieldHook and GoTagFieldHook