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`