github.com/99designs/gqlgen@v0.17.45/docs/content/recipes/modelgen-hook.md (about)

     1  ---
     2  title: "Allowing mutation of generated models before rendering"
     3  description: How to use a model mutation function to insert a ORM-specific tags onto struct fields.
     4  linkTitle: "Modelgen hook"
     5  menu: { main: { parent: "recipes" } }
     6  ---
     7  
     8  ## BuildMutateHook
     9  
    10  The following recipe shows how to use a `modelgen` plugin hook to mutate generated
    11  models before they are rendered into a resulting file. This feature has many uses but
    12  the example focuses only on inserting ORM-specific tags into generated struct fields. This
    13  is a common use case since it allows for better field matching of DB queries and
    14  the generated data structure.
    15  
    16  First of all, we need to create a function that will mutate the generated model.
    17  Then we can attach the function to the plugin and use it like any other plugin.
    18  
    19  Create `generate.go` file in the same folder as `resolver.go` (usually in `graph` folder) and add the following code:
    20  
    21  ```go
    22  //go:build ignore
    23  
    24  package main
    25  
    26  import (
    27  	"fmt"
    28  	"os"
    29  
    30  	"github.com/99designs/gqlgen/api"
    31  	"github.com/99designs/gqlgen/codegen/config"
    32  	"github.com/99designs/gqlgen/plugin/modelgen"
    33  )
    34  
    35  // Defining mutation function
    36  func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
    37  	for _, model := range b.Models {
    38  		for _, field := range model.Fields {
    39  			field.Tag += ` orm_binding:"` + model.Name + `.` +  field.Name + `"`
    40  		}
    41  	}
    42  
    43  	return b
    44  }
    45  
    46  func main() {
    47  	cfg, err := config.LoadConfigFromDefaultLocations()
    48  	if err != nil {
    49  		fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
    50  		os.Exit(2)
    51  	}
    52  
    53  	// Attaching the mutation function onto modelgen plugin
    54  	p := modelgen.Plugin{
    55  		MutateHook: mutateHook,
    56  	}
    57  
    58  	err = api.Generate(cfg, api.ReplacePlugin(&p))
    59  
    60  	if err != nil {
    61  		fmt.Fprintln(os.Stderr, err.Error())
    62  		os.Exit(3)
    63  	}
    64  }
    65  ```
    66  
    67  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). 
    68  
    69  Now you can run `go generate ./...` to generate the code.
    70  
    71  Now fields from generated models will contain a additional tag `orm_binding`.
    72  
    73  This schema:
    74  
    75  ```graphql
    76  type Object {
    77  	field1: String
    78  	field2: Int
    79  }
    80  ```
    81  
    82  Will gen generated into:
    83  
    84  ```go
    85  type Object struct {
    86  	field1 *string  `json:"field1" orm_binding:"Object.field1"`
    87  	field2 *int     `json:"field2" orm_binding:"Object.field2"`
    88  }
    89  ```
    90  
    91  ## FieldMutateHook
    92  
    93  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.
    94  
    95  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.
    96  
    97  ```go
    98  import (
    99  	"fmt"
   100  	"github.com/vektah/gqlparser/v2/ast"
   101  	"os"
   102  
   103  	"github.com/99designs/gqlgen/api"
   104  	"github.com/99designs/gqlgen/codegen/config"
   105  	"github.com/99designs/gqlgen/plugin/modelgen"
   106  )
   107  
   108  // Defining mutation function
   109  func constraintFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *modelgen.Field) (*modelgen.Field, error) {
   110  	// Call default hook to proceed standard directives like goField and goTag.
   111  	// You can omit it, if you don't need.
   112  	if f, err := modelgen.DefaultFieldMutateHook(td, fd, f); err != nil {
   113  		return f, err
   114  	}
   115  
   116  	c := fd.Directives.ForName("constraint")
   117  	if c != nil {
   118  		formatConstraint := c.Arguments.ForName("format")
   119  
   120  		if formatConstraint != nil{
   121  			f.Tag += " validate:"+formatConstraint.Value.String()
   122  		}
   123  
   124  	}
   125  
   126  	return f, nil
   127  }
   128  
   129  func main() {
   130  	cfg, err := config.LoadConfigFromDefaultLocations()
   131  	if err != nil {
   132  		fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
   133  		os.Exit(2)
   134  	}
   135  
   136  	// Attaching the mutation function onto modelgen plugin
   137  	p := modelgen.Plugin{
   138  		FieldHook: constraintFieldHook,
   139  	}
   140  
   141  	err = api.Generate(cfg, api.ReplacePlugin(&p))
   142  
   143  	if err != nil {
   144  		fmt.Fprintln(os.Stderr, err.Error())
   145  		os.Exit(3)
   146  	}
   147  }
   148  ```
   149  
   150  This schema:
   151  
   152  ```graphql
   153  directive @constraint(
   154  	format: String
   155  ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
   156  
   157  input ObjectInput {
   158  	contactEmail: String @constraint(format: "email")
   159  	website: String @constraint(format: "uri")
   160  }
   161  ```
   162  
   163  Will generate the model:
   164  
   165  ```go
   166  type ObjectInput struct {
   167  	contactEmail *string  `json:"contactEmail" validate:"email"`
   168  	website      *string  `json:"website" validate:"uri"`
   169  }
   170  ```
   171  
   172  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
   173  
   174  ```yaml
   175  directives:
   176    constraint:
   177      skip_runtime: true
   178  ```
   179  
   180  The built-in directives `@goField` and `@goTag` is implemented using the FieldMutateHook. See: `plugin/modelgen/models.go` functions `GoFieldHook` and `GoTagFieldHook`